"""GUI to exercise firmware flasher with target selection.

The GUI provides:
- Target selection (combobox) to choose FPGA target
- Configuration management dialog (global config, models, targets)
- Address input for flash operations
- Buttons to call erase, write, verify, and full sequence
- Log output window

Reference: qggrifobeamupform.cpp for UI structure.
"""
from __future__ import annotations

import os
import threading
import tkinter as tk
from tkinter import filedialog, messagebox, scrolledtext, ttk, simpledialog
from typing import Optional
from pathlib import Path

from pydownloadfwviasrio.core.core import FirmwareFlasher
from pydownloadfwviasrio.profiles import (
    GlobalConfig,
    FlashTarget, 
    FlashModel,
    ProfileManager,
    initialize_from_targets_ini
)
from pydownloadfwviasrio.tftp_client import SRIOTFTPClient


class FlasherGUI:
    def __init__(self, root: tk.Tk) -> None:
        self.root = root
        root.title("Firmware Flasher - SRIO over TFTP")

        # Will be initialized after log widget is created
        self.profile_manager: Optional[ProfileManager] = None
        self.current_target: Optional[FlashTarget] = None
        self.current_model: Optional[FlashModel] = None
        self.flasher: Optional[FirmwareFlasher] = None

        # Top frame: target selection
        target_frame = tk.Frame(root)
        target_frame.pack(padx=8, pady=8, fill=tk.X)

        tk.Label(target_frame, text="Target FPGA:").pack(side=tk.LEFT, padx=4)
        self.target_combo = ttk.Combobox(
            target_frame,
            values=[],  # Will be populated after initialization
            state="readonly",
            width=30,
        )
        self.target_combo.pack(side=tk.LEFT, padx=4)
        self.target_combo.bind("<<ComboboxSelected>>", self._on_target_selected)

        self.config_btn = tk.Button(target_frame, text="⚙️ Manage Configuration", command=self._manage_config)
        self.config_btn.pack(side=tk.LEFT, padx=4)

        # Target info panel
        info_frame = tk.LabelFrame(root, text="Target Information", padx=10, pady=10)
        info_frame.pack(padx=8, pady=(0, 8), fill=tk.X)

        self.info_label = tk.Label(
            info_frame,
            text="No target selected",
            justify=tk.LEFT,
            anchor=tk.W,
            font=("Courier", 9),
        )
        self.info_label.pack(fill=tk.X)

        # Control buttons
        control_frame = tk.Frame(root)
        control_frame.pack(padx=8, pady=8, fill=tk.X)

        self.erase_btn = tk.Button(control_frame, text="🗑️ Erase", command=self._erase, width=12)
        self.erase_btn.pack(side=tk.LEFT, padx=4)

        self.write_btn = tk.Button(control_frame, text="✍️ Write", command=self._write, width=12)
        self.write_btn.pack(side=tk.LEFT, padx=4)

        self.verify_btn = tk.Button(control_frame, text="🔍 Verify", command=self._verify, width=12)
        self.verify_btn.pack(side=tk.LEFT, padx=4)

        self.full_btn = tk.Button(control_frame, text="🔄 Full Sequence", command=self._full_sequence, width=18)
        self.full_btn.pack(side=tk.LEFT, padx=4)
        
        # Abort button (initially disabled, enabled during operations)
        self.abort_btn = tk.Button(
            control_frame,
            text="🛑 ABORT",
            command=self._abort_operation,
            width=12,
            bg="#FF6B6B",
            fg="white",
            font=('TkDefaultFont', 9, 'bold'),
            state=tk.DISABLED
        )
        self.abort_btn.pack(side=tk.LEFT, padx=12)
        
        # Verify after write checkbox (ben visibile con colore)
        self.verify_after_write_var = tk.BooleanVar(value=False)
        verify_check = tk.Checkbutton(
            control_frame,
            text="✓ Verify after each write",
            variable=self.verify_after_write_var,
            command=self._on_verify_checkbox_changed,
            font=("TkDefaultFont", 9, "bold"),
            fg="#0066CC",  # Blu per visibilità
        )
        verify_check.pack(side=tk.LEFT, padx=12)

        # Progress bar
        progress_frame = tk.Frame(root)
        progress_frame.pack(padx=8, pady=(0, 8), fill=tk.X)
        tk.Label(progress_frame, text="Progress:").pack(side=tk.LEFT, padx=4)
        self.progress = ttk.Progressbar(progress_frame, length=400, mode='determinate')
        self.progress.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=4)
        self.progress_label = tk.Label(progress_frame, text="0%", width=6)
        self.progress_label.pack(side=tk.LEFT, padx=4)

        # Log windows frame (stacked horizontally)
        logs_frame = tk.Frame(root)
        logs_frame.pack(padx=8, pady=(0, 8), fill=tk.BOTH, expand=True)
        
        # General log (top)
        general_log_frame = tk.LabelFrame(logs_frame, text="General Log", padx=5, pady=5)
        general_log_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True, pady=(0, 4))
        self.log_widget = scrolledtext.ScrolledText(general_log_frame, width=80, height=12)
        self.log_widget.pack(fill=tk.BOTH, expand=True)
        
        # TFTP commands log (bottom)
        tftp_log_frame = tk.LabelFrame(logs_frame, text="TFTP Commands", padx=5, pady=5)
        tftp_log_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True, pady=(4, 0))
        self.tftp_log_widget = scrolledtext.ScrolledText(tftp_log_frame, width=80, height=12, font=("Courier", 8))
        self.tftp_log_widget.pack(fill=tk.BOTH, expand=True)

        # Initialize configuration after log widget exists
        self._initialize_config()

    def _initialize_config(self) -> None:
        """Initialize configuration manager - called after log widget is created."""
        self.profile_manager = ProfileManager()
        
        # If empty, try to initialize from targets.ini
        if not self.profile_manager.targets:
            targets_ini = Path("_OLD/Vecchia_app/FpgaBeamMeUp/targets.ini")
            if targets_ini.exists():
                self._append_log(f"Loading configuration from {targets_ini}...")
                self.profile_manager.load_from_ini(targets_ini)
                self.profile_manager.save()
                self._append_log(f"Loaded {len(self.profile_manager.targets)} targets, {len(self.profile_manager.models)} models")
            else:
                self._append_log("WARNING: No configuration found!")
        
        # Update combobox with target names
        self.target_combo['values'] = self.profile_manager.list_targets()
        
        # Load verify_after_write setting from config
        self.verify_after_write_var.set(self.profile_manager.global_config.verify_after_write)
        
        # Select default target if available
        if self.profile_manager.global_config.default_target in self.profile_manager.targets:
            self.target_combo.set(self.profile_manager.global_config.default_target)
            self._on_target_selected(None)
        elif self.profile_manager.targets:
            self.target_combo.current(0)
            self._on_target_selected(None)

    def _append_log(self, message: str) -> None:
        """Append message to general log."""
        self.log_widget.insert(tk.END, message + "\n")
        self.log_widget.see(tk.END)
    
    def _append_tftp_log(self, message: str) -> None:
        """Append message to TFTP commands log."""
        self.tftp_log_widget.insert(tk.END, message + "\n")
        self.tftp_log_widget.see(tk.END)
    
    def _on_verify_checkbox_changed(self) -> None:
        """Save verify_after_write setting when checkbox changes."""
        new_value = self.verify_after_write_var.get()
        self.profile_manager.global_config.verify_after_write = new_value
        self.profile_manager.save()
        status = "enabled" if new_value else "disabled"
        self._append_log(f"Chunk verification after write: {status}")
    
    def _disable_controls(self) -> None:
        """Disable all operation buttons and enable abort button."""
        self.erase_btn.config(state=tk.DISABLED)
        self.write_btn.config(state=tk.DISABLED)
        self.verify_btn.config(state=tk.DISABLED)
        self.full_btn.config(state=tk.DISABLED)
        self.config_btn.config(state=tk.DISABLED)
        self.target_combo.config(state=tk.DISABLED)
        self.abort_btn.config(state=tk.NORMAL)
        self.operation_running = True
        self.abort_requested = False
    
    def _enable_controls(self) -> None:
        """Enable all operation buttons and disable abort button."""
        self.erase_btn.config(state=tk.NORMAL)
        self.write_btn.config(state=tk.NORMAL)
        self.verify_btn.config(state=tk.NORMAL)
        self.full_btn.config(state=tk.NORMAL)
        self.config_btn.config(state=tk.NORMAL)
        self.target_combo.config(state='readonly')
        self.abort_btn.config(state=tk.DISABLED)
        self.operation_running = False
        self.abort_requested = False
    
    def _abort_operation(self) -> None:
        """Request abort of current operation."""
        self.abort_requested = True
        self._append_log("⚠️ ABORT requested - stopping operation...")
        self.abort_btn.config(state=tk.DISABLED, text="🛑 Aborting...")
    
    def _check_abort(self) -> bool:
        """Check if abort was requested.
        
        Returns:
            True if abort was requested, False otherwise.
        """
        if self.abort_requested:
            self._append_log("❌ Operation aborted by user")
            return True
        return False

    def _update_progress(self, current: int, total: int) -> None:
        """Update progress bar and percentage label."""
        if total > 0:
            percentage = int((current / total) * 100)
            self.progress['value'] = percentage
            self.progress_label.config(text=f"{percentage}%")
            self.root.update_idletasks()

    def _on_target_selected(self, event) -> None:
        """Handle target selection change."""
        selected_name = self.target_combo.get()
        result = self.profile_manager.get_target_with_model(selected_name)
        
        if result:
            self.current_target, self.current_model = result
            
            # Update info panel
            golden_path = self.current_target.golden_binary_path or "(not configured)"
            user_path = self.current_target.user_binary_path or "(not configured)"
            
            info_text = f"""Target: {self.current_target.id_target} ({self.current_target.description})
IP:Port         : {self.profile_manager.global_config.ip}:{self.profile_manager.global_config.port}
SRIO Slot       : 0x{self.current_target.slot_address:02X}
Architecture    : {self.current_target.architecture}
Model           : {self.current_model.model} ({self.current_model.description})
Flash Type      : {self.current_model.flash_type}
Addressing      : {'4-byte' if self.current_model.is_4byte_addressing else '3-byte'}
Sectors         : {self.current_model.num_sectors}
Golden Area     : 0x{self.current_model.golden_start:08X} - 0x{self.current_model.golden_stop:08X}
User Area       : 0x{self.current_model.user_start:08X} - 0x{self.current_model.user_stop:08X}
Golden Binary   : {golden_path}
User Binary     : {user_path}"""
            
            self.info_label.config(text=info_text)

            # Log selection
            self._append_log(f"Target selected: {self.current_target.id_target}")
            self._append_log(f"  Model: {self.current_model.model}, Slot: 0x{self.current_target.slot_address:02X}")
            self._append_log(f"  User area: 0x{self.current_model.user_start:08X} - 0x{self.current_model.user_stop:08X}")
            
            if self.current_target.golden_binary_path:
                self._append_log(f"  Golden binary: {self.current_target.golden_binary_path}")
            if self.current_target.user_binary_path:
                self._append_log(f"  User binary: {self.current_target.user_binary_path}")
            
            # Create client and flasher (using a dummy FlashProfile for compatibility)
            # TODO: Update FirmwareFlasher to work directly with target+model
            from pydownloadfwviasrio.profiles import FlashProfile
            temp_profile = FlashProfile(
                name=self.current_target.id_target,
                slot_address=f"0x{self.current_target.slot_address:02X}",
                ip=self.profile_manager.global_config.ip,
                port=self.profile_manager.global_config.port,
                base_address=self.current_model.user_start,
                size=(self.current_model.user_stop - self.current_model.user_start + 1),
                binary_path=None,  # Will be selected during operation
            )
            
            client = SRIOTFTPClient(
                self.profile_manager.global_config.ip,
                self.profile_manager.global_config.port,
                max_retries=self.profile_manager.global_config.max_tftp_retries,
            )
            self.flasher = FirmwareFlasher(
                profile=temp_profile,
                client=client,
                max_register_retries=self.profile_manager.global_config.max_register_retries,
            )

    def _ask_memory_area(self) -> Optional[tuple[str, int, Optional[str]]]:
        """Ask user to select memory area (golden or user).
        
        Returns:
            Tuple of (area_name, address, binary_path) or None if cancelled.
        """
        if not self.current_model:
            return None
        
        # Create dialog
        dialog = tk.Toplevel(self.root)
        dialog.title("Select Memory Area")
        dialog.geometry("400x200")
        dialog.transient(self.root)
        dialog.grab_set()
        
        result = {'area': None}
        
        # Info frame
        info_frame = tk.LabelFrame(dialog, text="Available Memory Areas", padx=10, pady=10)
        info_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        
        golden_text = f"Golden Area: 0x{self.current_model.golden_start:08X} - 0x{self.current_model.golden_stop:08X}"
        user_text = f"User Area:   0x{self.current_model.user_start:08X} - 0x{self.current_model.user_stop:08X}"
        
        tk.Label(info_frame, text=golden_text, font=("Courier", 9)).pack(anchor=tk.W)
        tk.Label(info_frame, text=user_text, font=("Courier", 9)).pack(anchor=tk.W)
        
        # Buttons
        btn_frame = tk.Frame(dialog)
        btn_frame.pack(pady=10)
        
        def select_golden():
            result['area'] = 'golden'
            dialog.destroy()
        
        def select_user():
            result['area'] = 'user'
            dialog.destroy()
        
        tk.Button(btn_frame, text="Golden Area", command=select_golden, width=15).pack(side=tk.LEFT, padx=5)
        tk.Button(btn_frame, text="User Area", command=select_user, width=15).pack(side=tk.LEFT, padx=5)
        tk.Button(btn_frame, text="Cancel", command=dialog.destroy, width=15).pack(side=tk.LEFT, padx=5)
        
        # Center dialog
        dialog.wait_window()
        
        if result['area'] == 'golden':
            binary_path = self.current_target.golden_binary_path if self.current_target else None
            return ('golden', self.current_model.golden_start, binary_path)
        elif result['area'] == 'user':
            binary_path = self.current_target.user_binary_path if self.current_target else None
            return ('user', self.current_model.user_start, binary_path)
        else:
            return None

    def _run_in_thread(self, func) -> None:
        """Run a function in a background thread to keep GUI responsive."""
        import threading
        
        def wrapper():
            try:
                self._disable_controls()
                func()
            except Exception as e:
                self._append_log(f"❌ Exception: {e}")
            finally:
                # Always re-enable controls
                self.root.after(0, self._enable_controls)
        
        threading.Thread(target=wrapper, daemon=True).start()

    def _erase(self) -> None:
        def job() -> None:
            if not self.flasher:
                self._append_log("ERROR: No profile selected")
                return
            
            # Ask user which area to erase
            area_info = self._ask_memory_area()
            if not area_info:
                self._append_log("Operation cancelled")
                return
            
            if self._check_abort():
                return
            
            area_name, address, _ = area_info
            self._update_progress(0, 100)
            self._append_log(f"Erasing {area_name} area at 0x{address:08X}")
            self.flasher.erase(address, 65536, log=self._append_log, tftp_log=self._append_tftp_log, progress_callback=self._update_progress, abort_check=self._check_abort)
            
            if self._check_abort():
                return
            
            self._update_progress(100, 100)
            self._append_log(f"✅ Erase completed")

        self._run_in_thread(job)

    def _write(self) -> None:
        def job() -> None:
            if not self.flasher:
                self._append_log("ERROR: No target selected")
                return
            
            # Ask user which area to write
            area_info = self._ask_memory_area()
            if not area_info:
                self._append_log("Operation cancelled")
                return
            
            if self._check_abort():
                return
            
            area_name, address, configured_binary = area_info
            
            # Use configured binary or ask user
            if configured_binary and os.path.exists(configured_binary):
                path = configured_binary
                self._append_log(f"Using configured {area_name} binary: {path}")
            else:
                if configured_binary:
                    self._append_log(f"WARNING: Configured binary not found: {configured_binary}")
                path = filedialog.askopenfilename(title=f"Select {area_name} binary to write")
                if not path:
                    self._append_log("Operation cancelled")
                    return
            
            if self._check_abort():
                return
            
            with open(path, "rb") as f:
                data = f.read()
            self._update_progress(0, 100)
            self._append_log(f"Writing {len(data)} bytes to {area_name} area at 0x{address:08X}")
            ok = self.flasher.write(
                address,
                data,
                log=self._append_log,
                tftp_log=self._append_tftp_log,
                progress_callback=self._update_progress,
                verify_after_write=self.verify_after_write_var.get(),
                max_chunk_write_retries=self.profile_manager.global_config.max_chunk_write_retries,
                abort_check=self._check_abort,
            )
            
            if self._check_abort():
                return
            
            self._update_progress(100, 100)
            if ok:
                self._append_log(f"✅ Write completed")
            else:
                self._append_log(f"❌ Write failed")

        self._run_in_thread(job)

    def _verify(self) -> None:
        def job() -> None:
            if not self.flasher:
                self._append_log("ERROR: No target selected")
                return
            
            # Ask user which area to verify
            area_info = self._ask_memory_area()
            if not area_info:
                self._append_log("Operation cancelled")
                return
            
            if self._check_abort():
                return
            
            area_name, address, configured_binary = area_info
            
            # Use configured binary or ask user
            if configured_binary and os.path.exists(configured_binary):
                path = configured_binary
                self._append_log(f"Using configured {area_name} binary: {path}")
            else:
                if configured_binary:
                    self._append_log(f"WARNING: Configured binary not found: {configured_binary}")
                path = filedialog.askopenfilename(title=f"Select {area_name} binary to verify")
                if not path:
                    self._append_log("Operation cancelled")
                    return
            
            if self._check_abort():
                return
            
            with open(path, "rb") as f:
                data = f.read()
            self._update_progress(0, 100)
            self._append_log(f"Verifying {len(data)} bytes in {area_name} area at 0x{address:08X}")
            ok = self.flasher.verify(address, data, log=self._append_log, tftp_log=self._append_tftp_log, progress_callback=self._update_progress, abort_check=self._check_abort)
            
            if self._check_abort():
                return
            
            self._update_progress(100, 100)
            if ok:
                self._append_log("✅ Verification successful!")
            else:
                self._append_log("❌ Verification FAILED!")

        self._run_in_thread(job)

    def _full_sequence(self) -> None:
        def job() -> None:
            if not self.flasher:
                self._append_log("ERROR: No target selected")
                return
            
            # Use target's binary_path if configured, otherwise ask user
            if self.current_target and self.current_target.binary_path:
                path = self.current_target.binary_path
                if not os.path.exists(path):
                    self._append_log(f"ERROR: Configured binary not found: {path}")
                    return
                self._append_log(f"Using configured binary: {path}")
            else:
                path = filedialog.askopenfilename(title="Select binary for full sequence")
                if not path:
                    return
            
            if self._check_abort():
                return
            
            with open(path, "rb") as f:
                data = f.read()
            address = self._get_address()
            
            # Erase
            self._append_log("=== 🗑️ Starting Erase ===")
            self._update_progress(0, 100)
            ok = self.flasher.erase(address, len(data), log=self._append_log, progress_callback=self._update_progress, abort_check=self._check_abort)
            if not ok:
                self._append_log("❌ ABORT: Erase failed")
                return
            
            if self._check_abort():
                return
            
            # Write
            self._append_log("=== ✍️ Starting Write ===")
            self._update_progress(0, 100)
            ok = self.flasher.write(
                address,
                data,
                log=self._append_log,
                tftp_log=self._append_tftp_log,
                progress_callback=self._update_progress,
                verify_after_write=self.verify_after_write_var.get(),
                max_chunk_write_retries=self.profile_manager.global_config.max_chunk_write_retries,
                abort_check=self._check_abort,
            )
            if not ok:
                self._append_log("❌ ABORT: Write failed")
                return
            
            if self._check_abort():
                return
            
            # Verify
            self._append_log("=== 🔍 Starting Verify ===")
            self._update_progress(0, 100)
            ok = self.flasher.verify(address, data, log=self._append_log, progress_callback=self._update_progress, abort_check=self._check_abort)
            
            if self._check_abort():
                return
            
            self._update_progress(100, 100)
            if ok:
                self._append_log("🎉 SUCCESS: Full sequence completed")
            else:
                self._append_log("❌ FAILED: Verify mismatch")

        self._run_in_thread(job)

    def _manage_config(self) -> None:
        """Open configuration management dialog."""
        ConfigManagerDialog(self.root, self.profile_manager, self._refresh_targets)

    def _refresh_targets(self) -> None:
        """Refresh target list in combobox after changes."""
        current = self.target_combo.get()
        self.target_combo['values'] = self.profile_manager.list_targets()
        # Try to keep current selection
        if current in self.profile_manager.list_targets():
            self.target_combo.set(current)
        elif self.profile_manager.targets:
            self.target_combo.current(0)
            self._on_target_selected(None)


