"""Toplevel window for configuring Target and LRU connections.

This module provides the `ConnectionSettingsWindow` dialog used by the
main UI to configure Target and LRU communication settings.
"""

import tkinter as tk
from tkinter import ttk, messagebox


class ConnectionSettingsWindow(tk.Toplevel):
    """
    A modal dialog window for configuring Target and LRU connection settings.

    This window allows users to specify connection parameters for different
    communication protocols like SFP, TFTP, and Serial.
    """

    def __init__(self, master, config_manager, connection_config):
        """
        Initialize the ConnectionSettingsWindow.

        Args:
            master (tk.Widget): The parent widget.
            config_manager (ConfigManager): The application's configuration manager.
            connection_config (dict): A dictionary containing the current
                                      connection settings.
        """
        super().__init__(master)
        self.master_view = master
        self.config_manager = config_manager
        self.connection_config = connection_config

        self.title("Connection Settings")
        self.transient(master)
        self.grab_set()
        self.resizable(False, False)

        self._create_widgets()
        self._load_settings()
        self.protocol("WM_DELETE_WINDOW", self._on_cancel)

        # Center the window on the main view
        self.update_idletasks()
        try:
            main_x = master.winfo_rootx()
            main_y = master.winfo_rooty()
            main_w = master.winfo_width()
            main_h = master.winfo_height()
        except Exception:
            main_x = main_y = main_w = main_h = 0

        try:
            win_w = self.winfo_reqwidth()
            win_h = self.winfo_reqheight()
        except Exception:
            win_w = self.winfo_width()
            win_h = self.winfo_height()
        x = main_x + (main_w // 2) - (win_w // 2)
        y = main_y + (main_h // 2) - (win_h // 2)
        self.geometry(f"{win_w}x{win_h}+{x}+{y}")

    def _load_settings(self):
        """
        Populate the UI fields with the current connection settings.

        Reads values from `self.connection_config` and sets the corresponding
        Tkinter variables to update the widgets.
        """
        # --- Load Target Settings ---
        target_cfg = self.connection_config.get("target", {})
        target_sfp_cfg = target_cfg.get("sfp", {})
        # Normalize connection type so it matches the radiobutton values used elsewhere
        t = (target_cfg.get("type", "SFP") or "").lower()
        if t == "serial":
            self.target_vars["conn_type"].set("Serial")
        else:
            self.target_vars["conn_type"].set(t.upper() or "SFP")
        self.target_vars["tftp_ip"].set(
            target_cfg.get("tftp", {}).get("ip", "127.0.0.1")
        )
        self.target_vars["tftp_port"].set(target_cfg.get("tftp", {}).get("port", 69))
        self.target_vars["serial_port"].set(
            target_cfg.get("serial", {}).get("port", "COM1")
        )
        self.target_vars["serial_baud"].set(
            target_cfg.get("serial", {}).get("baudrate", 9600)
        )
        self.target_vars["sfp_ip"].set(target_sfp_cfg.get("ip", "127.0.0.1"))
        self.target_vars["sfp_port"].set(target_sfp_cfg.get("port", 60003))
        self.target_vars["sfp_local_port"].set(target_sfp_cfg.get("local_port", 60002))
        self.target_vars["sfp_use_json"].set(
            target_sfp_cfg.get("use_json_protocol", False)
        )

        self.target_vars["sfp_prediction_offset"].set(
            target_sfp_cfg.get("prediction_offset_ms", 0.0)
        )

        # Select the correct notebook tab for target
        try:
            tab_idx = {"SFP": 0, "TFTP": 1, "Serial": 2}[
                self.target_vars["conn_type"].get()
            ]
            self.target_vars["notebook"].select(tab_idx)
        except Exception:
            pass

        # --- Load LRU Settings ---
        lru_cfg = self.connection_config.get("lru", {})
        lru_sfp_cfg = lru_cfg.get("sfp", {})
        t = (lru_cfg.get("type", "SFP") or "").lower()
        if t == "serial":
            self.lru_vars["conn_type"].set("Serial")
        else:
            self.lru_vars["conn_type"].set(t.upper() or "SFP")
        self.lru_vars["tftp_ip"].set(lru_cfg.get("tftp", {}).get("ip", "127.0.0.1"))
        self.lru_vars["tftp_port"].set(lru_cfg.get("tftp", {}).get("port", 69))
        self.lru_vars["serial_port"].set(lru_cfg.get("serial", {}).get("port", "COM1"))
        self.lru_vars["serial_baud"].set(
            lru_cfg.get("serial", {}).get("baudrate", 9600)
        )
        self.lru_vars["sfp_ip"].set(lru_sfp_cfg.get("ip", "127.0.0.1"))
        self.lru_vars["sfp_port"].set(lru_sfp_cfg.get("port", 60003))
        self.lru_vars["sfp_local_port"].set(lru_sfp_cfg.get("local_port", 60002))
        self.lru_vars["sfp_use_json"].set(lru_sfp_cfg.get("use_json_protocol", False))

        self.lru_vars["sfp_prediction_offset"].set(
            lru_sfp_cfg.get("prediction_offset_ms", 0.0)
        )

        # Select the correct notebook tab for lru
        try:
            tab_idx = {"SFP": 0, "TFTP": 1, "Serial": 2}[
                self.lru_vars["conn_type"].get()
            ]
            self.lru_vars["notebook"].select(tab_idx)
        except Exception:
            pass

    def _create_widgets(self):
        """
        Create and layout all the widgets for the dialog.

        This includes separate panels for Target and LRU connections,
        and action buttons for saving or canceling.
        """
        # Main content frame (scrolling not required here but keep expandable)
        main_frame = ttk.Frame(self, padding="10")
        main_frame.pack(fill=tk.BOTH, expand=True)

        target_frame = ttk.LabelFrame(main_frame, text="Target Connection")
        target_frame.pack(fill=tk.X, expand=True, pady=5, padx=5)
        self.target_vars = self._create_connection_panel(target_frame)

        lru_frame = ttk.LabelFrame(main_frame, text="LRU Connection")
        lru_frame.pack(fill=tk.X, expand=True, pady=5, padx=5)
        self.lru_vars = self._create_connection_panel(lru_frame)

        # Keep action buttons fixed at the bottom of the dialog so they are
        # not pushed out of view by variable-height content in the main_frame.
        sep = ttk.Separator(self, orient=tk.HORIZONTAL)
        sep.pack(fill=tk.X, side=tk.BOTTOM, pady=(0, 5))

        button_frame = ttk.Frame(self)
        button_frame.pack(fill=tk.X, side=tk.BOTTOM, pady=8, padx=10)
        ttk.Button(button_frame, text="Save", command=self._on_save).pack(
            side=tk.RIGHT, padx=5
        )
        ttk.Button(button_frame, text="Cancel", command=self._on_cancel).pack(
            side=tk.RIGHT, padx=5
        )

    def _create_connection_panel(self, parent_frame):
        """
        Create a panel for a single connection type (SFP/TFTP/Serial).

        This panel includes a dropdown to select the connection type and a
        notebook with tabs for each protocol's specific settings.

        Args:
            parent_frame (ttk.Frame): The parent frame to contain the panel.

        Returns:
            dict: A dictionary mapping variable names to their Tkinter
                  variable objects.
        """
        vars = {}

        # Top row: label + combobox to choose connection type
        type_row = ttk.Frame(parent_frame)
        type_row.pack(fill=tk.X, padx=5, pady=(5, 10))

        vars["conn_type"] = tk.StringVar()
        ttk.Label(type_row, text="Type:").pack(side=tk.LEFT)
        type_combo = ttk.Combobox(
            type_row,
            textvariable=vars["conn_type"],
            state="readonly",
            values=["SFP", "TFTP", "Serial"],
            width=10,
        )
        type_combo.pack(side=tk.LEFT, padx=6)

        # Notebook with a tab per connection type
        notebook = ttk.Notebook(parent_frame)
        notebook.pack(fill=tk.X, expand=False, padx=5, pady=2)
        vars["notebook"] = notebook

        # Frames for each tab
        sfp_frame = ttk.Frame(notebook, padding=5)
        tftp_frame = ttk.Frame(notebook, padding=5)
        serial_frame = ttk.Frame(notebook, padding=5)

        notebook.add(sfp_frame, text="SFP")
        notebook.add(tftp_frame, text="TFTP")
        notebook.add(serial_frame, text="Serial")

        def _on_tab_changed(event):
            idx = notebook.index(notebook.select())
            vars["conn_type"].set({0: "SFP", 1: "TFTP", 2: "Serial"}.get(idx, "SFP"))

        def _on_type_changed(event=None):
            name = vars["conn_type"].get()
            idx = {"SFP": 0, "TFTP": 1, "Serial": 2}.get(name, 0)
            notebook.select(idx)

        notebook.bind("<<NotebookTabChanged>>", _on_tab_changed)
        type_combo.bind("<<ComboboxSelected>>", _on_type_changed)

        # --- TFTP Widgets ---
        tftp_grid = tftp_frame
        tftp_grid.columnconfigure(1, weight=1)
        ttk.Label(tftp_grid, text="IP Address:").grid(
            row=0, column=0, sticky=tk.W, pady=2
        )
        vars["tftp_ip"] = tk.StringVar()
        ttk.Entry(tftp_grid, textvariable=vars["tftp_ip"]).grid(
            row=0, column=1, sticky=tk.EW, padx=5
        )
        ttk.Label(tftp_grid, text="Port:").grid(row=1, column=0, sticky=tk.W, pady=2)
        vars["tftp_port"] = tk.IntVar()
        ttk.Spinbox(
            tftp_grid, from_=1, to=65535, textvariable=vars["tftp_port"], width=7
        ).grid(row=1, column=1, sticky=tk.W, padx=5)

        # --- Serial Widgets ---
        serial_grid = serial_frame
        serial_grid.columnconfigure(1, weight=1)
        ttk.Label(serial_grid, text="COM Port:").grid(
            row=0, column=0, sticky=tk.W, pady=2
        )
        vars["serial_port"] = tk.StringVar()
        ttk.Combobox(
            serial_grid,
            textvariable=vars["serial_port"],
            values=[f"COM{i}" for i in range(1, 17)],
        ).grid(row=0, column=1, sticky=tk.EW, padx=5)
        ttk.Label(serial_grid, text="Baud Rate:").grid(
            row=1, column=0, sticky=tk.W, pady=2
        )
        vars["serial_baud"] = tk.IntVar()
        ttk.Combobox(
            serial_grid,
            textvariable=vars["serial_baud"],
            values=[9600, 19200, 38400, 57600, 115200],
            width=7,
        ).grid(row=1, column=1, sticky=tk.W, padx=5)

        # --- SFP Widgets ---
        sfp_grid = sfp_frame
        sfp_grid.columnconfigure(1, weight=1)
        ttk.Label(sfp_grid, text="Server IP:").grid(
            row=0, column=0, sticky=tk.W, pady=2
        )
        vars["sfp_ip"] = tk.StringVar()
        ttk.Entry(sfp_grid, textvariable=vars["sfp_ip"]).grid(
            row=0, column=1, sticky=tk.EW, padx=5
        )
        ttk.Label(sfp_grid, text="Server Port:").grid(
            row=1, column=0, sticky=tk.W, pady=2
        )
        vars["sfp_port"] = tk.IntVar()
        ttk.Spinbox(
            sfp_grid, from_=1, to=65535, textvariable=vars["sfp_port"], width=7
        ).grid(row=1, column=1, sticky=tk.W, padx=5)
        ttk.Label(sfp_grid, text="Local Port:").grid(
            row=2, column=0, sticky=tk.W, pady=2
        )
        vars["sfp_local_port"] = tk.IntVar()
        ttk.Spinbox(
            sfp_grid, from_=1, to=65535, textvariable=vars["sfp_local_port"], width=7
        ).grid(row=2, column=1, sticky=tk.W, padx=5)

        # Prediction Offset widget on row 3
        ttk.Label(sfp_grid, text="Prediction Offset (ms):").grid(
            row=3, column=0, sticky=tk.W, pady=2
        )
        vars["sfp_prediction_offset"] = tk.DoubleVar()
        ttk.Spinbox(
            sfp_grid,
            from_=0,
            to=1000,
            increment=1,
            textvariable=vars["sfp_prediction_offset"],
            width=7,
        ).grid(row=3, column=1, sticky=tk.W, padx=5)

        # JSON protocol checkbox moved to row 4
        vars["sfp_use_json"] = tk.BooleanVar()
        ttk.Checkbutton(
            sfp_grid, text="Use JSON Protocol", variable=vars["sfp_use_json"]
        ).grid(row=4, column=0, columnspan=2, sticky=tk.W, pady=(5, 0))

        # place test button to the right of the notebook area
        test_row = ttk.Frame(parent_frame)
        test_row.pack(fill=tk.X, padx=5, pady=(2, 8))
        test_button = ttk.Button(
            test_row,
            text="Test Connection",
            command=lambda: self._test_connection(vars),
        )
        test_button.pack(side=tk.RIGHT)

        return vars

    # Note: previous implementation used pack/grid switching per type. The UI
    # now uses a Notebook with tabs and a Combobox to select the active type.

    def _test_connection(self, vars):
        """
        Perform a quick connection test for the selected protocol.

        Displays a messagebox with the success or failure result. Catches
        and displays any exceptions that occur during the test.

        Args:
            vars (dict): The dictionary of Tkinter variables for the
                         connection panel being tested.
        """
        conn_type = vars["conn_type"].get()
        result = False
        config = {}

        try:
            if conn_type == "TFTP":
                from target_simulator.core.tftp_communicator import TFTPCommunicator

                config = {"ip": vars["tftp_ip"].get(), "port": vars["tftp_port"].get()}
                result = TFTPCommunicator.test_connection(config)
            elif conn_type == "Serial":
                from target_simulator.core.serial_communicator import SerialCommunicator

                config = {
                    "port": vars["serial_port"].get(),
                    "baudrate": vars["serial_baud"].get(),
                }
                result = SerialCommunicator.test_connection(config)
            elif conn_type == "SFP":
                from target_simulator.core.sfp_communicator import SFPCommunicator

                config = {"local_port": vars["sfp_local_port"].get()}
                result = SFPCommunicator.test_connection(config)
        except Exception as e:
            messagebox.showerror("Test Error", f"An error occurred: {e}", parent=self)
            return

        if result:
            messagebox.showinfo(
                "Connection Test",
                f"{conn_type} connection test successful!",
                parent=self,
            )
        else:
            messagebox.showerror(
                "Connection Test", f"{conn_type} connection test failed.", parent=self
            )

    def _on_save(self):
        """
        Save the connection settings and close the dialog.

        Collects all values from the UI widgets, updates the main application's
        configuration, and then destroys the window.
        """
        new_config = {
            "target": {
                "type": self.target_vars["conn_type"].get().lower(),
                "tftp": {
                    "ip": self.target_vars["tftp_ip"].get(),
                    "port": self.target_vars["tftp_port"].get(),
                },
                "serial": {
                    "port": self.target_vars["serial_port"].get(),
                    "baudrate": self.target_vars["serial_baud"].get(),
                },
                "sfp": {
                    "ip": self.target_vars["sfp_ip"].get(),
                    "port": self.target_vars["sfp_port"].get(),
                    "local_port": self.target_vars["sfp_local_port"].get(),
                    "use_json_protocol": self.target_vars["sfp_use_json"].get(),
                    "prediction_offset_ms": self.target_vars[
                        "sfp_prediction_offset"
                    ].get(),
                },
            },
            "lru": {
                "type": self.lru_vars["conn_type"].get().lower(),
                "tftp": {
                    "ip": self.lru_vars["tftp_ip"].get(),
                    "port": self.lru_vars["tftp_port"].get(),
                },
                "serial": {
                    "port": self.lru_vars["serial_port"].get(),
                    "baudrate": self.lru_vars["serial_baud"].get(),
                },
                "sfp": {
                    "ip": self.lru_vars["sfp_ip"].get(),
                    "port": self.lru_vars["sfp_port"].get(),
                    "local_port": self.lru_vars["sfp_local_port"].get(),
                    "use_json_protocol": self.lru_vars["sfp_use_json"].get(),
                    "prediction_offset_ms": self.lru_vars[
                        "sfp_prediction_offset"
                    ].get(),
                },
            },
        }
        self.master_view.update_connection_settings(new_config)
        self.destroy()

    def _on_cancel(self):
        """
        Close the dialog without saving any changes.
        """
        self.destroy()


# target_simulator/gui/connection_settings_window.py
"""
Toplevel window for configuring Target and LRU connections.
"""
import tkinter as tk
from tkinter import ttk, messagebox


class ConnectionSettingsWindow(tk.Toplevel):
    """A dialog for configuring connection settings.

    Inputs:
        - master: parent Tk widget
        - config_manager: ConfigManager instance for persistence
        - connection_config: dict with initial connection settings

    Side-effects:
        - creates a modal Toplevel window and writes settings via
            master_view.update_connection_settings on Save.
    """

    def __init__(self, master, config_manager, connection_config):
        super().__init__(master)
        self.master_view = master
        self.config_manager = config_manager
        self.connection_config = connection_config

        self.title("Connection Settings")
        self.transient(master)
        self.grab_set()
        self.resizable(False, False)

        self._create_widgets()
        self._load_settings()
        self.protocol("WM_DELETE_WINDOW", self._on_cancel)

        # Center the window on the main view
        self.update_idletasks()
        main_x = master.winfo_rootx()
        main_y = master.winfo_rooty()
        main_w = master.winfo_width()
        main_h = master.winfo_height()
        # Use requested size so the dialog is large enough to show all widgets.
        # Some test doubles or minimal masters may not implement winfo_reqwidth/
        # winfo_reqheight, so fall back to winfo_width/height when necessary.
        try:
            win_w = self.winfo_reqwidth()
            win_h = self.winfo_reqheight()
        except Exception:
            win_w = self.winfo_width()
            win_h = self.winfo_height()
        x = main_x + (main_w // 2) - (win_w // 2)
        y = main_y + (main_h // 2) - (win_h // 2)
        self.geometry(f"{win_w}x{win_h}+{x}+{y}")

    def _load_settings(self):
        """Populate UI fields from self.connection_config.

        Reads values from the provided connection_config and sets the
        associated Tk variables used by the widgets.
        """
        # --- Load Target Settings ---
        target_cfg = self.connection_config.get("target", {})
        target_sfp_cfg = target_cfg.get("sfp", {})
        # Normalize connection type so it matches the radiobutton values used elsewhere
        t = (target_cfg.get("type", "SFP") or "").lower()
        if t == "serial":
            self.target_vars["conn_type"].set("Serial")
        else:
            self.target_vars["conn_type"].set(t.upper() or "SFP")
        self.target_vars["tftp_ip"].set(
            target_cfg.get("tftp", {}).get("ip", "127.0.0.1")
        )
        self.target_vars["tftp_port"].set(target_cfg.get("tftp", {}).get("port", 69))
        self.target_vars["serial_port"].set(
            target_cfg.get("serial", {}).get("port", "COM1")
        )
        self.target_vars["serial_baud"].set(
            target_cfg.get("serial", {}).get("baudrate", 9600)
        )
        self.target_vars["sfp_ip"].set(target_sfp_cfg.get("ip", "127.0.0.1"))
        self.target_vars["sfp_port"].set(target_sfp_cfg.get("port", 60003))
        self.target_vars["sfp_local_port"].set(target_sfp_cfg.get("local_port", 60002))
        self.target_vars["sfp_use_json"].set(
            target_sfp_cfg.get("use_json_protocol", False)
        )

        self.target_vars["sfp_prediction_offset"].set(
            target_sfp_cfg.get("prediction_offset_ms", 0.0)
        )

        # Select the correct notebook tab for target
        try:
            tab_idx = {"SFP": 0, "TFTP": 1, "Serial": 2}[
                self.target_vars["conn_type"].get()
            ]
            self.target_vars["notebook"].select(tab_idx)
        except Exception:
            pass

        # --- Load LRU Settings ---
        lru_cfg = self.connection_config.get("lru", {})
        lru_sfp_cfg = lru_cfg.get("sfp", {})
        t = (lru_cfg.get("type", "SFP") or "").lower()
        if t == "serial":
            self.lru_vars["conn_type"].set("Serial")
        else:
            self.lru_vars["conn_type"].set(t.upper() or "SFP")
        self.lru_vars["tftp_ip"].set(lru_cfg.get("tftp", {}).get("ip", "127.0.0.1"))
        self.lru_vars["tftp_port"].set(lru_cfg.get("tftp", {}).get("port", 69))
        self.lru_vars["serial_port"].set(lru_cfg.get("serial", {}).get("port", "COM1"))
        self.lru_vars["serial_baud"].set(
            lru_cfg.get("serial", {}).get("baudrate", 9600)
        )
        self.lru_vars["sfp_ip"].set(lru_sfp_cfg.get("ip", "127.0.0.1"))
        self.lru_vars["sfp_port"].set(lru_sfp_cfg.get("port", 60003))
        self.lru_vars["sfp_local_port"].set(lru_sfp_cfg.get("local_port", 60002))
        self.lru_vars["sfp_use_json"].set(lru_sfp_cfg.get("use_json_protocol", False))

        self.lru_vars["sfp_prediction_offset"].set(
            lru_sfp_cfg.get("prediction_offset_ms", 0.0)
        )

        # Select the correct notebook tab for lru
        try:
            tab_idx = {"SFP": 0, "TFTP": 1, "Serial": 2}[
                self.lru_vars["conn_type"].get()
            ]
            self.lru_vars["notebook"].select(tab_idx)
        except Exception:
            pass

    def _create_widgets(self):
        """Create and layout all widgets for the connection dialog.

        Returns nothing; stores Tk variables in self.target_vars and
        self.lru_vars for later readout.
        """
        # Main content frame (scrolling not required here but keep expandable)
        main_frame = ttk.Frame(self, padding="10")
        main_frame.pack(fill=tk.BOTH, expand=True)

        target_frame = ttk.LabelFrame(main_frame, text="Target Connection")
        target_frame.pack(fill=tk.X, expand=True, pady=5, padx=5)
        self.target_vars = self._create_connection_panel(target_frame)

        lru_frame = ttk.LabelFrame(main_frame, text="LRU Connection")
        lru_frame.pack(fill=tk.X, expand=True, pady=5, padx=5)
        self.lru_vars = self._create_connection_panel(lru_frame)

        # Keep action buttons fixed at the bottom of the dialog so they are
        # not pushed out of view by variable-height content in the main_frame.
        sep = ttk.Separator(self, orient=tk.HORIZONTAL)
        sep.pack(fill=tk.X, side=tk.BOTTOM, pady=(0, 5))

        button_frame = ttk.Frame(self)
        button_frame.pack(fill=tk.X, side=tk.BOTTOM, pady=8, padx=10)
        ttk.Button(button_frame, text="Save", command=self._on_save).pack(
            side=tk.RIGHT, padx=5
        )
        ttk.Button(button_frame, text="Cancel", command=self._on_cancel).pack(
            side=tk.RIGHT, padx=5
        )

    def _create_connection_panel(self, parent_frame):
        """Create the per-connection-type panel (SFP/TFTP/Serial).

        Inputs:
            - parent_frame: ttk.Frame to populate
        Returns:
            - dict of Tk variable objects used by the panel widgets
        """
        vars = {}

        # Top row: label + combobox to choose connection type
        type_row = ttk.Frame(parent_frame)
        type_row.pack(fill=tk.X, padx=5, pady=(5, 10))

        vars["conn_type"] = tk.StringVar()
        ttk.Label(type_row, text="Type:").pack(side=tk.LEFT)
        type_combo = ttk.Combobox(
            type_row,
            textvariable=vars["conn_type"],
            state="readonly",
            values=["SFP", "TFTP", "Serial"],
            width=10,
        )
        type_combo.pack(side=tk.LEFT, padx=6)

        # Notebook with a tab per connection type
        notebook = ttk.Notebook(parent_frame)
        notebook.pack(fill=tk.X, expand=False, padx=5, pady=2)
        vars["notebook"] = notebook

        # Frames for each tab
        sfp_frame = ttk.Frame(notebook, padding=5)
        tftp_frame = ttk.Frame(notebook, padding=5)
        serial_frame = ttk.Frame(notebook, padding=5)

        notebook.add(sfp_frame, text="SFP")
        notebook.add(tftp_frame, text="TFTP")
        notebook.add(serial_frame, text="Serial")

        def _on_tab_changed(event):
            idx = notebook.index(notebook.select())
            vars["conn_type"].set({0: "SFP", 1: "TFTP", 2: "Serial"}.get(idx, "SFP"))

        def _on_type_changed(event=None):
            name = vars["conn_type"].get()
            idx = {"SFP": 0, "TFTP": 1, "Serial": 2}.get(name, 0)
            notebook.select(idx)

        notebook.bind("<<NotebookTabChanged>>", _on_tab_changed)
        type_combo.bind("<<ComboboxSelected>>", _on_type_changed)

        # --- TFTP Widgets ---
        tftp_grid = tftp_frame
        tftp_grid.columnconfigure(1, weight=1)
        ttk.Label(tftp_grid, text="IP Address:").grid(
            row=0, column=0, sticky=tk.W, pady=2
        )
        vars["tftp_ip"] = tk.StringVar()
        ttk.Entry(tftp_grid, textvariable=vars["tftp_ip"]).grid(
            row=0, column=1, sticky=tk.EW, padx=5
        )
        ttk.Label(tftp_grid, text="Port:").grid(row=1, column=0, sticky=tk.W, pady=2)
        vars["tftp_port"] = tk.IntVar()
        ttk.Spinbox(
            tftp_grid, from_=1, to=65535, textvariable=vars["tftp_port"], width=7
        ).grid(row=1, column=1, sticky=tk.W, padx=5)

        # --- Serial Widgets ---
        serial_grid = serial_frame
        serial_grid.columnconfigure(1, weight=1)
        ttk.Label(serial_grid, text="COM Port:").grid(
            row=0, column=0, sticky=tk.W, pady=2
        )
        vars["serial_port"] = tk.StringVar()
        ttk.Combobox(
            serial_grid,
            textvariable=vars["serial_port"],
            values=[f"COM{i}" for i in range(1, 17)],
        ).grid(row=0, column=1, sticky=tk.EW, padx=5)
        ttk.Label(serial_grid, text="Baud Rate:").grid(
            row=1, column=0, sticky=tk.W, pady=2
        )
        vars["serial_baud"] = tk.IntVar()
        ttk.Combobox(
            serial_grid,
            textvariable=vars["serial_baud"],
            values=[9600, 19200, 38400, 57600, 115200],
            width=7,
        ).grid(row=1, column=1, sticky=tk.W, padx=5)

        # --- SFP Widgets ---
        sfp_grid = sfp_frame
        sfp_grid.columnconfigure(1, weight=1)
        ttk.Label(sfp_grid, text="Server IP:").grid(
            row=0, column=0, sticky=tk.W, pady=2
        )
        vars["sfp_ip"] = tk.StringVar()
        ttk.Entry(sfp_grid, textvariable=vars["sfp_ip"]).grid(
            row=0, column=1, sticky=tk.EW, padx=5
        )
        ttk.Label(sfp_grid, text="Server Port:").grid(
            row=1, column=0, sticky=tk.W, pady=2
        )
        vars["sfp_port"] = tk.IntVar()
        ttk.Spinbox(
            sfp_grid, from_=1, to=65535, textvariable=vars["sfp_port"], width=7
        ).grid(row=1, column=1, sticky=tk.W, padx=5)
        ttk.Label(sfp_grid, text="Local Port:").grid(
            row=2, column=0, sticky=tk.W, pady=2
        )
        vars["sfp_local_port"] = tk.IntVar()
        ttk.Spinbox(
            sfp_grid, from_=1, to=65535, textvariable=vars["sfp_local_port"], width=7
        ).grid(row=2, column=1, sticky=tk.W, padx=5)

        # Prediction Offset widget on row 3
        ttk.Label(sfp_grid, text="Prediction Offset (ms):").grid(
            row=3, column=0, sticky=tk.W, pady=2
        )
        vars["sfp_prediction_offset"] = tk.DoubleVar()
        ttk.Spinbox(
            sfp_grid,
            from_=0,
            to=1000,
            increment=1,
            textvariable=vars["sfp_prediction_offset"],
            width=7,
        ).grid(row=3, column=1, sticky=tk.W, padx=5)

        # JSON protocol checkbox moved to row 4
        vars["sfp_use_json"] = tk.BooleanVar()
        ttk.Checkbutton(
            sfp_grid, text="Use JSON Protocol", variable=vars["sfp_use_json"]
        ).grid(row=4, column=0, columnspan=2, sticky=tk.W, pady=(5, 0))

        # place test button to the right of the notebook area
        test_row = ttk.Frame(parent_frame)
        test_row.pack(fill=tk.X, padx=5, pady=(2, 8))
        test_button = ttk.Button(
            test_row,
            text="Test Connection",
            command=lambda: self._test_connection(vars),
        )
        test_button.pack(side=tk.RIGHT)

        return vars

    # Note: previous implementation used pack/grid switching per type. The UI
    # now uses a Notebook with tabs and a Combobox to select the active type.

    def _test_connection(self, vars):
        """Perform a quick connection test for the selected type.

        Inputs:
          - vars: dict of Tk variables for a connection panel
        Returns:
          - None, but shows a messagebox with success/failure.
        """
        conn_type = vars["conn_type"].get()
        result = False
        config = {}

        try:
            if conn_type == "TFTP":
                from target_simulator.core.tftp_communicator import TFTPCommunicator

                config = {"ip": vars["tftp_ip"].get(), "port": vars["tftp_port"].get()}
                result = TFTPCommunicator.test_connection(config)
            elif conn_type == "Serial":
                from target_simulator.core.serial_communicator import SerialCommunicator

                config = {
                    "port": vars["serial_port"].get(),
                    "baudrate": vars["serial_baud"].get(),
                }
                result = SerialCommunicator.test_connection(config)
            elif conn_type == "SFP":
                from target_simulator.core.sfp_communicator import SFPCommunicator

                config = {"local_port": vars["sfp_local_port"].get()}
                result = SFPCommunicator.test_connection(config)
        except Exception as e:
            messagebox.showerror("Test Error", f"An error occurred: {e}", parent=self)
            return

        if result:
            messagebox.showinfo(
                "Connection Test",
                f"{conn_type} connection test successful!",
                parent=self,
            )
        else:
            messagebox.showerror(
                "Connection Test", f"{conn_type} connection test failed.", parent=self
            )

    def _on_save(self):
        """Collect values from widgets and save them to master view.

        Builds the structured connection dict and calls
        master_view.update_connection_settings(new_config).
        """
        new_config = {
            "target": {
                "type": self.target_vars["conn_type"].get().lower(),
                "tftp": {
                    "ip": self.target_vars["tftp_ip"].get(),
                    "port": self.target_vars["tftp_port"].get(),
                },
                "serial": {
                    "port": self.target_vars["serial_port"].get(),
                    "baudrate": self.target_vars["serial_baud"].get(),
                },
                "sfp": {
                    "ip": self.target_vars["sfp_ip"].get(),
                    "port": self.target_vars["sfp_port"].get(),
                    "local_port": self.target_vars["sfp_local_port"].get(),
                    "use_json_protocol": self.target_vars["sfp_use_json"].get(),
                    "prediction_offset_ms": self.target_vars[
                        "sfp_prediction_offset"
                    ].get(),
                },
            },
            "lru": {
                "type": self.lru_vars["conn_type"].get().lower(),
                "tftp": {
                    "ip": self.lru_vars["tftp_ip"].get(),
                    "port": self.lru_vars["tftp_port"].get(),
                },
                "serial": {
                    "port": self.lru_vars["serial_port"].get(),
                    "baudrate": self.lru_vars["serial_baud"].get(),
                },
                "sfp": {
                    "ip": self.lru_vars["sfp_ip"].get(),
                    "port": self.lru_vars["sfp_port"].get(),
                    "local_port": self.lru_vars["sfp_local_port"].get(),
                    "use_json_protocol": self.lru_vars["sfp_use_json"].get(),
                    "prediction_offset_ms": self.lru_vars[
                        "sfp_prediction_offset"
                    ].get(),
                },
            },
        }
        self.master_view.update_connection_settings(new_config)
        self.destroy()

    def _on_cancel(self):
        """Close the dialog without saving changes."""
        self.destroy()
