"""
High-Resolution Spectroscopy generation GUI Panel

This module provides a comprehensive graphical user interface for configuring and
executing synthetic high-resolution spectroscopic observations of exoplanets.

The GUI panel allows users to:
- Configure target parameters and observational setup
- Set atmospheric conditions and instrumental parameters  
- Control PWV (precipitable water vapor) modeling
- Select radiation transfer modes and cross-correlation models
- Execute dataset generations with real-time progress monitoring
- View diagnostic plots and results

Classes:
    UpdatableImage: Dynamic image widget for displaying generation results
    FrameGenerationHRData: Main GUI panel for HR generation configuration

Functions:
    ask_stellar_params: Convenience wrapper for stellar parameter dialog
"""

import pickle
import subprocess
from pathlib import Path
from tkinter import messagebox
import tkinter as tk

from GUIBRUSHR.GUI.DataInterface.DataInterface import get_target_nights, get_target_instruments, get_target_list, \
    get_list_cross_corr_models, get_target_info
from GUIBRUSHR.GUI.DataInterface.DBSQLite3 import DBSQLite3
from GUIBRUSHR.GUI.Input_Output_Panels.Input_Panels.TabPanels.FrameGenerationHRData.shuffle_HR import shuffle_HR
from GUIBRUSHR.GUI.Input_Output_Panels.Input_Panels.TabPanels.FrameGenerationHRData.MathematicalFunctions import MathematicalFunctions
from GUIBRUSHR.GUI.LAYOUT.MyPanel import MyPanel
from GUIBRUSHR.GUI.LAYOUT.MyTabPanel import MyTabPanel
from GUIBRUSHR.GUI.LAYOUT.ScaleManager import ScaleManager
from GUIBRUSHR.GUI.WIDGET.MyButton import MyButton
from GUIBRUSHR.GUI.WIDGET.MyCheckBox import MyCheckBox
from GUIBRUSHR.GUI.WIDGET.MyDropDown import MyDropdown
from GUIBRUSHR.GUI.WIDGET.MyEntry import MyEntry
from GUIBRUSHR.GUI.WIDGET.MyImage import MyImage
from GUIBRUSHR.GUI.WIDGET.MyLabel import MyLabel
from GUIBRUSHR.GUI.WIDGET.MyTextField import MyTextField
from GUIBRUSHR.GUI.WIDGET.MyPDFWindow import MyPDFWindow
from GUIBRUSHR.General_Constants.Classes.Target import Target
from GUIBRUSHR.General_Constants.Classes.HelpButton import HelpButton
from GUIBRUSHR.General_Constants.FunctionsAndConstants.Constant_Variables import ConstantVariables
from GUIBRUSHR.General_Constants.FunctionsAndConstants.general_functions import create_path_night, \
    download_star_spectrum


# ----------------------------------------------------------------------
# Custom updatable image widget
# ----------------------------------------------------------------------
class UpdatableImage(MyImage):
    """
    A specialized image widget that supports dynamic updating of displayed images.
    
    This class extends MyImage to provide the ability to change the displayed
    image at runtime, which is useful for showing updated plots, diagnostic
    images, or simulation results without recreating the widget.

    Inherits from MyImage and maintains all its functionality while adding
    dynamic update capabilities through PIL/Pillow image processing.
    """

    def __init__(self, parent: tk.Widget, row: int, column: int, width: int, height: int,
                 initial_path: str, **kwargs) -> None:
        """
        Initialize the updatable image widget.

        Parameters
        ----------
        parent : tk.Widget
            Parent widget container
        row : int
            Row position in the parent's grid layout
        column : int
            Column position in the parent's grid layout
        width : int
            Desired image width in pixels
        height : int
            Desired image height in pixels
        initial_path : str
            Path to the initial image file to display
        **kwargs
            Additional keyword arguments passed to MyImage parent class

        Notes
        -----
        The widget automatically resizes images to fit the specified dimensions
        while maintaining aspect ratio handling as configured in the parent class.
        """
        super().__init__(parent, row, column, width, height, initial_path, **kwargs)

    def update_image(self, new_path: str) -> None:
        """
        Update the displayed image with a new image file.

        Dynamically loads and displays a new image while preserving the widget's
        position and configuration. The new image is automatically resized to
        match the widget's dimensions.

        Parameters
        ----------
        new_path : str
            Path to the new image file to display. Supports common formats
            including PNG, JPEG, GIF, BMP, and TIFF.

        Raises
        ------
        FileNotFoundError
            If the specified image file cannot be found
        PIL.UnidentifiedImageError
            If the file is not a valid image format
        Exception
            For other image loading or display errors

        Notes
        -----
        The method handles both canvas-based and label-based image display
        mechanisms automatically. A reference to the new image is maintained
        to prevent garbage collection.
        """
        try:
            from PIL import Image, ImageTk

            # Create new PhotoImage
            new_image = ImageTk.PhotoImage(
                Image.open(new_path).resize((int(self.width), int(self.height)))
            )

            # Update the image
            if hasattr(self, 'canvas'):
                # Using canvas
                self.canvas.delete("all")
                self.canvas.create_image(0, 0, anchor=tk.NW, image=new_image)
                self.image = new_image  # Keep reference
            else:
                # Using label - need to find the label widget
                for child in self.winfo_children():
                    if isinstance(child, tk.Label):
                        child.configure(image=new_image)
                        child.image = new_image  # Keep reference
                        break
                self.image = new_image

        except Exception as e:
            print(f"Error updating image: {e}")