class ConfigManagerDialog:
    """Dialog for managing global config, models, and targets."""

    def __init__(self, parent: tk.Tk, manager: ProfileManager, refresh_callback) -> None:
        self.manager = manager
        self.refresh_callback = refresh_callback

        self.dialog = tk.Toplevel(parent)
        self.dialog.title("Configuration Manager")
        self.dialog.geometry("900x600")

        # Create notebook (tabbed interface)
        notebook = ttk.Notebook(self.dialog)
        notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        # Tab 1: Global Configuration
        global_tab = tk.Frame(notebook)
        notebook.add(global_tab, text="Global Config")
        self._create_global_tab(global_tab)

        # Tab 2: Flash Models
        models_tab = tk.Frame(notebook)
        notebook.add(models_tab, text="Flash Models")
        self._create_models_tab(models_tab)

        # Tab 3: Targets
        targets_tab = tk.Frame(notebook)
        notebook.add(targets_tab, text="Targets")
        self._create_targets_tab(targets_tab)

        # Bottom buttons
        btn_frame = tk.Frame(self.dialog)
        btn_frame.pack(fill=tk.X, padx=10, pady=(0, 10))
        
        tk.Button(btn_frame, text="Save & Close", command=self._save_and_close, width=15).pack(side=tk.RIGHT, padx=5)
        tk.Button(btn_frame, text="Close", command=self.dialog.destroy, width=15).pack(side=tk.RIGHT, padx=5)

    def _create_global_tab(self, parent: tk.Frame) -> None:
        """Create global configuration tab."""
        form_frame = tk.LabelFrame(parent, text="Connection Settings", padx=20, pady=20)
        form_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        row = 0
        
        # IP Address
        tk.Label(form_frame, text="IP Address:").grid(row=row, column=0, sticky=tk.W, pady=5)
        self.global_ip_entry = tk.Entry(form_frame, width=40)
        self.global_ip_entry.grid(row=row, column=1, pady=5)
        self.global_ip_entry.insert(0, self.manager.global_config.ip)
        row += 1

        # Port
        tk.Label(form_frame, text="Port:").grid(row=row, column=0, sticky=tk.W, pady=5)
        self.global_port_entry = tk.Entry(form_frame, width=40)
        self.global_port_entry.grid(row=row, column=1, pady=5)
        self.global_port_entry.insert(0, str(self.manager.global_config.port))
        row += 1

        # SRIO Base
        tk.Label(form_frame, text="SRIO Base (hex):").grid(row=row, column=0, sticky=tk.W, pady=5)
        self.global_srio_entry = tk.Entry(form_frame, width=40)
        self.global_srio_entry.grid(row=row, column=1, pady=5)
        self.global_srio_entry.insert(0, f"0x{self.manager.global_config.srio_base:X}")
        row += 1

        # FPGA Base
        tk.Label(form_frame, text="FPGA Base (hex):").grid(row=row, column=0, sticky=tk.W, pady=5)
        self.global_fpga_entry = tk.Entry(form_frame, width=40)
        self.global_fpga_entry.grid(row=row, column=1, pady=5)
        self.global_fpga_entry.insert(0, f"0x{self.manager.global_config.fpga_base:X}")
        row += 1

        # Sector Size
        tk.Label(form_frame, text="Sector Size (hex):").grid(row=row, column=0, sticky=tk.W, pady=5)
        self.global_sector_entry = tk.Entry(form_frame, width=40)
        self.global_sector_entry.grid(row=row, column=1, pady=5)
        self.global_sector_entry.insert(0, f"0x{self.manager.global_config.fpga_sector:X}")
        row += 1

        # Default Target
        tk.Label(form_frame, text="Default Target:").grid(row=row, column=0, sticky=tk.W, pady=5)
        self.global_default_combo = ttk.Combobox(form_frame, width=37, state="readonly")
        self.global_default_combo.grid(row=row, column=1, pady=5)
        self.global_default_combo['values'] = list(self.manager.targets.keys())
        if self.manager.global_config.default_target in self.manager.targets:
            self.global_default_combo.set(self.manager.global_config.default_target)
        row += 1

        # === Retry Configuration Section ===
        tk.Label(form_frame, text="━━━━━ Retry Configuration ━━━━━", font=("Arial", 9, "bold")).grid(row=row, column=0, columnspan=2, pady=(15, 5))
        row += 1

        # TFTP Retries
        tk.Label(form_frame, text="TFTP Retries:").grid(row=row, column=0, sticky=tk.W, pady=5)
        self.global_tftp_retries_entry = tk.Entry(form_frame, width=40)
        self.global_tftp_retries_entry.grid(row=row, column=1, pady=5)
        self.global_tftp_retries_entry.insert(0, str(self.manager.global_config.max_tftp_retries))
        row += 1

        # Register Retries
        tk.Label(form_frame, text="Register Retries:").grid(row=row, column=0, sticky=tk.W, pady=5)
        self.global_register_retries_entry = tk.Entry(form_frame, width=40)
        self.global_register_retries_entry.grid(row=row, column=1, pady=5)
        self.global_register_retries_entry.insert(0, str(self.manager.global_config.max_register_retries))
        row += 1

        # Chunk Write Retries
        tk.Label(form_frame, text="Chunk Write Retries:").grid(row=row, column=0, sticky=tk.W, pady=5)
        self.global_chunk_retries_entry = tk.Entry(form_frame, width=40)
        self.global_chunk_retries_entry.grid(row=row, column=1, pady=5)
        self.global_chunk_retries_entry.insert(0, str(self.manager.global_config.max_chunk_write_retries))
        row += 1

        tk.Button(form_frame, text="Apply Global Settings", command=self._apply_global).grid(row=row, column=1, pady=20)

    def _create_models_tab(self, parent: tk.Frame) -> None:
        """Create flash models management tab."""
        # Left side: model list
        list_frame = tk.Frame(parent)
        list_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=10, pady=10)

        tk.Label(list_frame, text="Flash Models:", font=("Arial", 10, "bold")).pack(anchor=tk.W)

        self.models_listbox = tk.Listbox(list_frame, height=20)
        self.models_listbox.pack(fill=tk.BOTH, expand=True, pady=(5, 0))
        self.models_listbox.bind("<<ListboxSelect>>", self._on_model_select)

        # Buttons
        btn_frame = tk.Frame(list_frame)
        btn_frame.pack(fill=tk.X, pady=(5, 0))
        tk.Button(btn_frame, text="New Model", command=self._new_model).pack(side=tk.LEFT, padx=2)
        tk.Button(btn_frame, text="Delete Model", command=self._delete_model).pack(side=tk.LEFT, padx=2)

        # Right side: model editor/details
        self.model_editor_frame = tk.LabelFrame(parent, text="Model Details", padx=10, pady=10)
        self.model_editor_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=(0, 10), pady=10)

        self._refresh_models_list()
        self._show_model_editor(None)  # Start with empty editor

    def _create_targets_tab(self, parent: tk.Frame) -> None:
        """Create targets management tab."""
        # Left side: target list
        list_frame = tk.Frame(parent)
        list_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=10, pady=10)

        tk.Label(list_frame, text="FPGA Targets:", font=("Arial", 10, "bold")).pack(anchor=tk.W)

        self.targets_listbox = tk.Listbox(list_frame, height=20)
        self.targets_listbox.pack(fill=tk.BOTH, expand=True, pady=(5, 0))
        self.targets_listbox.bind("<<ListboxSelect>>", self._on_target_select)

        # Buttons
        btn_frame = tk.Frame(list_frame)
        btn_frame.pack(fill=tk.X, pady=(5, 0))
        tk.Button(btn_frame, text="New Target", command=self._new_target).pack(side=tk.LEFT, padx=2)
        tk.Button(btn_frame, text="Delete Target", command=self._delete_target).pack(side=tk.LEFT, padx=2)

        # Right side: target editor/details
        self.target_editor_frame = tk.LabelFrame(parent, text="Target Details", padx=10, pady=10)
        self.target_editor_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=(0, 10), pady=10)

        self._refresh_targets_list()
        self._show_target_editor(None)  # Start with empty editor

    # Global Config Methods
    def _apply_global(self) -> None:
        """Apply global configuration changes."""
        try:
            self.manager.global_config.ip = self.global_ip_entry.get().strip()
            self.manager.global_config.port = int(self.global_port_entry.get().strip())
            self.manager.global_config.srio_base = int(self.global_srio_entry.get().strip(), 16)
            self.manager.global_config.fpga_base = int(self.global_fpga_entry.get().strip(), 16)
            self.manager.global_config.fpga_sector = int(self.global_sector_entry.get().strip(), 16)
            self.manager.global_config.default_target = self.global_default_combo.get()
            
            # Retry configuration
            self.manager.global_config.max_tftp_retries = int(self.global_tftp_retries_entry.get().strip())
            self.manager.global_config.max_register_retries = int(self.global_register_retries_entry.get().strip())
            self.manager.global_config.max_chunk_write_retries = int(self.global_chunk_retries_entry.get().strip())
            
            messagebox.showinfo("Success", "Global settings applied successfully", parent=self.dialog)
        except ValueError as e:
            messagebox.showerror("Error", f"Invalid input: {e}", parent=self.dialog)

    # Models Methods
    def _refresh_models_list(self) -> None:
        """Refresh models listbox."""
        self.models_listbox.delete(0, tk.END)
        for id_model, model in sorted(self.manager.models.items()):
            self.models_listbox.insert(tk.END, f"[{id_model}] {model.model} - {model.description}")

    def _on_model_select(self, event) -> None:
        """Display selected model in editor."""
        selection = self.models_listbox.curselection()
        if not selection:
            return

        models_list = sorted(self.manager.models.items())
        id_model, model = models_list[selection[0]]
        self._show_model_editor(model)

    def _show_model_editor(self, model: Optional[FlashModel]) -> None:
        """Show model editor form."""
        # Clear frame
        for widget in self.model_editor_frame.winfo_children():
            widget.destroy()

        # Create form
        form = tk.Frame(self.model_editor_frame)
        form.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

        row = 0

        # Model ID
        tk.Label(form, text="Model ID:").grid(row=row, column=0, sticky=tk.W, pady=3)
        self.model_id_entry = tk.Entry(form, width=35)
        self.model_id_entry.grid(row=row, column=1, pady=3, sticky=tk.W)
        if model:
            self.model_id_entry.insert(0, str(model.id_model))
            self.model_id_entry.config(state='readonly')
        row += 1

        # Model Name
        tk.Label(form, text="Model Name:").grid(row=row, column=0, sticky=tk.W, pady=3)
        self.model_name_entry = tk.Entry(form, width=35)
        self.model_name_entry.grid(row=row, column=1, pady=3, sticky=tk.W)
        if model:
            self.model_name_entry.insert(0, model.model)
        row += 1

        # Description
        tk.Label(form, text="Description:").grid(row=row, column=0, sticky=tk.W, pady=3)
        self.model_desc_entry = tk.Entry(form, width=35)
        self.model_desc_entry.grid(row=row, column=1, pady=3, sticky=tk.W)
        if model:
            self.model_desc_entry.insert(0, model.description)
        row += 1

        # Flash Type
        tk.Label(form, text="Flash Type:").grid(row=row, column=0, sticky=tk.W, pady=3)
        self.model_type_entry = tk.Entry(form, width=35)
        self.model_type_entry.grid(row=row, column=1, pady=3, sticky=tk.W)
        if model:
            self.model_type_entry.insert(0, str(model.flash_type))
        row += 1

        # 4-byte Addressing
        tk.Label(form, text="4-byte Addressing:").grid(row=row, column=0, sticky=tk.W, pady=3)
        self.model_addr4_var = tk.BooleanVar(value=model.is_4byte_addressing if model else False)
        tk.Checkbutton(form, variable=self.model_addr4_var).grid(row=row, column=1, sticky=tk.W, pady=3)
        row += 1

        # Num Sectors
        tk.Label(form, text="Sectors:").grid(row=row, column=0, sticky=tk.W, pady=3)
        self.model_sectors_entry = tk.Entry(form, width=35)
        self.model_sectors_entry.grid(row=row, column=1, pady=3, sticky=tk.W)
        if model:
            self.model_sectors_entry.insert(0, str(model.num_sectors))
        row += 1

        # Golden Start
        tk.Label(form, text="Golden Start (hex):").grid(row=row, column=0, sticky=tk.W, pady=3)
        self.model_gold_start_entry = tk.Entry(form, width=35)
        self.model_gold_start_entry.grid(row=row, column=1, pady=3, sticky=tk.W)
        if model:
            self.model_gold_start_entry.insert(0, f"0x{model.golden_start:08X}")
        row += 1

        # Golden Stop
        tk.Label(form, text="Golden Stop (hex):").grid(row=row, column=0, sticky=tk.W, pady=3)
        self.model_gold_stop_entry = tk.Entry(form, width=35)
        self.model_gold_stop_entry.grid(row=row, column=1, pady=3, sticky=tk.W)
        if model:
            self.model_gold_stop_entry.insert(0, f"0x{model.golden_stop:08X}")
        row += 1

        # User Start
        tk.Label(form, text="User Start (hex):").grid(row=row, column=0, sticky=tk.W, pady=3)
        self.model_user_start_entry = tk.Entry(form, width=35)
        self.model_user_start_entry.grid(row=row, column=1, pady=3, sticky=tk.W)
        if model:
            self.model_user_start_entry.insert(0, f"0x{model.user_start:08X}")
        row += 1

        # User Stop
        tk.Label(form, text="User Stop (hex):").grid(row=row, column=0, sticky=tk.W, pady=3)
        self.model_user_stop_entry = tk.Entry(form, width=35)
        self.model_user_stop_entry.grid(row=row, column=1, pady=3, sticky=tk.W)
        if model:
            self.model_user_stop_entry.insert(0, f"0x{model.user_stop:08X}")
        row += 1

        # Test Address
        tk.Label(form, text="Test Address (hex, opt):").grid(row=row, column=0, sticky=tk.W, pady=3)
        self.model_test_addr_entry = tk.Entry(form, width=35)
        self.model_test_addr_entry.grid(row=row, column=1, pady=3, sticky=tk.W)
        if model and model.test_address:
            self.model_test_addr_entry.insert(0, f"0x{model.test_address:08X}")
        row += 1

        # Buttons
        btn_frame = tk.Frame(form)
        btn_frame.grid(row=row, column=0, columnspan=2, pady=15)
        
        tk.Button(btn_frame, text="Save Model", command=lambda: self._save_model(model), width=12).pack(side=tk.LEFT, padx=5)
        tk.Button(btn_frame, text="Clear", command=lambda: self._show_model_editor(None), width=12).pack(side=tk.LEFT, padx=5)

    def _new_model(self) -> None:
        """Prepare editor for new model."""
        self.models_listbox.selection_clear(0, tk.END)
        self._show_model_editor(None)

    def _save_model(self, original_model: Optional[FlashModel]) -> None:
        """Save model from editor."""
        try:
            if original_model:
                id_model = original_model.id_model
            else:
                id_model = int(self.model_id_entry.get().strip())
                if id_model in self.manager.models:
                    messagebox.showerror("Error", f"Model ID {id_model} already exists.", parent=self.dialog)
                    return

            test_addr_str = self.model_test_addr_entry.get().strip()
            test_addr = int(test_addr_str, 16) if test_addr_str else None

            new_model = FlashModel(
                id_model=id_model,
                model=self.model_name_entry.get().strip(),
                description=self.model_desc_entry.get().strip(),
                flash_type=int(self.model_type_entry.get().strip()),
                is_4byte_addressing=self.model_addr4_var.get(),
                num_sectors=int(self.model_sectors_entry.get().strip()),
                golden_start=int(self.model_gold_start_entry.get().strip(), 16),
                golden_stop=int(self.model_gold_stop_entry.get().strip(), 16),
                user_start=int(self.model_user_start_entry.get().strip(), 16),
                user_stop=int(self.model_user_stop_entry.get().strip(), 16),
                test_address=test_addr,
            )

            self.manager.add_model(new_model)
            self._refresh_models_list()
            self._show_model_editor(new_model)
            
            # Select the saved model in list
            for idx, (id_m, m) in enumerate(sorted(self.manager.models.items())):
                if id_m == id_model:
                    self.models_listbox.selection_clear(0, tk.END)
                    self.models_listbox.selection_set(idx)
                    self.models_listbox.see(idx)
                    break
            
            messagebox.showinfo("Success", f"Model '{new_model.model}' saved successfully", parent=self.dialog)

        except ValueError as e:
            messagebox.showerror("Error", f"Invalid input: {e}", parent=self.dialog)

    def _delete_model(self) -> None:
        """Delete selected model."""
        selection = self.models_listbox.curselection()
        if not selection:
            messagebox.showwarning("No Selection", "Please select a model to delete.", parent=self.dialog)
            return

        models_list = sorted(self.manager.models.items())
        id_model, model = models_list[selection[0]]
        
        # Check if model is in use
        in_use = [t.id_target for t in self.manager.targets.values() if t.id_model == id_model]
        if in_use:
            messagebox.showerror("Cannot Delete", f"Model is in use by targets: {', '.join(in_use)}", parent=self.dialog)
            return

        if messagebox.askyesno("Confirm Delete", f"Delete model '{model.model}'?", parent=self.dialog):
            self.manager.delete_model(id_model)
            self._refresh_models_list()
            self._show_model_editor(None)

    # Targets Methods
    def _refresh_targets_list(self) -> None:
        """Refresh targets listbox."""
        self.targets_listbox.delete(0, tk.END)
        for id_target, target in sorted(self.manager.targets.items()):
            self.targets_listbox.insert(tk.END, f"{target.id_target} - {target.description}")

    def _on_target_select(self, event) -> None:
        """Display selected target in editor."""
        selection = self.targets_listbox.curselection()
        if not selection:
            return

        targets_list = sorted(self.manager.targets.items())
        id_target, target = targets_list[selection[0]]
        self._show_target_editor(target)

    def _show_target_editor(self, target: Optional[FlashTarget]) -> None:
        """Show target editor form."""
        # Clear frame
        for widget in self.target_editor_frame.winfo_children():
            widget.destroy()

        # Create form
        form = tk.Frame(self.target_editor_frame)
        form.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

        row = 0

        # Target ID
        tk.Label(form, text="Target ID:").grid(row=row, column=0, sticky=tk.W, pady=3)
        self.target_id_entry = tk.Entry(form, width=35)
        self.target_id_entry.grid(row=row, column=1, pady=3, sticky=tk.W)
        if target:
            self.target_id_entry.insert(0, target.id_target)
            self.target_id_entry.config(state='readonly')
        row += 1

        # Description
        tk.Label(form, text="Description:").grid(row=row, column=0, sticky=tk.W, pady=3)
        self.target_desc_entry = tk.Entry(form, width=35)
        self.target_desc_entry.grid(row=row, column=1, pady=3, sticky=tk.W)
        if target:
            self.target_desc_entry.insert(0, target.description)
        row += 1

        # Slot Address
        tk.Label(form, text="Slot Address (hex):").grid(row=row, column=0, sticky=tk.W, pady=3)
        self.target_slot_entry = tk.Entry(form, width=35)
        self.target_slot_entry.grid(row=row, column=1, pady=3, sticky=tk.W)
        if target:
            self.target_slot_entry.insert(0, f"0x{target.slot_address:02X}")
        row += 1

        # Architecture
        tk.Label(form, text="Architecture:").grid(row=row, column=0, sticky=tk.W, pady=3)
        self.target_arch_combo = ttk.Combobox(form, width=32, values=["Xilinx", "RFIF", "Other"])
        self.target_arch_combo.grid(row=row, column=1, pady=3, sticky=tk.W)
        if target:
            self.target_arch_combo.set(target.architecture)
        row += 1

        # Name
        tk.Label(form, text="Name:").grid(row=row, column=0, sticky=tk.W, pady=3)
        self.target_name_entry = tk.Entry(form, width=35)
        self.target_name_entry.grid(row=row, column=1, pady=3, sticky=tk.W)
        if target:
            self.target_name_entry.insert(0, target.name)
        row += 1

        # File Prefix
        tk.Label(form, text="File Prefix:").grid(row=row, column=0, sticky=tk.W, pady=3)
        self.target_prefix_entry = tk.Entry(form, width=35)
        self.target_prefix_entry.grid(row=row, column=1, pady=3, sticky=tk.W)
        if target:
            self.target_prefix_entry.insert(0, target.file_prefix)
        row += 1

        # Model Selection
        tk.Label(form, text="Flash Model:").grid(row=row, column=0, sticky=tk.W, pady=3)
        self.target_model_combo = ttk.Combobox(form, width=32, state="readonly")
        self.target_model_combo.grid(row=row, column=1, pady=3, sticky=tk.W)
        model_choices = [f"[{id_}] {m.model} - {m.description}" for id_, m in sorted(self.manager.models.items())]
        self.target_model_combo['values'] = model_choices
        if target:
            for idx, (id_, m) in enumerate(sorted(self.manager.models.items())):
                if id_ == target.id_model:
                    self.target_model_combo.current(idx)
                    break
        row += 1

        # Golden Binary Path
        tk.Label(form, text="Golden Binary (opt):").grid(row=row, column=0, sticky=tk.W, pady=3)
        golden_binary_frame = tk.Frame(form)
        golden_binary_frame.grid(row=row, column=1, pady=3, sticky=tk.W)
        self.target_golden_binary_entry = tk.Entry(golden_binary_frame, width=25)
        self.target_golden_binary_entry.pack(side=tk.LEFT)
        tk.Button(golden_binary_frame, text="Browse", command=lambda: self._browse_target_binary('golden')).pack(side=tk.LEFT, padx=5)
        if target and target.golden_binary_path:
            self.target_golden_binary_entry.insert(0, target.golden_binary_path)
        row += 1

        # User Binary Path
        tk.Label(form, text="User Binary (opt):").grid(row=row, column=0, sticky=tk.W, pady=3)
        user_binary_frame = tk.Frame(form)
        user_binary_frame.grid(row=row, column=1, pady=3, sticky=tk.W)
        self.target_user_binary_entry = tk.Entry(user_binary_frame, width=25)
        self.target_user_binary_entry.pack(side=tk.LEFT)
        tk.Button(user_binary_frame, text="Browse", command=lambda: self._browse_target_binary('user')).pack(side=tk.LEFT, padx=5)
        if target and target.user_binary_path:
            self.target_user_binary_entry.insert(0, target.user_binary_path)
        row += 1

        # Buttons
        btn_frame = tk.Frame(form)
        btn_frame.grid(row=row, column=0, columnspan=2, pady=15)
        
        tk.Button(btn_frame, text="Save Target", command=lambda: self._save_target(target), width=12).pack(side=tk.LEFT, padx=5)
        tk.Button(btn_frame, text="Clear", command=lambda: self._show_target_editor(None), width=12).pack(side=tk.LEFT, padx=5)

    def _browse_target_binary(self, area_type: str) -> None:
        """Browse for binary file.
        
        Args:
            area_type: 'golden' or 'user'
        """
        path = filedialog.askopenfilename(title=f"Select {area_type} binary file")
        if path:
            if area_type == 'golden':
                self.target_golden_binary_entry.delete(0, tk.END)
                self.target_golden_binary_entry.insert(0, path)
            else:
                self.target_user_binary_entry.delete(0, tk.END)
                self.target_user_binary_entry.insert(0, path)

    def _new_target(self) -> None:
        """Prepare editor for new target."""
        self.targets_listbox.selection_clear(0, tk.END)
        self._show_target_editor(None)

    def _save_target(self, original_target: Optional[FlashTarget]) -> None:
        """Save target from editor."""
        try:
            if original_target:
                id_target = original_target.id_target
            else:
                id_target = self.target_id_entry.get().strip()
                if not id_target:
                    messagebox.showerror("Error", "Target ID is required.", parent=self.dialog)
                    return
                if id_target in self.manager.targets:
                    messagebox.showerror("Error", f"Target '{id_target}' already exists.", parent=self.dialog)
                    return

            # Extract model ID from combobox selection
            model_str = self.target_model_combo.get()
            if not model_str:
                messagebox.showerror("Error", "Please select a model.", parent=self.dialog)
                return
            
            # Parse "[ID] ..." format
            id_model = int(model_str.split(']')[0].strip('['))

            golden_binary_path = self.target_golden_binary_entry.get().strip() or None
            user_binary_path = self.target_user_binary_entry.get().strip() or None

            new_target = FlashTarget(
                id_target=id_target,
                description=self.target_desc_entry.get().strip(),
                slot_address=int(self.target_slot_entry.get().strip(), 16),
                architecture=self.target_arch_combo.get(),
                name=self.target_name_entry.get().strip(),
                file_prefix=self.target_prefix_entry.get().strip(),
                id_model=id_model,
                golden_binary_path=golden_binary_path,
                user_binary_path=user_binary_path,
            )

            self.manager.add_target(new_target)
            self._refresh_targets_list()
            self.refresh_callback()
            self._show_target_editor(new_target)
            
            # Select the saved target in list
            for idx, (id_t, t) in enumerate(sorted(self.manager.targets.items())):
                if id_t == id_target:
                    self.targets_listbox.selection_clear(0, tk.END)
                    self.targets_listbox.selection_set(idx)
                    self.targets_listbox.see(idx)
                    break
            
            # Refresh default target combo in global tab
            self.global_default_combo['values'] = list(self.manager.targets.keys())
            
            messagebox.showinfo("Success", f"Target '{new_target.id_target}' saved successfully", parent=self.dialog)

        except ValueError as e:
            messagebox.showerror("Error", f"Invalid input: {e}", parent=self.dialog)

    def _delete_target(self) -> None:
        """Delete selected target."""
        selection = self.targets_listbox.curselection()
        if not selection:
            messagebox.showwarning("No Selection", "Please select a target to delete.", parent=self.dialog)
            return

        targets_list = sorted(self.manager.targets.items())
        id_target, target = targets_list[selection[0]]

        if messagebox.askyesno("Confirm Delete", f"Delete target '{target.id_target}'?", parent=self.dialog):
            self.manager.delete_target(id_target)
            self._refresh_targets_list()
            self.refresh_callback()
            self._show_target_editor(None)
            
            # Refresh default target combo in global tab
            self.global_default_combo['values'] = list(self.manager.targets.keys())

    def _save_and_close(self) -> None:
        """Save configuration and close dialog."""
        self.manager.save()
        self.refresh_callback()
        messagebox.showinfo("Success", "Configuration saved successfully", parent=self.dialog)
        self.dialog.destroy()


def main() -> None:
    root = tk.Tk()
    app = FlasherGUI(root)
    root.mainloop()


if __name__ == "__main__":
    main()
