"""
Frame_LH.py

This module contains the FrameLH class for performing likelihood computations
in cross-correlation analysis of exoplanet atmospheric spectra.

The main functionality includes:

- Setting up GUI elements for parameter input
- Computing likelihood grids over Kp (planetary semi-amplitude), RV (radial velocity),
  and scale factor parameters
- Generating diagnostic plots and saving results

Author: [Original Author]
Date: [Date]
"""
from pathlib import Path
from pickle import dump
from tkinter import messagebox

import numpy as np
import matplotlib
matplotlib.rcParams['agg.path.chunksize'] = 10000
import os
if not os.environ.get('SPHINX_BUILD'):
    matplotlib.use("TkAgg")
import matplotlib.pyplot as plt
from numpy import roll
from scipy import stats
from scipy.interpolate import splrep, splev
from petitRADTRANS import physical_constants as phys_const

from GUIBRUSHR.GUI.DataInterface.DataInterface import get_target_info
from GUIBRUSHR.GUI.WIDGET.MyDropDown import MyDropdown
from GUIBRUSHR.GUI.WIDGET.MyEntry import MyEntry
from GUIBRUSHR.GUI.WIDGET.MyFigure import MyFigure
from GUIBRUSHR.GUI.WIDGET.MyButton import MyButton
from GUIBRUSHR.GUI.LAYOUT.MyPanel import MyPanel
from GUIBRUSHR.General_Constants.Classes.Night import Night
from GUIBRUSHR.General_Constants.FunctionsAndConstants.Constant_Variables import (
    ConstantVariables
)
from GUIBRUSHR.General_Constants.FunctionsAndConstants.general_functions import (
    read_fits, create_path_night, trpca, read_depth_fits
)
from GUIBRUSHR.General_Constants.Classes.HelpButton import HelpButton