class FrameGenerationHRData(MyPanel):
    """
    Comprehensive GUI panel for configuring and executing high-resolution spectroscopy generations.

    This panel provides a complete user interface for setting up synthetic exoplanet
    observations with full control over:

    **Core Configuration:**
    - Target selection and orbital parameters
    - Instrument selection and spectral orders
    - Radiation transfer modes (transmission/emission)
    - Cross-correlation model selection

    **Atmospheric Modeling:**
    - PWV (precipitable water vapor) functions and time series
    - Observatory parameters and atmospheric conditions
    - Telluric absorption modeling with SkyCalc integration
    - Airmass calculations and observational coordinates

    **Advanced Options:**
    - Wavelength coverage and resolution settings
    - Stellar parameter configuration via dialogs
    - Discontinuous vs continuous PWV modeling
    - Real-time simulation monitoring and progress tracking

    **Output Management:**
    - FITS file generation with proper headers
    - Diagnostic plot creation and PDF viewing
    - Result visualization and quality assessment
    - Background process management for long generations

    The panel integrates with the broader GUIBRUSHR framework and maintains
    compatibility with standard astronomical data reduction pipelines.

    Attributes
    ----------
    synthetic_process : subprocess.Popen or None
        Background process handle for running generations
    synthetic_running : bool
        Flag indicating if a generation is currently executing
    math_functions : MathematicalFunctions
        Utility class for PWV and atmospheric function calculations
    """

    def __init__(
        self,
        parent: tk.Widget,
        color: str,
        row: int,
        column: int,
        path_default: str,
        frame_input,
        window: tk.Toplevel,
        width_GUI: int,
        height_frame: int,
        **kwargs
    ) -> None:
        """
        Initialize the FrameGenerationHRData panel with all UI components and configurations.

        Parameters
        ----------
        parent : tk.Widget
            Parent widget container for this panel
        color : str
            Background color for the panel (hex color code or named color)
        row : int
            Row position in the parent widget's grid layout
        column : int
            Column position in the parent widget's grid layout
        path_default : str
            Root path to GUIBRUSHR directory structure containing models,
            telluric grids, and configuration files
        frame_input : FrameInput
            Input frame object containing target folder path and related
            configuration information
        window : tk.Toplevel
            Main application window for parent references and modal dialogs
        width_GUI : int
            Total width of the GUI in pixels for layout calculations
        height_frame : int
            Height allocated for this frame in pixels
        **kwargs : dict
            Additional keyword arguments passed to MyPanel parent class

        Notes
        -----
        The initialization process:
        1. Sets up all UI component references and instance variables
        2. Loads initial target list from the specified folder
        3. Initializes dropdown menus with available options
        4. Creates atmospheric parameter panels and controls
        5. Sets up background process monitoring
        6. Configures mathematical function utilities

        The panel automatically adapts to available targets and instruments
        found in the specified data directory structure.
        """
        super().__init__(parent, color, row, column, **kwargs)

        # === Core UI component references ===
        self.target_information = None
        self.dropdown_observatory_coords = None
        self.terminator_night = None           # Night termination handler
        self.dropdown_target = None            # Target selection dropdown
        self.dropdown_hr_instrument = None     # HR instrument dropdown
        self.list_hr_instrument = None         # Available HR instruments list
        self.dropdown_cross_corr = None        # Cross-correlation model dropdown

        # === Framework and window references ===
        self.parent = parent
        self.color = color
        self.path_folder_targets = frame_input.frame_path.path_folder_targets.get_text()
        self.window = window
        self.path_default = path_default
        self.frame_input = frame_input
        self.width_GUI = width_GUI
        self.height_frame = height_frame

        # === File and path management variables ===
        self.folder_retrieval = None    # Folder retrieval settings
        self.prefix_file = None         # File prefix for outputs
        self.underscore = None          # Underscore formatting option
        self.p = None                   # Processing parameter
        self.filepng = None            # PNG file path for plots
        self.lista_parametri = None    # Parameter list storage
        self.path_imm = None           # Image path reference
        self.last_plot_reduced = None  # Last reduced plot reference

        # === Background process management ===
        self.synthetic_process = None    # Subprocess handle for generations
        self.synthetic_running = False   # generation execution flag

        # === Mathematical function utilities ===
        self.math_functions = MathematicalFunctions()

        # === Initialize UI components ===
        # Load available targets from data directory
        target_list = get_target_list(self.path_folder_targets)

        # Set up all UI panels and controls
        self._initialize_ui_components(target_list)
        self._initialize_atmospheric_parameters()

        # Global exception handling flag
        self.global_exc = 1

    def _initialize_ui_components(self, target_list: list) -> None:
        """
        Initialize all primary UI components with their default values and layout.

        Creates the main control panels and dropdown menus for target selection,
        instrument configuration, and basic generation parameters. This method
        establishes the foundation UI elements that other initialization methods
        build upon.

        Parameters
        ----------
        target_list : list
            List of available target names loaded from the data directory

        Notes
        -----
        The initialization order is important as later dropdowns depend on
        selections made in earlier ones. The method creates:
        1. Main controls panel with light blue background
        2. Target selection dropdown with auto-population
        3. Instrument selection based on first target
        4. Nights text field with available observation nights
        5. Radiation transfer mode selection
        """
        # Create main panel container with light blue background for visual separation
        self.main_controls_panel = MyPanel(self, "#F0F8FF", 0, 1, columnspan=12, rowspan=3)

        # Help button for main controls
        help_text_main = {
            "Target": "Select the exoplanet target for which to generate simulated high-resolution spectra. The target must be previously configured in the database with all orbital and physical parameters.",

            "Nights": "Observation dates to simulate, in YYYY-MM-DD format. Multiple nights can be comma-separated (e.g., '2023-05-15,2023-05-16'). Each night generates a separate simulated dataset with time-dependent atmospheric conditions.",

            "Rad. Transf. Mode": "Radiative transfer mode: 'Transmission' simulates primary transit observations (planetary atmosphere backlit by star, showing absorption features). 'Emission' simulates secondary eclipse observations (planetary thermal emission and reflected starlight).",

            "HR Instrument": "High-resolution spectrograph to simulate. The instrument's resolving power, wavelength coverage, and line spread function will be applied to the synthetic spectra to match real observational characteristics.",

            "Model CC": "Atmospheric model for the planetary spectrum to inject. Select from pre-computed petitRADTRANS models in the target's Models directory. Different models represent various atmospheric compositions, temperature profiles, or cloud properties.",

            "Order Selection": "Mainly used for internal file structure: Specify which spectral orders to search.",

            "Tell rm method": "Mainly used for internal file structure: Telluric removal algorithm."
        }

        HelpButton(
            self.main_controls_panel, 0, 0,
            "Main Controls Help",
            help_text_main,
            columnspan=1
        )

        # Target selection dropdown
        self.dropdown_target = MyDropdown(self.main_controls_panel, 0, 1, target_list, "Target:", color=self.main_controls_panel.cget("bg"), columnspan=2)

        # Get initial instrument list and nights
        self.list_hr_instrument = get_target_instruments(
            self.path_folder_targets, target_list[0], "HR", "Transmission", Path(__file__).name
        )
        nights = get_target_nights(
            self.path_folder_targets,
            target_list[0],
            "Transmission",
            self.list_hr_instrument[0],
            0
        )

        # Nights text field
        self.textfield_nights = MyTextField(self.main_controls_panel, 0, 3, nights, "Nights:", color=self.main_controls_panel.cget("bg"), columnspan=4, width=80)

        # Radiation transfer mode dropdown
        self.dropdown_rad_mode = MyDropdown(
            self.main_controls_panel, 0, 7, ConstantVariables.LIST_RAD_MODE, "Rad. Transf. Mode:",
            initial_value=0, color=self.main_controls_panel.cget("bg"), columnspan=2
        )

        # HR instrument dropdown
        self.dropdown_hr_instrument = MyDropdown(
            self.main_controls_panel, 0, 9, self.list_hr_instrument, "HR Instrument:",
            color=self.main_controls_panel.cget("bg"), columnspan=2
        )

        # Cross-correlation model dropdown
        dict_list_cc = get_list_cross_corr_models(self.path_folder_targets, target_list[0], "Transmission")
        self.dropdown_cross_corr = MyDropdown(
            self.main_controls_panel, 0, 11, list(sorted(dict_list_cc.keys())),
            "Model CC", initial_value=0,
            color=self.main_controls_panel.cget("bg"), columnspan=2
        )

        # Order selection text field
        self.textfield_order_selection = MyTextField(
            self.main_controls_panel, 1, 1, "full", "Order Selection \n(only for shuffle):",
            color=self.main_controls_panel.cget("bg"), columnspan=3, width=20
        )

        # Telluric removal method dropdown
        self.dropdown_tell_rm_method = MyDropdown(
            self.main_controls_panel, 1, 4, ConstantVariables.LIST_TELL_TABLE,
            "Tell rm method \n(only for shuffle):", color=self.main_controls_panel.cget("bg"), columnspan=2
        )

        # Set up callbacks
        self.dropdown_hr_instrument.set_callback(self.update_nights)
        self.dropdown_rad_mode.set_callback(self.update_from_target_or_rad_mode)
        self.dropdown_target.set_callback(self.update_from_target_or_rad_mode)

    def _initialize_atmospheric_parameters(self):
        """
        Initialize atmospheric parameter panels and widgets using tab structure.

        Creates a tabbed interface for organizing atmospheric and observational
        parameters. Sets up PWV parameters panel and creates additional panels
        for atmospheric, observational, wavelength, observatory, and coordinates
        parameters within the tab structure.
        """
        # Create tab manager for organizing atmospheric and observational parameters
        self.param_tab_manager = MyTabPanel(
            parent=self,
            tab_list=ConstantVariables.list_tab_simulated_hr,
            row=4,
            column=4, width=int(self.width_GUI / 2), rowspan=15,
        )

        # First tab - Basic Configuration with PWV parameters and generation controls
        # Create a panel inside the first tab for PWV parameters
        self.pwv_panel = MyPanel(self.param_tab_manager.tab_list[0], "#E8F4FD", 0, 1, columnspan=10, rowspan=3)

        # Help button for PWV parameters
        help_text_pwv = {
            "dt": "Time step in seconds for PWV evolution. Smaller values create more detailed temporal variations. Recommended: 1 second for high-fidelity simulations.",

            "pace": "PWV correlation timescale parameter in seconds.",

            "s": "Shape parameter for the PWV distribution function. Controls the steepness of PWV variations. Typical values: 0.5-1.0. Default: 0.69598.",

            "scale": "cale parameter for the random component, by default 0.69598. Controls the amplitude of PWV variations.",

            "tExp": "Exposure time in seconds for each spectral frame. Used to average PWV over the exposure duration, simulating realistic integration effects. Typical: 300-3600 seconds.",

            "Seed": "Random number generator seed for reproducible PWV time series. Using the same seed with identical parameters produces identical PWV variations. Set to any integer for reproducibility."
        }

        HelpButton(
            self.pwv_panel, 0, 0,
            "PWV Parameters Help",
            help_text_pwv,
            columnspan=1
        )

        # Header for PWV parameters
        MyLabel(
            self.pwv_panel, 0, 1,
            color=self.pwv_panel.cget("bg"),
            label_text="PWV PARAMETERS", font=ScaleManager.get().font_label_large if ScaleManager.get() else ("Sans", 12, "bold"), columnspan=10
        )

        # PWV parameter entry widgets arranged in a single row
        self.entry_dt = MyEntry(
            self.pwv_panel, 1, 1, "1", label_text="dt:",
            color=self.pwv_panel.cget("bg"), columnspan=2
        )

        self.entry_pace = MyEntry(
            self.pwv_panel, 1, 3, "6e5", label_text="pace:",
            color=self.pwv_panel.cget("bg"), columnspan=2
        )

        self.entry_s = MyEntry(
            self.pwv_panel, 1, 5, "0.69598", label_text="s:",
            color=self.pwv_panel.cget("bg"), columnspan=2
        )

        self.entry_scale = MyEntry(
            self.pwv_panel, 1, 7, "3.0862", label_text="scale:",
            color=self.pwv_panel.cget("bg"), columnspan=2
        )

        self.entry_tExp = MyEntry(
            self.pwv_panel, 1, 9, "1800", label_text="tExp:",
            color=self.pwv_panel.cget("bg"), columnspan=2
        )

        # Add seed entry parameter for reproducible random number generation
        self.entry_seed = MyEntry(
            self.pwv_panel, 1, 11, "42", label_text="Seed:",
            color=self.pwv_panel.cget("bg"), columnspan=2
        )

        # Create generation parameters panel under PWV panel in first tab
        self.generation_params_panel = MyPanel(self.param_tab_manager.tab_list[0], "#F0F8FF", 3, 1, columnspan=10, rowspan=2)

        # Help button for generation parameters
        help_text_sim = {
            "Gain": "Detector gain factor (electrons/ADU). Multiplies the photon counts to convert to detector units. Typical CCD gains: 1-5.",

            "Error Type": "'Error from reduction': Uses real error estimates from actual data reduction. 'Synthetic error': Generates theoretical noise based on photon statistics. Use synthetic for idealized generations.",

            "Noise Factor": "Multiplicative factor for error bars. Increases (>1) or decreases (<1) the noise level relative to theoretical/measured values. Use to simulate better/worse observing conditions. Default: 1 (realistic noise).",

            "Save Plots": "When enabled, generates and saves diagnostic plots during generation including. Useful for quality assessment but increases processing time.",

            "Include 0.1 PWV": "When enabled, adds an additional simulated night with very low PWV (0.1 mm), representing exceptionally dry conditions. This value should not be used due to possible errors from the original skycalc simulator."
        }

        HelpButton(
            self.generation_params_panel, 0, 0,
            "Generation Parameters Help",
            help_text_sim,
            columnspan=1
        )

        # Header for generation parameters
        MyLabel(
            self.generation_params_panel, 0, 1,
            color=self.generation_params_panel.cget("bg"),
            label_text="GENERATIONS PARAMETERS", font=ScaleManager.get().font_label_large if ScaleManager.get() else ("Sans", 12, "bold"), columnspan=10
        )

        # Gain entry - Controls the gain factor for the generation
        self.entry_gain = MyEntry(
            self.generation_params_panel, 1, 1, "1.0", "Gain:",
            color=self.generation_params_panel.cget("bg"), columnspan=2
        )

        # Error type dropdown - Controls the type of error generation
        self.dropdown_error_type = MyDropdown(
            self.generation_params_panel, 1, 3, ["Error from reduction", "Synthetic error"],
            "Error Type:", color=self.generation_params_panel.cget("bg"), columnspan=2
        )

        # Noise factor entry - Controls the amplitude of synthetic noise
        self.entry_noise_factor = MyEntry(
            self.generation_params_panel, 1, 5, "1", "Noise Factor:",
            color=self.generation_params_panel.cget("bg"), columnspan=2
        )

        # Checkboxes
        self.checkbox_save_plots = MyCheckBox(
            self.generation_params_panel, 1, 7, "Save Plots:"
        )

        self.checkbox_include_0_1 = MyCheckBox(
            self.generation_params_panel, 1, 9, "Include 0.1 PWV:"
        )

        # Second tab contains the advanced parameters with a nested tab manager
        # Create a nested tab manager inside the "Advanced Parameters" tab
        self.advanced_tab_manager = MyTabPanel(
            parent=self.param_tab_manager.tab_list[1],
            tab_list=ConstantVariables.list_tab_simulated_hr_advanced,
            row=0,
            column=0,
            width=int(self.width_GUI / 2),
            rowspan=15,
        )

        # Create panels within their respective tabs in the advanced tab manager
        self.atm_panel = MyPanel(self.advanced_tab_manager.tab_list[0], "#E8F4FD", 0, 0, columnspan=15, rowspan=15)
        self.obs_panel = MyPanel(self.advanced_tab_manager.tab_list[1], "#FDF4E8", 0, 0, columnspan=15, rowspan=15)
        self.wl_panel = MyPanel(self.advanced_tab_manager.tab_list[2], "#E8FDF4", 0, 0, columnspan=15, rowspan=15)
        self.observatory_panel = MyPanel(self.advanced_tab_manager.tab_list[3], "#F4E8FD", 0, 0, columnspan=15, rowspan=15)

        # Keep coordinates panel in main frame for now
        self.coordinates_panel = MyPanel(self, "#FDE8F4", 4, 5, columnspan=8, rowspan=2)

        # Initialize each panel
        self._initialize_atmospheric_panel()
        self._initialize_observational_panel()
        self._initialize_wavelength_panel()
        self._initialize_observatory_panel()
        self._initialize_coordinates_panel()

        # Add generation controls (dropdown and run button) in main frame
        self._initialize_generation_controls()

    def _initialize_generation_controls(self):
        """
        Initialize generation mode dropdown and run button in main frame.

        Creates the generation mode selection and run button that need to stay
        in the main frame for easy access.
        """
        # Create a new panel for generation controls in main frame
        self.main_generation_controls_panel = MyPanel(self, "#F0F8FF", 6, 5, columnspan=8, rowspan=1)

        # Help button for generation controls
        help_text_sim_ctrl = {
            "Generation Mode": "Select the generation type:\n\n'Synthetic': Generates fully synthetic data with continuous PWV evolution using the specified PWV function. Creates realistic time-varying telluric absorption.\n\n'Synthetic Discontinuous PWV': Similar to Synthetic but with discontinuous PWV jumps between exposures instead of smooth evolution. Simpler but less realistic.\n\n'Shuffle': Injects planetary signal into existing reduced observational data where each phases is shuffled. Requires real data to already exist for the selected target/night/instrument combination."
        }

        HelpButton(
            self.main_generation_controls_panel, 0, 0,
            "Generation Mode Help",
            help_text_sim_ctrl,
            columnspan=1
        )

        # Generation mode dropdown
        self.dropdown_generation_mode = MyDropdown(
            self.main_generation_controls_panel, 0, 1,
            ["Synthetic", "Synthetic Discontinuous PWV", "Shuffle"],
            "Generation Mode:",
            color=self.main_generation_controls_panel.cget("bg"), columnspan=3
        )

        # Run button
        self.button_run = MyButton(
            self.main_generation_controls_panel, 0, 4, 'RUN', '#4CAF50',
            self.run_selected_generation, color_panel=self.main_generation_controls_panel.cget("bg"), columnspan=1, sticky="we"
        )


    def _initialize_atmospheric_panel(self):
        """
        Initialize atmospheric parameters panel.

        Creates the atmospheric parameters section with PWV functions,
        airmass settings, atmospheric conditions, and PWV image display.
        Includes validation information and parameter organization.
        """
        # Help button for atmospheric parameters
        help_text_atm = {
            "Airmass": "Air mass of the observation. -999: retrieved from almanac (computed automatically based on target position and time). 0: calculated by SkyCalc algorithm. Positive values: constant airmass throughout the night. Typical values: 1.0 (zenith) to 2.5 (low altitude).",

            "PWV Function": "Mathematical function describing precipitable water vapor (PWV) time evolution. Different functions simulate various atmospheric conditions and temporal variability patterns. PWV affects telluric absorption strength, particularly water vapor lines.",

            "Monthly Solar Flux": "Monthly average solar radio flux at 10.7 cm (F10.7 index) in solar flux units (sfu). -999: retrieved from almanac for the observation date. Values typically range 50-300 sfu. Affects upper atmospheric ionization and airglow emission.",

            "PWV Mode": "'pwv': Use the selected PWV function for temporal evolution. 'season': Use seasonal average PWV value appropriate for the observatory and observation date. PWV mode determines how atmospheric water vapor is modeled.",

            "Season": "Season code for seasonal PWV modeling (0-3). Only relevant if 'PWV Mode' is set to 'season'. Values: 0=Winter, 1=Spring, 2=Summer, 3=Fall. Different seasons have characteristic PWV ranges.",

            "Time": "Time offset in hours from midnight for PWV function evaluation. Used to set the starting point of the PWV time series. Typically 0 to start at midnight UTC.",

            "Initial X": "Initial phase value for PWV function (in degrees or radians depending on function). Defines the starting point of the PWV evolution curve. Used to offset the PWV time series.",

            "Final X": "Final phase value for PWV function (in degrees or radians). Defines the ending point of the PWV evolution. Together with Initial X, sets the range of PWV variation to explore."
        }

        HelpButton(
            self.atm_panel, 0, 0,
            "Atmospheric Parameters Help",
            help_text_atm,
            columnspan=1
        )

        # Section header
        MyLabel(
            self.atm_panel, 0, 1, color=self.atm_panel.cget("bg"),
            label_text="ATMOSPHERIC PARAMETERS", font=ScaleManager.get().font_label_large if ScaleManager.get() else ("Sans", 12, "bold"), columnspan=10
        )

        # Airmass info label
        MyLabel(
            self.atm_panel, 1, 1, color=self.atm_panel.cget("bg"),
            label_text="Airmass = -999 means retrieved by almanac, 0 means calculated by algorithm, other values are constant along the night",
            font=ScaleManager.get().font_label_italic if ScaleManager.get() else ("Sans", 9, "italic"), columnspan=10
        )

        # Parameters arranged in a 2-column layout for better readability
        # First row of parameters
        self.entry_airmass = MyEntry(
            self.atm_panel, 2, 1, "-999", label_text="Airmass:",
            color=self.atm_panel.cget("bg"), columnspan=3
        )

        self.dropdown_pwv = MyDropdown(
            self.atm_panel, 2, 4, self.math_functions.list_functions(), "PWV Function:",
            color=self.atm_panel.cget("bg"), columnspan=3
        )
        self.dropdown_pwv.set_callback(self.update_pwv_image)

        # Second row of parameters
        self.entry_msolflux = MyEntry(
            self.atm_panel, 3, 1, "-999", label_text="Monthly Solar Flux:",
            color=self.atm_panel.cget("bg"), columnspan=3
        )

        self.dropdown_pwv_mode = MyDropdown(
            self.atm_panel, 3, 4, ["pwv", "season"], "PWV Mode:",
            color=self.atm_panel.cget("bg"), columnspan=3
        )

        # Third row of parameters
        self.entry_season = MyEntry(
            self.atm_panel, 4, 1, "0", label_text="Season:",
            color=self.atm_panel.cget("bg"), columnspan=3
        )

        self.entry_time = MyEntry(
            self.atm_panel, 4, 4, "0", label_text="Time:",
            color=self.atm_panel.cget("bg"), columnspan=3
        )

        # Fourth row - new X-axis range parameters
        self.entry_initial_x = MyEntry(
            self.atm_panel, 5, 1, "-90", label_text="Initial X:",
            color=self.atm_panel.cget("bg"), columnspan=3
        )

        self.entry_final_x = MyEntry(
            self.atm_panel, 5, 4, "-75", label_text="Final X:",
            color=self.atm_panel.cget("bg"), columnspan=3
        )

        # PWV Image display positioned below parameters for better layout
        default_image_path = Path(
            self.path_default, "GUIBRUSHR", "Files", "pwv_functions_plots",
            self.math_functions.list_functions()[0] + ".png"
        )
        self.pwv_image = UpdatableImage(
            self.atm_panel, 6, 1, 500, 350, str(default_image_path),
            columnspan=6, rowspan=4, label=False
        )

    def _initialize_observational_panel(self):
        """
        Initialize observational parameters panel.

        Sets up moon parameters, light source controls, coordinate settings,
        atmospheric emission options, and thermal emission parameters.
        Provides information about parameter retrieval methods.
        """
        # Help button for observational parameters
        help_text_obs = {
            "Include Moon": "Y: Include scattered moonlight in sky background. N: Exclude moon contribution. Scattered moonlight significantly increases sky background, especially in optical wavelengths.",

            "Moon-Sun Sep [deg]": "Angular separation between Moon and Sun in degrees. -999: computed from almanac. Range 0-180°. Determines lunar phase (0°=New Moon, 180°=Full Moon). Full moon has maximum scattered light.",

            "Moon-Target Sep [deg]": "Angular separation between Moon and target in degrees. -999: computed from almanac. Closer moon increases scattered light contamination in target spectra.",

            "Moon Altitude [deg]": "Moon altitude above horizon in degrees. -999: computed from almanac. Higher moon increases sky brightness. Negative values mean moon is below horizon.",

            "Moon-Earth Distance": "Moon-Earth distance in Earth radii or AU. -999: computed from almanac. Affects apparent lunar brightness (closer = brighter).",

            "Include Starlight": "Y: Include scattered starlight background. N: Exclude. Integrated starlight contributes to diffuse sky background, particularly important in dark sky conditions.",

            "Include Zodiacal": "Y: Include zodiacal light (sunlight scattered by interplanetary dust). N: Exclude. Most prominent at ecliptic, contributes to sky background particularly in infrared.",

            "Ecliptic Longitude [deg]": "Ecliptic longitude of target in degrees. -999: computed from target coordinates. Used for zodiacal light calculation. Range 0-360°.",

            "Ecliptic Latitude [deg]": "Ecliptic latitude of target in degrees. -999: computed from target coordinates. Zodiacal light peaks at ecliptic plane (latitude = 0°).",

            "Lower Atmosphere": "Y: Include lower atmosphere emission (troposphere). N: Exclude. Lower atmospheric emission dominates in mid-infrared, particularly from H2O and CO2.",

            "Upper Atmosphere": "Y: Include upper atmosphere/stratosphere emission. N: Exclude. Contributes thermal emission and scattered sunlight at high altitudes.",

            "Airglow Continuum": "Y: Include airglow continuum emission. N: Exclude. Airglow from atmospheric chemiluminescence adds diffuse background, particularly in optical/NIR.",

            "Thermal Emission": "Y: Include telescope/instrument thermal emission. N: Exclude (default). Thermal emission from warm surfaces dominates background in mid-infrared.",

            "Therm T1/T2/T3 [K]": "Temperatures in Kelvin for thermal emission components. Multiple blackbody components model telescope optics, enclosure, and ambient surfaces at different temperatures.",

            "Therm E1/E2/E3": "Emissivities (0-1) for each thermal component. Product of emissivity and solid angle subtended. Higher values = stronger thermal contribution."
        }

        HelpButton(
            self.obs_panel, 0, 0,
            "Observational Parameters Help",
            help_text_obs,
            columnspan=1
        )

        # Section header
        MyLabel(
            self.obs_panel, 0, 1, color=self.obs_panel.cget("bg"),
            label_text="OBSERVATIONAL PARAMETERS", font=ScaleManager.get().font_label_large if ScaleManager.get() else ("Sans", 12, "bold"), columnspan=12
        )

        # Info label about -999 values
        MyLabel(
            self.obs_panel, 1, 1, color=self.obs_panel.cget("bg"),
            label_text="Parameters with value -999 are retrieved by the almanac. If you want to use a constant value, set it.",
            font=ScaleManager.get().font_label_italic if ScaleManager.get() else ("Sans", 9, "italic"), columnspan=12
        )

        # Moon parameters section (reorganized for better readability)
        self.dropdown_incl_moon = MyDropdown(
            self.obs_panel, 2, 1, ["Y", "N"], "Include Moon:",
            color=self.obs_panel.cget("bg"), columnspan=2
        )

        self.entry_moon_sun_sep = MyEntry(
            self.obs_panel, 2, 3, "-999", label_text="Moon-Sun Sep [deg]:",
            color=self.obs_panel.cget("bg"), columnspan=3
        )

        self.entry_moon_target_sep = MyEntry(
            self.obs_panel, 2, 6, "-999", label_text="Moon-Target Sep [deg]:",
            color=self.obs_panel.cget("bg"), columnspan=3
        )

        # Moon parameters continued
        self.entry_moon_alt = MyEntry(
            self.obs_panel, 3, 1, "-999", label_text="Moon Altitude [deg]:",
            color=self.obs_panel.cget("bg"), columnspan=4
        )

        self.entry_moon_earth_dist = MyEntry(
            self.obs_panel, 3, 5, "-999", label_text="Moon-Earth Distance:",
            color=self.obs_panel.cget("bg"), columnspan=4
        )

        # Light sources section
        self.dropdown_incl_starlight = MyDropdown(
            self.obs_panel, 4, 1, ["Y", "N"], "Include Starlight:",
            color=self.obs_panel.cget("bg"), columnspan=2
        )

        self.dropdown_incl_zodiacal = MyDropdown(
            self.obs_panel, 4, 3, ["Y", "N"], "Include Zodiacal:",
            color=self.obs_panel.cget("bg"), columnspan=2
        )

        # Ecliptic coordinates
        self.entry_ecl_lon = MyEntry(
            self.obs_panel, 5, 1, "-999", label_text="Ecliptic Longitude [deg]:",
            color=self.obs_panel.cget("bg"), columnspan=4
        )

        self.entry_ecl_lat = MyEntry(
            self.obs_panel, 5, 5, "-999", label_text="Ecliptic Latitude [deg]:",
            color=self.obs_panel.cget("bg"), columnspan=4
        )

        # Atmospheric emission section
        self.dropdown_incl_loweratm = MyDropdown(
            self.obs_panel, 6, 1, ["Y", "N"], "Lower Atmosphere:",
            color=self.obs_panel.cget("bg"), columnspan=3
        )

        self.dropdown_incl_upperatm = MyDropdown(
            self.obs_panel, 6, 4, ["Y", "N"], "Upper Atmosphere:",
            color=self.obs_panel.cget("bg"), columnspan=3
        )

        self.dropdown_incl_airglow = MyDropdown(
            self.obs_panel, 7, 1, ["Y", "N"], "Airglow Continuum:",
            color=self.obs_panel.cget("bg"), columnspan=3
        )

        # Thermal emission section
        self.dropdown_incl_therm = MyDropdown(
            self.obs_panel, 8, 1, ["Y", "N"], "Thermal Emission:", initial_value=1,
            color=self.obs_panel.cget("bg"), columnspan=3
        )

        # Thermal component 1
        self.entry_therm_t1 = MyEntry(
            self.obs_panel, 9, 1, "0.0", label_text="Therm T1 [K]:",
            color=self.obs_panel.cget("bg"), columnspan=3
        )

        self.entry_therm_e1 = MyEntry(
            self.obs_panel, 9, 4, "0.0", label_text="Therm E1:",
            color=self.obs_panel.cget("bg"), columnspan=3
        )

        # Thermal component 2
        self.entry_therm_t2 = MyEntry(
            self.obs_panel, 10, 1, "0.0", label_text="Therm T2 [K]:",
            color=self.obs_panel.cget("bg"), columnspan=3
        )

        self.entry_therm_e2 = MyEntry(
            self.obs_panel, 10, 4, "0.0", label_text="Therm E2:",
            color=self.obs_panel.cget("bg"), columnspan=3
        )

        # Thermal component 3
        self.entry_therm_t3 = MyEntry(
            self.obs_panel, 11, 1, "0.0", label_text="Therm T3 [K]:",
            color=self.obs_panel.cget("bg"), columnspan=3
        )

        self.entry_therm_e3 = MyEntry(
            self.obs_panel, 11, 4, "0.0", label_text="Therm E3:",
            color=self.obs_panel.cget("bg"), columnspan=3
        )

    def _initialize_wavelength_panel(self):
        """
        Initialize wavelength parameters panel.

        Configures wavelength coverage settings, resolution parameters,
        grid mode selection, and Line Spread Function (LSF) options.
        """
        # Help button for wavelength parameters
        help_text_wl = {
            "Vacuum/Air": "'vac': Wavelengths in vacuum. 'air': Wavelengths in air (corrected for refractive index). Most high-resolution spectrographs work in vacuum, low-resolution often in air wavelengths.",

            "Min Wavelength [nm]": "Minimum wavelength for telluric spectrum calculation in nanometers. Defines blue edge of spectral coverage. Example: 300 nm (UV) to 950 nm (optical/NIR boundary).",

            "Max Wavelength [nm]": "Maximum wavelength for telluric calculation in nanometers. Defines red edge of spectral coverage. Example: 2500 nm covers optical + NIR up to K-band.",

            "Wavelength Resolution Multiplier": "Multiplier for spectral resolution when using 'fixed_spectral_resolution' mode. Higher values = finer wavelength sampling. Typical: 2-10. Increases computation time linearly.",

            "Grid Mode": "'fixed_spectral_resolution': Constant R = λ/Δλ (like echelle spectrographs). 'fixed_wavelength_step': Constant Δλ spacing (like grating spectrographs). Choose based on instrument type.",

            "Wavelength Delta [nm]": "Wavelength step size in nanometers for 'fixed_wavelength_step' mode. Determines wavelength sampling. Smaller values = higher sampling but longer computation. Typical: 0.01-0.1 nm.",

            "LSF Type": "Line Spread Function type. 'none': No instrumental broadening (infinite resolution). 'Gaussian': Symmetric Gaussian profile (most spectrographs). 'Boxcar': Rectangular profile (for some imaging spectrographs).",

            "LSF Gauss FWHM": "Full Width at Half Maximum of Gaussian LSF in wavelength units. Defines instrumental resolution broadening. Must match instrument specifications. Only used if LSF Type = 'Gaussian'.",

            "LSF Boxcar FWHM": "Full width of boxcar (rectangular) LSF in wavelength units. Simulates pixel-limited resolution. Only used if LSF Type = 'Boxcar'."
        }

        HelpButton(
            self.wl_panel, 0, 0,
            "Wavelength Parameters Help",
            help_text_wl,
            columnspan=1
        )

        # Section header
        MyLabel(
            self.wl_panel, 0, 1, color=self.wl_panel.cget("bg"),
            label_text="WAVELENGTH PARAMETERS", font=ScaleManager.get().font_label_large if ScaleManager.get() else ("Sans", 12, "bold"), columnspan=10
        )

        # Grid mode info label
        MyLabel(
            self.wl_panel, 1, 1, color=self.wl_panel.cget("bg"),
            label_text="For 'fixed_spectral_resolution' use 'Wavelength Resolution Multiplier', otherwise use 'Wavelength Delta [nm]'",
            font=ScaleManager.get().font_label_italic if ScaleManager.get() else ("Sans", 9, "italic"), columnspan=10
        )

        # Wavelength coverage section
        self.dropdown_vacair = MyDropdown(
            self.wl_panel, 2, 1, ["vac", "air"], "Vacuum/Air:",
            color=self.wl_panel.cget("bg"), columnspan=2
        )

        self.entry_wmin = MyEntry(
            self.wl_panel, 2, 3, "300.0", label_text="Min Wavelength [nm]:",
            color=self.wl_panel.cget("bg"), columnspan=3
        )

        self.entry_wmax = MyEntry(
            self.wl_panel, 2, 6, "2000.0", label_text="Max Wavelength [nm]:",
            color=self.wl_panel.cget("bg"), columnspan=3
        )

        # Grid mode section
        self.dropdown_wgrid_mode = MyDropdown(
            self.wl_panel, 3, 1, ["fixed_spectral_resolution", "fixed_wavelength_step"],
            "Grid Mode:", color=self.wl_panel.cget("bg"), columnspan=4
        )

        # Resolution parameters
        self.entry_wres = MyEntry(
            self.wl_panel, 4, 1, "4", label_text="Wavelength Resolution Multiplier:",
            color=self.wl_panel.cget("bg"), columnspan=4
        )

        self.entry_wdelta = MyEntry(
            self.wl_panel, 4, 5, "0.1", label_text="Wavelength Delta [nm]:",
            color=self.wl_panel.cget("bg"), columnspan=4
        )

        # Line Spread Function section
        self.dropdown_lsf_type = MyDropdown(
            self.wl_panel, 5, 1, ["none", "Gaussian", "Boxcar"], "LSF Type:",
            color=self.wl_panel.cget("bg"), columnspan=3
        )

        self.entry_lsf_gauss_fwhm = MyEntry(
            self.wl_panel, 6, 1, "5.0", label_text="LSF Gauss FWHM:",
            color=self.wl_panel.cget("bg"), columnspan=4
        )

        self.entry_lsf_boxcar_fwhm = MyEntry(
            self.wl_panel, 6, 5, "5.0", label_text="LSF Boxcar FWHM:",
            color=self.wl_panel.cget("bg"), columnspan=4
        )

    def _initialize_observatory_panel(self):
        """
        Initialize observatory parameters panel.

        Sets up observatory selection and temperature flag configuration.
        """
        # Help button for observatory parameters
        help_text_observatory = {
            "Observatory": "Predefined observatory site for SkyCalc atmospheric modeling. Options: 'lasilla': La Silla Observatory (2400m, Chile), 'paranal': Paranal/VLT (2635m, Chile), '3060m': Generic 3060m altitude site. Different sites have different atmospheric profiles (PWV, pressure, temperature).",

            "Temperature Flag": "Temperature flag for atmospheric modeling. 1: Use site-specific temperature profile from almanac/climatology. 0: Use fixed temperature value. Temperature affects atmospheric density profile and refraction."
        }

        HelpButton(
            self.observatory_panel, 0, 0,
            "Observatory Parameters Help",
            help_text_observatory,
            columnspan=1
        )

        # Section header
        MyLabel(
            self.observatory_panel, 0, 1, color=self.observatory_panel.cget("bg"),
            label_text="OBSERVATORY PARAMETERS", font=ScaleManager.get().font_label_large if ScaleManager.get() else ("Sans", 12, "bold"), columnspan=10
        )

        # Observatory selection
        self.dropdown_observatory = MyDropdown(
            self.observatory_panel, 1, 1, ["lasilla", "paranal", "3060m"], "Observatory:",
            color=self.observatory_panel.cget("bg"), columnspan=4
        )

        # Temperature flag
        self.entry_temp_flag = MyEntry(
            self.observatory_panel, 2, 1, "1", label_text="Temperature Flag:",
            color=self.observatory_panel.cget("bg"), columnspan=4
        )

    def _initialize_coordinates_panel(self):
        """
        Initialize coordinates parameters panel with vertical layout.

        Creates entry fields for equatorial coordinates (RA, Dec) and
        observatory coordinates (latitude, longitude, altitude).
        """
        # Help button for coordinates parameters
        help_text_coords = {
            "RA [deg]": "Right Ascension of target in decimal degrees (J2000 epoch). Range 0-360°. Used for airmass calculation, barycentric corrections, and almanac computations. Automatically populated from target database but can be overridden.",

            "Dec [deg]": "Declination of target in decimal degrees (J2000 epoch). Range -90° to +90°. Together with RA defines target position on celestial sphere. Critical for accurate airmass and barycentric velocity calculations.",

            "Select Observatory": "Choose observatory from database. Pre-configured observatories have accurate geodetic coordinates. Selecting an observatory automatically populates latitude, longitude, and altitude fields. Required for barycentric corrections and almanac calculations.",

            "Observatory Lat [deg]": "Observatory geodetic latitude in decimal degrees. North positive (+), South negative (-). Range -90° to +90°. Critical for Earth rotation velocity corrections and accurate airmass calculations.",

            "Observatory Long [deg]": "Observatory geodetic longitude in decimal degrees. East positive (+), West negative (-). Range -180° to +180°. Used for calculating Local Sidereal Time and Earth rotation effects on radial velocities.",

            "Observatory Alt [m]": "Observatory altitude above sea level in meters. Affects atmospheric pressure/density profiles, extinction, and atmospheric refraction. Higher altitude = less atmosphere = less extinction and better transparency."
        }

        HelpButton(
            self.coordinates_panel, 0, 0,
            "Coordinates Parameters Help",
            help_text_coords,
            columnspan=1
        )

        # Section header
        MyLabel(
            self.coordinates_panel, 0, 1, color=self.coordinates_panel.cget("bg"),
            label_text="COORDINATES PARAMETERS", font=ScaleManager.get().font_label_large if ScaleManager.get() else ("Sans", 12, "bold"), columnspan=6
        )


        self.target_information = get_target_info(self.path_folder_targets, self.dropdown_target.get_value(), self.dropdown_rad_mode.get_value())
        # Arrange coordinate parameters vertically (one below the other)
        # Equatorial coordinates
        self.entry_ra = MyEntry(
            self.coordinates_panel, 1, 1, str(self.target_information.ra), label_text="RA [deg]:",
            color=self.coordinates_panel.cget("bg"), columnspan=3
        )

        self.entry_dec = MyEntry(
            self.coordinates_panel, 2, 1,  str(self.target_information.dec), label_text="Dec [deg]:",
            color=self.coordinates_panel.cget("bg"), columnspan=3
        )

        # Observatory selection dropdown
        # Get observatories from database
        DB = DBSQLite3(self.path_default)
        observatories, lat, long, alt = DB.get_observatories()
        DB.close_DB()

        self.dropdown_observatory_coords = MyDropdown(
            self.coordinates_panel, 3, 1, observatories, "Select Observatory:",
            color=self.coordinates_panel.cget("bg"), columnspan=3
        )
        self.dropdown_observatory_coords.set_callback(self.update_observatory_coordinates)

        lat = str(lat[0]) if lat is not None else "0"
        long = str(long[0]) if long is not None else "0"
        alt = str(alt[0]) if alt is not None else "0"
        # Observatory coordinates
        self.entry_obs_lat = MyEntry(
            self.coordinates_panel, 4, 1, lat, label_text="Observatory Lat [deg]:",
            color=self.coordinates_panel.cget("bg"), columnspan=3
        )

        self.entry_obs_long = MyEntry(
            self.coordinates_panel, 5, 1, long, label_text="Observatory Long [deg]:",
            color=self.coordinates_panel.cget("bg"), columnspan=3
        )

        self.entry_obs_alt = MyEntry(
            self.coordinates_panel, 6, 1, alt, label_text="Observatory Alt [m]:",
            color=self.coordinates_panel.cget("bg"), columnspan=3
        )

    def update_observatory_coordinates(self):
        """
        Update observatory coordinates when dropdown selection changes.

        Retrieves the selected observatory's coordinates from the database
        and updates the latitude, longitude, and altitude entry fields.
        """
        selected_observatory = self.dropdown_observatory_coords.get_value()

        if selected_observatory and selected_observatory != "None":
            DB = DBSQLite3(self.path_default)
            observatory_data = DB.get_observatory_by_key(selected_observatory)
            DB.close_DB()

            if observatory_data:
                # Update the entry fields with observatory data
                self.entry_obs_lat.set_value(str(observatory_data[1]))  # latitude
                self.entry_obs_long.set_value(str(observatory_data[2]))  # longitude
                self.entry_obs_alt.set_value(str(observatory_data[3]))   # altitude
                print(f"Updated coordinates for observatory: {selected_observatory}")
            else:
                print(f"No data found for observatory: {selected_observatory}")
        else:
            print("No observatory selected or 'None' selected")

    def update_observatory_list(self, observatories):
        """
        Update the observatory dropdown with a new list of observatories.

        Recreates the observatory dropdown widget with new observatory options
        and maintains the callback function.

        Parameters
        ----------
        observatories : list
            List of observatory names to populate the dropdown
        """
        try:
            # Clean the existing dropdown widget
            self.dropdown_observatory_coords.clean_widget()

            # Recreate the dropdown with new observatory list
            self.dropdown_observatory_coords = MyDropdown(
                self.coordinates_panel, 3, 1, observatories, "Select Observatory:",
                color=self.coordinates_panel.cget("bg"), columnspan=3
            )

            # Restore the callback function
            self.dropdown_observatory_coords.set_callback(self.update_observatory_coordinates)

            print(f"Updated observatory dropdown with {len(observatories)} observatories")
        except Exception as e:
            print(f"Error updating observatory dropdown: {e}")

    def get_atmospheric_parameters(self):
        """
        Get all atmospheric parameters as a dictionary.

        Collects all atmospheric, observational, wavelength, and observatory
        parameters from the GUI widgets. Parameters with value -999 are
        excluded from the final dictionary as they indicate values to be
        retrieved automatically.

        Returns
        -------
        dict
            Dictionary containing all atmospheric parameter values with
            -999 values excluded
        """
        print("Collecting atmospheric parameters...")

        # Collect all parameters first
        all_params = {
            # Atmospheric parameters
            "airmass": float(self.entry_airmass.get_value()),
            "pwv_mode": self.dropdown_pwv_mode.get_value(),
            "season": int(self.entry_season.get_value()),
            "time": int(self.entry_time.get_value()),
            "pwv": self.dropdown_pwv.get_value(),
            "pwv_func": self.dropdown_pwv.get_value(),
            "msolflux": float(self.entry_msolflux.get_value()),
            "initial_x": float(self.entry_initial_x.get_value()),
            "final_x": float(self.entry_final_x.get_value()),

            # Observational parameters
            "incl_moon": self.dropdown_incl_moon.get_value(),
            "moon_sun_sep": float(self.entry_moon_sun_sep.get_value()),
            "moon_target_sep": float(self.entry_moon_target_sep.get_value()),
            "moon_alt": float(self.entry_moon_alt.get_value()),
            "moon_earth_dist": float(self.entry_moon_earth_dist.get_value()),
            "incl_starlight": self.dropdown_incl_starlight.get_value(),
            "incl_zodiacal": self.dropdown_incl_zodiacal.get_value(),
            "ecl_lon": float(self.entry_ecl_lon.get_value()),
            "ecl_lat": float(self.entry_ecl_lat.get_value()),
            "incl_loweratm": self.dropdown_incl_loweratm.get_value(),
            "incl_upperatm": self.dropdown_incl_upperatm.get_value(),
            "incl_airglow": self.dropdown_incl_airglow.get_value(),
            "incl_therm": self.dropdown_incl_therm.get_value(),
            "therm_t1": float(self.entry_therm_t1.get_value()),
            "therm_e1": float(self.entry_therm_e1.get_value()),
            "therm_t2": float(self.entry_therm_t2.get_value()),
            "therm_e2": float(self.entry_therm_e2.get_value()),
            "therm_t3": float(self.entry_therm_t3.get_value()),
            "therm_e3": float(self.entry_therm_e3.get_value()),

            # Wavelength parameters
            "vacair": self.dropdown_vacair.get_value(),
            "wmin": float(self.entry_wmin.get_value()),
            "wmax": float(self.entry_wmax.get_value()),
            "wgrid_mode": self.dropdown_wgrid_mode.get_value(),
            "wdelta": float(self.entry_wdelta.get_value()),
            "wres_mul": float(self.entry_wres.get_value()),
            "lsf_type": self.dropdown_lsf_type.get_value(),
            "lsf_gauss_fwhm": float(self.entry_lsf_gauss_fwhm.get_value()),
            "lsf_boxcar_fwhm": float(self.entry_lsf_boxcar_fwhm.get_value()),

            # Observatory parameters
            "observatory": self.dropdown_observatory.get_value(),
            "temp_flag": int(self.entry_temp_flag.get_value())
        }

        # Filter out parameters with value -999
        filtered_params = {}
        excluded_count = 0
        for key, value in all_params.items():
            if isinstance(value, (int, float)) and value == -999:
                excluded_count += 1
                print(f"  Excluding parameter '{key}' (value = -999)")
            else:
                filtered_params[key] = value

        print(f"  Total parameters: {len(all_params)}")
        print(f"  Excluded parameters: {excluded_count}")
        print(f"  Final parameters: {len(filtered_params)}")

        return filtered_params

    def get_almanac_parameters(self):
        """
        Get coordinate parameters as an almanac dictionary.

        Collects equatorial coordinates and observatory position parameters
        needed for astronomical calculations and almanac computations.

        Returns
        -------
        dict
            Dictionary containing coordinate parameters including RA, Dec,
            observatory latitude, longitude, and altitude
        """
        print("Collecting almanac parameters...")
        return {
            # Equatorial coordinates
            "ra": float(self.entry_ra.get_value()),
            "dec": float(self.entry_dec.get_value()),

            # Observatory coordinates
            "obs_lat": float(self.entry_obs_lat.get_value()),
            "obs_long": float(self.entry_obs_long.get_value()),
            "obs_alt": float(self.entry_obs_alt.get_value())
        }

    def get_pwv_parameters(self):
        """
        Get PWV parameters from the first tab entry widgets.

        Collects PWV (precipitable water vapor) modeling parameters used
        for atmospheric simulation and telluric absorption calculations.

        Returns
        -------
        dict
            Dictionary containing PWV parameters including dt, pace, s,
            scale, tExp, and seed values
        """
        print("Collecting PWV parameters...")
        return {
            "dt": float(self.entry_dt.get_value()),
            "pace": float(self.entry_pace.get_value()),
            "s": float(self.entry_s.get_value()),
            "scale": float(self.entry_scale.get_value()),
            "tExp": float(self.entry_tExp.get_value()),
            "seed": int(self.entry_seed.get_value())
        }

    def get_row(self):
        """
        Get the currently selected row information from the filter table.

        Retrieves target information and array values from the currently
        selected row in the filter table interface.

        Returns
        -------
        tuple
            (target, array_values, selected_name) or (None, None, None)
            if no row is selected or an error occurs
        """
        try:
            target = self.dropdown_target.get_value()
            arr_values = self.frame_input.frame_filter_table.frame_table.table.get_arr_values()
            selected_name = arr_values[ConstantVariables.DICT_FILTER_TABLE["ID"]["index"]]
        except Exception as e:
            messagebox.showerror("Choose a row", str(e))
            return None, None, None
        return target, arr_values, selected_name

    def update_from_target_or_rad_mode(self):
        """
        Update night information for the current target and instrument configuration.

        Called when the target selection or radiation transfer mode changes.
        Updates the available nights, instruments, and cross-correlation models
        to match the new selection.
        """
        if self.global_exc == 0:
            return
        self.update_nights_instrument()
        self.update_CC_model()
        self.update_nights()

    def update_nights(self):
        """
        Update HR instrument settings and available nights.

        Called when the HR instrument selection changes. Updates the nights
        text field with available observation nights for the selected
        target and instrument combination.
        """
        if self.global_exc == 0:
            return
        instrumenti = self.dropdown_hr_instrument.get_value()
        target_id = self.dropdown_target.get_value()
        nights = get_target_nights(
            self.path_folder_targets,
            target_id,
            self.dropdown_rad_mode.get_value(),
            instrumenti,
            0
        )
        self.target_information = get_target_info(self.path_folder_targets, target_id, self.dropdown_rad_mode.get_value())
        self.entry_ra.set_value(str(self.target_information.ra))
        self.entry_dec.set_value(str(self.target_information.dec))
        self.textfield_nights.insert_text(nights)

    def update_nights_instrument(self):
        """
        Update the HR instrument dropdown based on current target and mode.

        Refreshes the list of available HR instruments for the selected
        target and radiation transfer mode. Recreates the dropdown widget
        with updated options.
        """
        if self.global_exc == 0:
            return
        target_id = self.dropdown_target.get_value()
        # Update HR instruments
        self.list_hr_instrument = get_target_instruments(
            self.path_folder_targets,
            target_id,
            "HR",
            self.dropdown_rad_mode.get_value(),
            Path(__file__).name
        )
        self.dropdown_hr_instrument.clean_widget()
        self.dropdown_hr_instrument = MyDropdown(
            self.main_controls_panel, 0, 9, self.list_hr_instrument, "HR Instrument:",
            color=self.main_controls_panel.cget("bg"), columnspan=2
        )
        self.dropdown_hr_instrument.set_callback(self.update_nights)

    def update_CC_model(self):
        """
        Update the cross-correlation model dropdown.

        Refreshes the list of available cross-correlation models based on
        the current target and radiation transfer mode selection.
        """
        target_id = self.dropdown_target.get_value()
        # Update cross-correlation models
        self.dropdown_cross_corr.clean_widget()
        dict_list_cc = get_list_cross_corr_models(
            self.path_folder_targets,
            target_id,
            self.dropdown_rad_mode.get_value()
        )
        self.dropdown_cross_corr = MyDropdown(
            self.main_controls_panel, 0, 11, list(sorted(dict_list_cc.keys())),
            "Model CC", initial_value=0,
            color=self.main_controls_panel.cget("bg"), columnspan=2
        )

    def run_selected_generation(self):
        """
        Run the selected generation based on dropdown choice.

        Checks the generation mode dropdown and calls the appropriate
        generation function. Supports synthetic generations with continuous
        or discontinuous PWV, and shuffle generations.
        """
        selected_mode = self.dropdown_generation_mode.get_value()
        print(f"Selected generation mode: {selected_mode}")

        if selected_mode == "Synthetic":
            print("Running Synthetic generation with PWV parameters...")
            self.run_synthetic(False)
        elif selected_mode == "Synthetic Discontinuous PWV":
            print("Running Synthetic Discontinuous PWV generation...")
            self.run_synthetic(True)
        elif selected_mode == "Shuffle":
            print("Running shuffle generation...")
            self.run_shuffle()
        else:
            print(f"Unknown generation mode: {selected_mode}")

    def modify_target_list(self, target_list):
        """
        Update the target dropdown with a new list of targets.

        Recreates the target dropdown widget with new target options
        and updates the callback function.

        Parameters
        ----------
        target_list : list
            List of target names to populate the dropdown
        """
        self.dropdown_target.clean_widget()
        self.dropdown_target = MyDropdown(
            self.main_controls_panel, 0, 1, target_list, "Target:",
            color=self.main_controls_panel.cget("bg"), columnspan=2
        )
        self.dropdown_target.set_callback(self.update_from_target_or_rad_mode)

    def run_shuffle(self):
        """
        Run the HR data shuffle with the current configuration.

        Collects all current settings and parameters from the GUI and
        executes the HR data shuffle using the shuffle_HR function.
        This method runs synchronously and blocks until completion.
        """
        print("=== Starting HR Data shuffle ===")
        self.terminator_night = "sim"

        target_obj = Target(
            str(Path(self.path_folder_targets, self.dropdown_target.get_value(), "info.yaml")),
            self.dropdown_rad_mode.get_value()
        )
        print(f"Target: {target_obj.name}")
        print(f"Radiation Transfer Mode: {self.dropdown_rad_mode.get_value()}")

        with DBSQLite3(ConstantVariables.path_default) as DB:
            instrument_obj = DB.get_instrument_by_key(self.dropdown_hr_instrument.get_value())
        print(f"HR Instrument: {self.dropdown_hr_instrument.get_value()}")

        print(f"Nights: {self.textfield_nights.get_text()}")
        print(f"Cross-correlation Model: {self.dropdown_cross_corr.get_value()}")
        print(f"Order Selection: {self.textfield_order_selection.get_text()}")
        print(f"Telluric Removal Method: {self.dropdown_tell_rm_method.get_value()}")

        print("Running shuffle...")
        shuffle_HR(
            target_obj,
            self.path_folder_targets,
            self.dropdown_rad_mode.get_value(),
            instrument_obj,
            self.textfield_nights.get_text(),
            self.dropdown_cross_corr.get_value(),
            self.textfield_order_selection.get_text(),
            self.dropdown_tell_rm_method.get_value(),
        )
        print("=== HR Data shuffle Completed ===\n")
        messagebox.showinfo(
            "Shuffle HR Generation Complete",
            "The shuffle HR generation has completed successfully!"
        )

    def run_synthetic(self, discontinuos=True):
        """
        Run the synthetic HR data generation in background with the current configuration.

        Handles stellar spectrum download if needed and runs the synthetic
        generation in a background subprocess. Manages parameter collection,
        stellar spectrum acquisition, and background process monitoring.

        Parameters
        ----------
        discontinuos : bool, optional
            Whether to use discontinuous PWV modeling, by default True
        """
        self.terminator_night = "syn"
        print("=== Starting Background Synthetic HR Data Generation ===")

        target_obj = Target(
            str(Path(self.path_folder_targets, self.dropdown_target.get_value(), "info.yaml")),
            self.dropdown_rad_mode.get_value()
        )
        print(f"Target: {target_obj.name}")
        print(f"Radiation Transfer Mode: {self.dropdown_rad_mode.get_value()}")

        with DBSQLite3(ConstantVariables.path_default) as DB:
            instrument_obj = DB.get_instrument_by_key(self.dropdown_hr_instrument.get_value())
        print(f"HR Instrument: {self.dropdown_hr_instrument.get_value()}")

        folder_stellar_spc = str(Path(self.path_folder_targets, target_obj.name, "Star_Spectrum"))

        path_phoenixf = download_star_spectrum(self, target_obj, folder_stellar_spc)
        if path_phoenixf is None:
            return

        # Get atmospheric parameters
        print("Collecting atmospheric and almanac parameters...")
        atmospheric_params = self.get_atmospheric_parameters()
        almanac_params = self.get_almanac_parameters()
        pwv_params = self.get_pwv_parameters()

        print(f"Atmospheric parameters collected: {len(atmospheric_params)} parameters")
        print(f"Almanac parameters collected: {len(almanac_params)} parameters")
        if discontinuos:
            print(f"PWV parameters not collected")
        else:
            print(f"PWV parameters collected: {len(pwv_params)} parameters")

        # Save parameters to pickle file
        print("Saving parameters to pickle file...")
        pickle_path = self._save_synthetic_hr_data(
            target_obj, instrument_obj, path_phoenixf, atmospheric_params, almanac_params, pwv_params, discontinuos
        )

        # Disable buttons and start background process
        print("Starting background subprocess...")
        self._disable_buttons()
        self._start_synthetic_subprocess(pickle_path)

        print("=== Background Synthetic HR Generation Started ===")
        print("The generation is running in the background. You will be notified when it completes.")

    def update_pwv_image(self):
        """
        Update the PWV image when the dropdown selection changes.

        Loads and displays the appropriate PWV function plot based on
        the selected function. Falls back to default logo if the
        specific function image is not found.
        """
        try:
            selected_function = self.dropdown_pwv.get_value()
            print(f"Updating PWV image for function: {selected_function}")
            image_path = Path(self.path_default, "GUIBRUSHR", "Files", "pwv_functions_plots", f"{selected_function}.png")

            if image_path.exists():
                self.pwv_image.update_image(str(image_path))
                print(f"PWV image updated successfully: {selected_function}")
            else:
                # If image doesn't exist, use default logo
                default_image_path = Path(self.path_default, "GUIBRUSHR", "Files", "logo.png")
                if default_image_path.exists():
                    self.pwv_image.update_image(str(default_image_path))
                    print(f"Using default logo (function image not found): {selected_function}")
                else:
                    print(f"Neither function image nor default logo found. Function: {selected_function}")
                print(f"PWV function image not found: {image_path}")
        except Exception as e:
            print(f"Error updating PWV image: {e}")

    def _disable_buttons(self):
        """
        Disable buttons during background execution.

        Prevents user interaction with generation controls while a
        background process is running.
        """
        try:
            self.button_run.set_status('disabled')
        except Exception as _:
            pass  # Handle cases where buttons might not exist yet

    def _enable_buttons(self):
        """
        Enable buttons after background execution completes.

        Re-enables user interaction with generation controls after
        background process completion.
        """
        try:
            self.button_run.set_status('normal')
        except Exception as _:
            pass  # Handle cases where buttons might not exist yet

    def _save_synthetic_hr_data(self, target_obj, instrument_obj, path_phoenixf, atmospheric_params, almanac_params, pwv_params, discontinuos):
        """
        Save all parameters needed for synthetic HR generation to pickle file.

        Creates a temporary directory and saves all generation parameters
        to a pickle file for use by the background subprocess.

        Parameters
        ----------
        target_obj : Target
            Target object containing target information
        instrument_obj : Instrument
            Instrument object containing instrument configuration
        path_phoenixf : str
            Path to stellar spectrum file
        atmospheric_params : dict
            Atmospheric parameters dictionary
        almanac_params : dict
            Almanac parameters dictionary
        pwv_params : dict
            PWV parameters dictionary
        discontinuos : bool
            Whether to use discontinuous PWV mode

        Returns
        -------
        str
            Path to the created pickle file containing all parameters

        Notes
        -----
        All parameters are organized into a single dictionary structure for
        improved organization and maintainability. The dictionary contains
        core generation parameters, GUI settings, and processing options.
        """
        # Create a temp directory for the background process
        temp_dir = Path(self.path_folder_targets, target_obj.name, "temp_synthetic")
        temp_dir.mkdir(exist_ok=True)

        pickle_path = temp_dir / "synthetic_hr_params.pkl"

        # Organize all parameters into a single dictionary
        generation_params = {
            # Core framework parameters
            'path_default': self.path_default,
            'target_obj': target_obj,
            'path_folder_targets': self.path_folder_targets,
            'rad_mode': self.dropdown_rad_mode.get_value(),
            'instrument_obj': instrument_obj,

            # Observation parameters
            'nights_text': self.textfield_nights.get_text(),
            'cross_corr_model': self.dropdown_cross_corr.get_value(),
            'order_selection': self.textfield_order_selection.get_text(),
            'tell_rm_method': self.dropdown_tell_rm_method.get_value(),
            'path_stellar_spectrum': path_phoenixf,

            # Physical model parameters
            'atmospheric_params': atmospheric_params,
            'almanac_params': almanac_params,
            'pwv_params': pwv_params,

            # Processing options
            'discontinuous': discontinuos,
            'save_plots': self.checkbox_save_plots.get_value(),
            'noise_factor': float(self.entry_noise_factor.get_value()),
            'gain': float(self.entry_gain.get_value()),
            'include_0_1': self.checkbox_include_0_1.get_value(),
            'error_type': self.dropdown_error_type.get_value(),

            # Fixed parameters
            'eccentricity': False  # Always False for now
        }

        # Save the complete parameter dictionary
        with open(str(pickle_path), "wb") as fo:
            pickle.dump(generation_params, fo)

        return str(pickle_path)

    def _start_synthetic_subprocess(self, pickle_path):
        """
        Start the synthetic HR generation subprocess.

        Launches the synthetic_HR.py script as a subprocess with the
        pickle file containing all parameters. Sets up process monitoring.

        Parameters
        ----------
        pickle_path : str
            Path to the pickle file containing all generation parameters
        """
        script_path = str(Path(
            self.path_default, "GUIBRUSHR", "GUI", "Input_Output_Panels", "Input_Panels",
            "TabPanels", "FrameGenerationHRData", "synthetic_HR.py"
        ))

        self.synthetic_process = subprocess.Popen(
            ["python3", script_path, pickle_path, self.path_default],
            stderr=subprocess.STDOUT,
            universal_newlines=True,
        )
        self.synthetic_running = True
        self.window.after(500, self.check_synthetic_proc)

    def check_synthetic_proc(self):
        """
        Check if the synthetic HR subprocess has completed.

        Monitors the subprocess status and handles completion or failure.
        Re-enables buttons and shows appropriate messages when the process
        finishes. Schedules itself to run again if the process is still active.
        """
        if self.synthetic_process.poll() is not None:
            # Subprocess completed
            self.synthetic_running = False
            self._enable_buttons()

            # Show completion message
            returncode = self.synthetic_process.returncode
            path_instrument = create_path_night(
                self.path_folder_targets, self.dropdown_target.get_value(), self.dropdown_rad_mode.get_value(), self.dropdown_hr_instrument.get_value()
            )
            nights = self.textfield_nights.get_text()
            night = nights.split(",")
            path_night = str(Path(path_instrument, night[-1]))
            path_night = path_night + self.terminator_night
            path_pdf = str(Path(path_night, "aligned_matrix.pdf"))
            if returncode == 0:
                messagebox.showinfo(
                    "Synthetic HR Generation Complete",
                    "The synthetic HR generation has completed successfully!"
                )
                MyPDFWindow(path_pdf=path_pdf, title="Synthetic HR Data")
            else:
                messagebox.showerror(
                    "Synthetic HR Generation Error",
                    f"The synthetic HR generation failed with return code {returncode}. Check console for details."
                )
        else:
            # Subprocess still running - check again later
            self.window.after(500, self.check_synthetic_proc)