# def compute_lhood_for_grid(
#     kp_val, rvvec, sfvec, phase_sine, vsys, baryvel, arr2, clight, csscaled,
#     intransit, spAbPCA_order, good_pixels, nfc_h, tell_rm_method, smooth_on,
#     smooth_size, apply_stadardize, model_reprocessing, spec_order, errfsp_sub,
#     pca_mode="spatial"
# ):
#     """
#     Compute the likelihood for a given Kp grid value over all RV and scale values.
#
#     This function computes the likelihood by:
#     1. Computing velocity shifts for each RV value
#     2. Resampling the cross-correlation model at shifted wavelengths
#     3. Applying PCA processing to the data
#     4. Computing chi-squared values for all scale factors
#     5. Converting to likelihood values
#
#     Parameters:
#         kp_val: Planetary semi-amplitude value for this grid point
#         rvvec: Array of radial velocity values to test
#         sfvec: Array of scale factor values to test
#         phase_sine: Sine of orbital phase for each observation
#         vsys: Systemic velocity of the system
#         baryvel: Barycentric velocity corrections
#         arr2: Wavelength array for good pixels
#         clight: Speed of light constant
#         csscaled: Spline representation of cross-correlation model
#         intransit: Indices of in-transit observations
#         spAbPCA_order: PCA-processed spectra for this order
#         good_pixels: Indices of good pixels for this order
#         nfc_h: Number of components for PCA
#         tell_rm_method: Telluric removal method
#         smooth_on: Whether smoothing is enabled
#         smooth_size: Size of smoothing kernel
#         model_reprocessing: Model reprocessing method
#         spec_order: Spectral data for this order
#         errfsp_sub: Error array subset for good pixels
#
#     Returns:
#         local_lhood: 2D array of likelihood values (len(rvvec), len(sfvec))
#
#     Note:
#         All inputs must be picklable (numpy arrays, numbers, etc.) for
#         potential parallelization.
#     """
#     # Initialize likelihood array for this Kp value
#     local_lhood = np.zeros((len(rvvec), len(sfvec)))
#
#     # Compute phase factor for this Kp value, vectorized over images
#     kpph = kp_val * phase_sine
#
#     # Loop over radial velocity values
#     for irv, rv_val in enumerate(rvvec):
#         # Compute the total velocity shift (vector over images)
#         vtot = -kpph - vsys + baryvel - rv_val
#
#         # Compute the wavelength shift for each good pixel and each image
#         dl2 = arr2 * vtot / clight
#
#         # Evaluate the cross-correlation spline at the shifted wavelengths
#         rcvsd_resample = splev(dl2 + arr2, csscaled, der=0)
#         mtolh = np.log10(rcvsd_resample)
#
#         # Prepare the mask for PCA (work only with good pixels for this order)
#         lcrm_mask = spAbPCA_order.copy()
#         lcrm_mask_nosignal = spAbPCA_order.copy()
#         lcrm_mask_nomask = np.zeros_like(spAbPCA_order)
#
#         # Update the mask only in the in-transit regions
#         lcrm_mask[:, intransit] = (
#             mtolh[:, intransit] + lcrm_mask[:, intransit]
#         )
#
#         # Apply custom PCA function
#         lcrm_pca, error_trpca = trpca(
#             lcrm_mask,
#             lcrm_mask_nomask,
#             good_pixels,
#             nfc_h,
#             smooth_on,
#             smooth_size,
#             apply_stadardize,
#             model_reprocessing,
#             pca_mode=pca_mode
#         )
#
#         # Extract only in-transit data
#         lcrm_pca = lcrm_pca[:, intransit]
#
#         # Get uncertainties and observed flux for good pixels in in-transit images
#         errplusjit = errfsp_sub[:, intransit]
#         flux_good = spec_order[good_pixels, :][:, intransit]
#
#         # Vectorize computation over scale factors
#         scale_factors = 10 ** sfvec  # shape: (n_sf,)
#         lcrm_pow = 10 ** lcrm_pca  # shape: (n_good, n_intransit)
#
#         # Compute model flux for all scale factors simultaneously
#         model_flux_all = (
#             1 + (lcrm_pow - 1)[:, :, None] * scale_factors[None, None, :]
#         )
#
#         # Convert observed flux from log scale
#         flux_obs = 10 ** flux_good  # shape: (n_good, n_intransit)
#
#         # Broadcast error array for vectorized computation
#         err_broadcast = errplusjit[:, :, None]
#
#         # Compute chi-squared for all scale factors at once
#         chi2_all = np.sum(
#             ((flux_obs[:, :, None] - model_flux_all) / err_broadcast) ** 2,
#             axis=(0, 1)
#         )
#
#         # Sum log errors (constant over scale factors)
#         log_err_sum = np.sum(np.log(errplusjit))
#
#         # Compute likelihood values for all scale factors
#         local_lhood[irv, :] = -log_err_sum - 0.5 * chi2_all
#
#     return local_lhood


class FrameLH(MyPanel):
    """
    GUI panel for likelihood computation in cross-correlation analysis.
    
    This class provides a user interface for setting up and running likelihood
    computations over grids of planetary parameters (Kp, RV, scale factor).
    It handles data loading, parameter setup, computation execution, and
    result visualization.
    
    Attributes:
        target_information: Information about the target system
        path_folder_targets: Path to target data folder
        widget_nights: Widget containing night selection controls
        frame_input: Input frame containing path information
        Various GUI elements for parameter input and control
    """
    
    def __init__(
        self, parent, color, row, column, path_default, frame_input, window,
        width_GUI, height_GUI, widget_nights, **kwargs
    ):
        """
        Initialize the FrameLH panel with GUI elements.
        
        Parameters:
            parent: Parent widget
            color: Background color for the panel
            row: Grid row position
            column: Grid column position
            path_default: Default path for file operations
            frame_input: Input frame containing path controls
            window: Main window reference
            width_GUI: GUI width for sizing calculations
            height_GUI: GUI height for sizing calculations
            widget_nights: Widget containing night selection controls
            **kwargs: Additional keyword arguments for parent class
        """
        # Initialize instance variables
        self.target_information = None
        self.path_folder_targets = (
            frame_input.frame_path.path_folder_targets.get_text()
        )
        self.widget_nights = widget_nights
        self.global_exc = 0
        self.frame_input = frame_input
        self.path_default = path_default
        self.window = window
        self.color = color
        
        # Initialize parent class
        super().__init__(parent, color, row, column, **kwargs)

        # Single row panel for all controls
        self.row1 = MyPanel(self, color, 0, 1)

        # Help text dictionary
        help_text = {
            "rv limit": "Maximum radial velocity offset in km/s to search around the expected rest-frame velocity. The grid will span from -rv_limit to +rv_limit. This parameter accounts for uncertainties in the systemic velocity and instrument calibration. Recommended: 10-20 km/s.",

            "rv step": "Step size in km/s for the radial velocity grid. Smaller steps provide finer resolution but increase computation time significantly (scales linearly). Recommended: 1-3 km/s for initial searches, 0.5-1 km/s for refinement.",

            "Kp limit": "Maximum planetary semi-amplitude in km/s to test. Kp is the amplitude of the planet's radial velocity orbit. The grid spans from 0 to Kp_limit. Set based on expected planetary mass and orbital parameters. Hot Jupiters: typically 100-250 km/s.",

            "Kp step": "Step size in km/s for the Kp grid. Controls the resolution of the Kp-RV map. Smaller steps provide better precision for determining the planet's Kp value. Recommended: 1-5 km/s depending on expected signal strength.",

            "sf limit": "Maximum scale factor (in log10) to test for the atmospheric signal strength. The grid spans from -sf_limit to +sf_limit in log10 space. This accounts for uncertainties in atmospheric model normalization and signal amplitude. Recommended: 0.5-1.5 (corresponds to factors of ~0.3x to 3x or 0.1x to 30x).",

            "sf step": "Step size for the scale factor grid in log10 space. Controls the resolution of likelihood computation over different signal strengths. Recommended: 0.05-0.2 depending on expected SNR.",

            "Smooth Size": "Size of the uniform smoothing kernel applied to remove residual low-frequency systematics after telluric removal. Set to > 1 to enable smoothing. Larger values remove more structure but may suppress real atmospheric features. Recommended: 20-50 for high-SNR data, 0 or 1 to disable.",

            "Model Rep": "Model reprocessing method: 'hard' applies full telluric PCA removal to the data with the injected model signal, 'soft' uses minimal telluric processing for cleaner injection testing. Hard is recommended for real data analysis, soft for validation and testing."
        }

        # Create help button
        self.help_button = HelpButton(
            self.row1, 0, 0,
            "Likelihood Computation Parameters Help",
            help_text,
            columnspan=1
        )

        # Grid parameters: rv, kp, sf limits and steps
        self.entry_rv_limit = MyEntry(
            self.row1, 0, 1, "15", "rv limit:", color=color, columnspan=2
        )
        self.entry_rv_step = MyEntry(
            self.row1, 0, 3, "3", "rv step:", color=color, columnspan=2
        )
        self.entry_kp_limit = MyEntry(
            self.row1, 0, 5, "201", "Kp limit:", color=color, columnspan=2
        )
        self.entry_kp_step = MyEntry(
            self.row1, 0, 7, "3", "Kp step:", color=color, columnspan=2
        )
        self.entry_sf_limit = MyEntry(
            self.row1, 0, 9, "1.1", "sf limit:", color=color, columnspan=2
        )
        self.entry_sf_step = MyEntry(
            self.row1, 0, 11, "0.1", "sf step:", color=color, columnspan=2
        )

        # Smoothing and model reprocessing
        self.entry_smooth_size = MyEntry(
            self.row1, 0, 13, "40", "Smooth Size:", color=color, columnspan=2
        )
        self.dropdown_model_rep = MyDropdown(
            self.row1, 0, 15, ConstantVariables.LIST_MODEL_REPRO, "Model Rep:",
            color=color, columnspan=2
        )

        # Control buttons
        self.button_lh = MyButton(
            self.row1, 0, 17, 'RUN LH', '#f5674a', self.lh_func, color_panel=color
        )
        self.button_show_lh = MyButton(
            self.row1, 0, 18, 'SHOW LAST LH', '#33c4ff', self.show_lh,
            color_panel=color
        )

        # Set initialization flag
        self.global_exc = 1

    def _get_gui_parameters(self):
        """
        Extract and validate parameters from GUI elements.
        
        Returns:
            dict: Dictionary containing all GUI parameters
        """
        # Get smoothing parameters
        smooth_size = int(self.entry_smooth_size.get_value())
        smooth_on = smooth_size > 1
        
        # Get model and target information
        model_reprocessing = self.dropdown_model_rep.get_value()
        target = self.widget_nights.dropdown_target.get_value()
        rad_mode = self.widget_nights.dropdown_rad_mode.get_value()

        # Initialize lists
        dates = []
        instruments = []
        path_instruments = []
        # Get night and order information
        order_sel = self.widget_nights.textfield_order_selection.get_text()
        selected_nights = self.widget_nights.textfield_selection.get_text()
        # Process each instrument's nights
        for instrument_nights in selected_nights.split(";"):
            instrument_nights = instrument_nights.split(":")
            path_results = create_path_night(
                self.path_folder_targets, target,
                rad_mode, instrument_nights[0]
            )
            new_dates = instrument_nights[1].split(",")
            for _ in new_dates:
                path_instruments.append(path_results)
                instruments.append(instrument_nights[0])
            dates = dates + new_dates
        
        # Get processing method
        tell_rm_method = self.widget_nights.dropdown_tell_rm_method.get_value()
        
        # Get grid parameters
        rv_limit = float(self.entry_rv_limit.get_value())
        rv_step = float(self.entry_rv_step.get_value())
        kp_limit = float(self.entry_kp_limit.get_value())
        kp_step = float(self.entry_kp_step.get_value())
        sf_limit = float(self.entry_sf_limit.get_value())
        sf_step = float(self.entry_sf_step.get_value())
        
        return {
            'smooth_size': smooth_size,
            'smooth_on': smooth_on,
            'model_reprocessing': model_reprocessing,
            'target': target,
            'order_sel': order_sel,
            'dates': dates,
            'instruments': instruments,
            'path_instruments': path_instruments,
            'rad_mode': rad_mode,
            'tell_rm_method': tell_rm_method,
            'rv_limit': rv_limit,
            'rv_step': rv_step,
            'kp_limit': kp_limit,
            'kp_step': kp_step,
            'sf_limit': sf_limit,
            'sf_step': sf_step
        }

    def _setup_paths_and_model(self, params):
        """
        Set up file paths and load cross-correlation model.
        
        Parameters:
            params: Dictionary of GUI parameters
            
        Returns:
            dict: Dictionary containing paths and model information
        """
        # Create result paths
        path_results = create_path_night(
            self.path_folder_targets, 
            params['target'],
            self.widget_nights.dropdown_rad_mode.get_value(),
            self.widget_nights.dropdown_hr_instrument.get_value()
        )
        
        # Get target information
        self.target_information = get_target_info(
            self.path_folder_targets, params['target']
        )
        
        # Set up cross-correlation model paths
        path_cc = str(Path(
            self.path_folder_targets, 
            params['target'], 
            "Models",
            self.widget_nights.dropdown_rad_mode.get_value(),
            self.widget_nights.dropdown_cross_corr.get_value()
        ))
        
        # Load cross-correlation model
        rad_mode = self.widget_nights.dropdown_rad_mode.get_value()
        model = read_depth_fits(path_cc, rad_mode)
        wl = read_fits(str(Path(path_cc, "wavelength.fits")))
        model_name = self.widget_nights.dropdown_cross_corr.get_value()
        
        # Pre-compute spline for cross-correlation scaling
        csscaled = splrep(wl, model)
        
        # Create output directory
        path_night_res = str(Path(path_results, params['dates'][0]))
        lh_dir = Path(path_night_res, "lh")
        lh_dir.mkdir(parents=True, exist_ok=True)
        
        # Generate filenames
        ndate = len(params['dates'])
        suffix = "_multi" if ndate > 1 else ""
        base_filename = (
            f"{params['order_sel']}_{params['tell_rm_method']}_"
            f"{model_name}{suffix}"
        )
        
        filepng1 = str(Path(
            path_results, params['dates'][0], "lh",
            f"{base_filename}.png"
        ))
        filepng2 = str(Path(
            path_results, params['dates'][0], "lh",
            f"{base_filename}_3sc.png"
        ))
        path_pickle = str(Path(
            path_results, params['dates'][0], "lh",
            f"{base_filename}_lh_hood.pkl"
        ))
        
        return {
            'path_results': path_results,
            'csscaled': csscaled,
            'model_name': model_name,
            'filepng1': filepng1,
            'filepng2': filepng2,
            'path_pickle': path_pickle
        }

    @staticmethod
    def _create_parameter_grids(params):
        """
        Create parameter grids for likelihood computation.
        
        Parameters:
            params: Dictionary of GUI parameters
            
        Returns:
            tuple: (rvvec, kpvec, sfvec) parameter arrays
        """
        rvvec = np.arange(
            -params['rv_limit'], 
            params['rv_limit'] + params['rv_step'], 
            params['rv_step']
        )
        kpvec = np.arange(
            0, 
            params['kp_limit'] + params['kp_step'], 
            params['kp_step']
        )
        sfvec = np.arange(
            -params['sf_limit'], 
            params['sf_limit'] + params['sf_step'], 
            params['sf_step']
        )
        
        return rvvec, kpvec, sfvec

    def _compute_likelihood_for_order(
        self, h, night_obj, params, rvvec, kpvec, sfvec, csscaled
    ):
        """
        Compute likelihood for a single spectral order.
        
        Parameters:
            h: Order index
            night_obj: Night object
            params: Dictionary of GUI parameters
            rvvec, kpvec, sfvec: Parameter grids
            csscaled: Cross-correlation spline
            
        Returns:
            numpy.ndarray: Likelihood contribution for this order
        """
        phase_sine = np.sin(2 * np.pi * night_obj.phases_considered) # night_obj.phase_new_range_considered

        lfsp = night_obj.lambdas[:, h, :]  # Wavelength array
        fsp = night_obj.spectra[:, h, :]  # Observed spectra
        errfsp = night_obj.sigma_spectra[:, h, :]  # Error bars
        good_pixels = night_obj.maskinvm[:, h, :] == 0 # Valid pixel mask

        # APPLY DOPPLER SHIFTS TO WAVELENGTH GRID
        # Reshape wavelength array to match good pixels structure
        wl_data_masked = lfsp[np.where(good_pixels)].reshape(
            -1, np.shape(good_pixels)[1]
        )

        # Initialize likelihood array for this order
        order_lhood = np.zeros((len(kpvec), len(rvvec), len(sfvec)))

        # Get speed of light constant
        clight = ConstantVariables.CLIGHT

        for ikp, kp_val in enumerate(kpvec):
            # Compute velocity component from planetary semi-amplitude
            kpph = np.array(kp_val * phase_sine)  # shape: (nimg,)

            for irv, rv_val in enumerate(rvvec):
                # Calculate stellar radial velocity for stellar spectrum correction
                ks = (
                    kp_val * self.target_information.mass /
                    (self.target_information.stellar_mass * ConstantVariables.SOLAR_TO_JUPITER_MASSES)
                )
                vtot_star = (
                    ks * np.sin(2 * np.pi * night_obj.phases_considered) -
                    self.target_information.systemic_velocity +
                    night_obj.barycentric_velocity_considered
                )

                # Compute total velocity shift (vectorized over images)
                vtot = np.array(
                    -kpph - self.target_information.systemic_velocity +
                    night_obj.barycentric_velocity_considered - rv_val
                )  # shape: (nimg,)
                # Calculate Doppler shifts for planet and star
                dl_planet = np.array(wl_data_masked * vtot / clight)  # Planet velocity shift
                wlen_shifted_planet = np.array(dl_planet + wl_data_masked)  # Planet rest frame wavelengths
                dl_star = wl_data_masked * vtot_star / clight  # Stellar velocity shift
                wlen_shifted_star = dl_star + wl_data_masked  # Stellar rest frame wavelengths
                # CALCULATE OBSERVED SPECTRUM BASED ON OBSERVATION MODE
                if params["rad_mode"] == 'Transmission':
                    # TRANSMISSION SPECTROSCOPY
                    # Evaluate atmospheric model at Doppler-shifted wavelengths
                    model_eval = splev(wlen_shifted_planet, csscaled, der=0)

                    # Convert from altitude to transit depth
                    # mol_mod = model_eval / phys_const.r_jup_mean
                    # mol_modf = (mol_mod / (self.target_information.stellar_radius * 9.731)) ** 2
                    # spectrum = 1 - mol_modf  # Transit depth spectrum
                else:
                    # EMISSION SPECTROSCOPY
                    # Calculate stellar spectrum contribution
                    pass
                    # if self.stellar_spectrum_type == "Phoenix":
                    #     stellar_spectrum_HR = splev(wlen_shifted_star * 1e-7, stellar_spline_model_HR)
                    #
                    # # Evaluate planetary emission model
                    # model_eval = splev(wlen_shifted_planet, csscaled, der=0)
                    #
                    # # Calculate planet-to-star flux ratio
                    # Fscaled = (model_eval / (np.pi * stellar_spectrum_HR)) * (
                    #         rp / (self.target_information.stellar_radius * phys_const.r_sun)) ** 2
                    # spectrum = 1 + Fscaled  # Combined stellar + planetary flux

                # PREPARE DATA FOR TELLURIC REMOVAL VIA PCA
                mtolh = np.log10(model_eval)  # Convert to log scale for PCA processing
                good_pixel = np.where(good_pixels[:, -1])
                good_pixel = good_pixel[0]

                # SETUP PCA PROCESSING BASED ON METHOD
                if params["model_reprocessing"] == "hard":
                    # Hard PCA: Use existing telluric removal data
                    lcrm_mask = night_obj.array_reconstructed_from_PCA_components[good_pixel, h, :]
                    lcrm_mask_nosignal = night_obj.array_reconstructed_from_PCA_components[good_pixel, h, :]
                    lcrm_mask_nomask = np.zeros(np.shape(night_obj.array_reconstructed_from_PCA_components[:, h, :]))
                else:  # "soft" method
                    # Soft PCA: Simplified approach with minimal telluric removal
                    lcrm_mask = night_obj.array_reconstructed_from_PCA_components[good_pixel, h, :]
                    lcrm_mask_nosignal = night_obj.array_reconstructed_from_PCA_components[good_pixel, h, :]
                    lcrm_mask_nomask = np.zeros(np.shape(night_obj.array_reconstructed_from_PCA_components[:, h, :]))
                    # Set to zero for simplified PCA
                    lcrm_mask[:, :] = 0
                    lcrm_mask_nosignal[:, :] = 0

                # INJECT ATMOSPHERIC MODEL INTO IN-TRANSIT DATA
                # Add theoretical atmospheric signal to in-transit observations
                lcrm_mask[:, night_obj.intransit] = (
                        mtolh + (lcrm_mask[:, night_obj.intransit])
                )

                # Call custom PCA function to compute refined mask
                lcrm_pca, error_trpca = trpca(
                    lcrm_mask,
                    lcrm_mask_nomask,
                    good_pixel,
                    night_obj.nfc[h],
                    night_obj.smooth_on,  # Smoothing flag
                    night_obj.smooth_size,  # Smoothing size
                    night_obj.apply_standardize,
                    params['model_reprocessing'],
                    night_obj.subtraction_of_the_average_spectrum,
                    night_obj.pca_mode  # PCA mode: spatial or temporal
                )
                
                # Only consider in-transit columns
                lcrm_pca = lcrm_pca[:, night_obj.intransit]
                
                # Prepare error and flux for good pixels over in-transit images
                errplusjit = errfsp[good_pixel, :]
                for isf in range(len(sfvec)):
                    scale = sfvec[isf]
                    chiquadro = np.sum(
                        (
                            (
                                fsp[good_pixel, :]  # Observed data
                                - (lcrm_pca * scale)  # Scaled model
                            )
                            / errplusjit  # Error bars
                        )
                        ** 2
                    )
                    #
                    order_lhood[ikp, irv, isf] += np.array(
                            -np.sum(np.log(errplusjit)) - 0.5 * chiquadro
                    )
        
        return order_lhood

    @staticmethod
    def _compute_detection_significance(lhood):
        """
        Compute detection significance from likelihood array.
        
        Parameters:
            lhood: 3D likelihood array
            
        Returns:
            tuple: (sigma_map, max_idx) where sigma_map is the 3D significance
                   array and max_idx is the indices of maximum likelihood
        """
        # Compute delta log-likelihood
        max_lhood = np.max(lhood)
        dlogL = 2 * (max_lhood - lhood)
        
        # Compute p-values and convert to sigma significance
        # Degrees of freedom = 2 (assumed)
        p_values = stats.chi2.sf(dlogL, 2)
        sigma_map = stats.norm.isf(p_values / 2.0)
        
        # Replace non-finite values with 30
        sigma_map[~np.isfinite(sigma_map)] = 30
        
        # Find grid indices where likelihood is maximum
        max_idx = np.unravel_index(np.argmax(lhood), lhood.shape)
        
        return sigma_map, max_idx

    def _create_diagnostic_plots(
        self, sigma_map, max_idx, rvvec, kpvec, sfvec, paths_dict
    ):
        """
        Create and save diagnostic contour plots.
        
        Parameters:
            sigma_map: 3D significance map
            max_idx: Indices of maximum likelihood
            rvvec, kpvec, sfvec: Parameter grids
            paths_dict: Dictionary containing file paths
        """
        # Extract maximum values
        kp_max = kpvec[max_idx[0]]
        rv_max = rvvec[max_idx[1]]
        sf_max = sfvec[max_idx[2]]
        
        print(f"Kp = {kp_max}, rv = {rv_max}, scale = {sf_max}")
        
        # Get slice at maximum scale factor
        sf_max_idx = np.where(sfvec == sf_max)[0][0]
        sigma_slice = sigma_map[:, :, sf_max_idx]
        
        # Define plot parameters
        colori = ["#FFD700", "#FF8C00", "#990000"]
        contour_colors = roll(colori, 2)
        contour_levels = [0, 1, 2, 3]
        
        # First plot: Contours with specified levels and custom labels
        plt.figure()
        plt.contourf(
            rvvec, kpvec, sigma_slice, 
            levels=contour_levels, colors=colori, alpha=0.7
        )
        cs1 = plt.contour(
            rvvec, kpvec, sigma_slice,
            levels=contour_levels, colors=contour_colors
        )
        
        # Add contour labels
        fmt = {
            lev: lab for lev, lab in zip(
                contour_levels, 
                ["", r"$1\sigma$", r"$2\sigma$", r"$3\sigma$"]
            )
        }
        cs1.clabel(cs1.levels, inline=True, fmt=fmt)
        
        # Add axis labels and reference lines
        plt.xlabel(r"$V_{rest}$ ($km\,s^{-1}$)")
        plt.ylabel(r"$K_{p}$ ($km\,s^{-1}$)")
        plt.axvline(0, linestyle='--', color='white')
        plt.axhline(self.target_information.kp, linestyle='--', color='white')
        
        # Save first plot
        plt.savefig(paths_dict['filepng2'], bbox_inches='tight')
        plt.close()
        
        # Second plot: Filled contours with colorbar
        fig, ax = plt.subplots()
        cs = ax.contourf(
            rvvec, kpvec, sigma_slice,
            levels=10, alpha=0.7, cmap="inferno"
        )
        fig.colorbar(cs, ax=ax)
        
        # Add axis labels and reference lines
        ax.set_xlabel(r"$V_{rest}$ ($km\,s^{-1}$)")
        ax.set_ylabel(r"$K_{p}$ ($km\,s^{-1}$)")
        ax.axvline(0, linestyle='--', color='white')
        ax.axhline(self.target_information.kp, linestyle='--', color='white')
        
        # Save second plot
        plt.savefig(paths_dict['filepng1'], bbox_inches='tight')
        plt.close(fig)

    def lh_func(self):
        """
        Perform likelihood computation over multiple nights and orders.

        This is the main function that orchestrates the entire likelihood
        computation process. It:
        1. Retrieves GUI parameters and sets up file paths
        2. Loads cross-correlation models and creates parameter grids
        3. Iterates over nights and spectral orders
        4. Computes likelihood arrays over (Kp, RV, scale) parameter space
        5. Computes detection significance and creates diagnostic plots
        6. Saves results to pickle files and updates the GUI display

        The function maintains the exact sequence of operations from the
        original implementation while organizing the code into logical
        helper functions for better readability and maintainability.
        """

        selected_nights = self.widget_nights.textfield_selection.get_text()
        if selected_nights == "":
            messagebox.showerror("No nights selected", "Please select nights.")
            return
        # Get parameters from GUI
        params = self._get_gui_parameters()
        
        # Set up paths and load cross-correlation model
        paths_dict = self._setup_paths_and_model(params)
        
        # Create parameter grids for likelihood computation
        rvvec, kpvec, sfvec = self._create_parameter_grids(params)
        
        # Allocate likelihood array: dimensions (len(kpvec), len(rvvec), len(sfvec))
        lhood = np.zeros((len(kpvec), len(rvvec), len(sfvec)))

        dates = params['dates']
        # instruments = params['instruments']
        path_instruments = params['path_instruments']
        rad_mode = params['rad_mode']
        tell_rm_method = params['tell_rm_method']
        order_sel = params['order_sel']
        # Loop over nights
        for night_idx in range(len(dates)):

            path_order = Path(path_instruments[night_idx], dates[night_idx], "orders_selection", order_sel)
            path_order_tell = path_order / tell_rm_method
            path_order_test = str(Path(path_order_tell, "test"))
            path_good_order = path_order / ("good_order_" + order_sel + ".pkl")
            night = Night(
                self.target_information.limit_phase_t14, path_instruments[night_idx],
                dates[night_idx], rad_mode, path_good_order, path_order_tell, path_order_test,
                table_output_file=None, CC=False, simulation=False, synthetic=False, retrieval_standardize="From pkl"
            )
            
            # Get dimensions
            _, nimg, npix = np.shape(night.aligned)
            norder = len(night.good_order)
            
            # Loop over orders
            for h in range(norder):
                # Compute likelihood for this order using both methods and compare
                order_lhood = self._compute_likelihood_for_order(
                    h, night, params, rvvec, kpvec, sfvec, paths_dict['csscaled']
                )

                # Add to total likelihood
                lhood += order_lhood
                
                # Print progress
                print(f"Night: {night.date}, Order index: {h}")
        
        # Post-processing: Compute detection significance
        sigma_map, max_idx = self._compute_detection_significance(lhood)
        
        # Create diagnostic plots
        self._create_diagnostic_plots(
            sigma_map, max_idx, rvvec, kpvec, sfvec, paths_dict
        )
        
        # Show results in standalone window
        MyFigure(title="Likelihood Results", type_data="Image", image_path=paths_dict['filepng1'])
        
        # Save results to pickle file
        with open(paths_dict['path_pickle'], "wb") as f:
            dump(kpvec, f)
            dump(rvvec, f)
            dump(sfvec, f)
            dump(lhood, f)
        
        print("Computation completed successfully")

    def show_lh(self):
        """
        Display the most recently computed likelihood results.
        
        This function reconstructs the file path for the most recent
        likelihood computation based on current GUI settings and
        displays the corresponding plot in the GUI.
        """
        selected_nights = self.widget_nights.textfield_selection.get_text()
        if selected_nights == "":
            messagebox.showerror("No nights selected", "Please select nights.")
            return
        # Get parameters from GUI (same as lh_func)
        params = self._get_gui_parameters()
        
        # Set up paths using the same method as lh_func
        paths_dict = self._setup_paths_and_model(params)
        
        # Display the plot in a standalone window
        MyFigure(title="Likelihood Results", type_data="Image", image_path=paths_dict['filepng1'])
