# File: cpp_python_debug/gui/profile_manager_window.py
import tkinter as tk
from tkinter import (
    ttk,
    messagebox,
    filedialog,
)  # filedialog non sembra usato qui, ma lascio
import logging
import json  # Mantenuto per deep copy e potenziale debug
import os
import threading
import time  # Non usato direttamente, ma potrebbe servire in futuro per ritardi
from typing import (
    TYPE_CHECKING,
    List,
    Dict,
    Any,
    Optional,
    Callable,
    Union,
    Tuple,
)  # Aggiunto Tuple

# MODIFICA: Import assoluti
from cpp_python_debug.gui.dialogs import (
    SymbolAnalysisProgressDialog,
    SymbolListViewerDialog,
    FunctionSelectorDialog,
)
from cpp_python_debug.core import file_utils  # Per calculate_file_checksum
from cpp_python_debug.core.symbol_analyzer import SymbolAnalyzer
from cpp_python_debug.core.gdb_mi_session import GDBMISession
from cpp_python_debug.gui.action_editor_window import ActionEditorWindow

if TYPE_CHECKING:
    from cpp_python_debug.core.config_manager import AppSettings

    # Evita import diretto di GDBGui per prevenire potenziale import circolare stretto se GDBGui importasse ProfileManagerWindow
    # Useremo 'tk.Widget' o 'Any' per il parent se GDBGui non è strettamente necessario per type checking qui.
    # In questo caso, GDBGui è il parent di ProfileManagerWindow, quindi il type hint è utile.
    from cpp_python_debug.gui.main_window import GDBGui


logger = logging.getLogger(__name__)  # Logger specifico per questo modulo

# Default per una nuova azione, copiato da ActionEditorWindow per coerenza locale
# Idealmente, questo potrebbe essere in un modulo di costanti condiviso.
DEFAULT_ACTION_CONFIG = {
    "breakpoint_location": "main",
    "variables_to_dump": ["my_variable"],
    "output_format": "json",
    "output_directory": "./gdb_dumps",  # Default generico, potrebbe essere sovrascritto da AppSettings
    "filename_pattern": "{profile_name}_{app_name}_{breakpoint}_{variable}_{timestamp}.{format}",
    "continue_after_dump": True,
    "dump_on_every_hit": True,
}

DEFAULT_PROFILE_CONFIG = {
    "profile_name": "New Profile",
    "target_executable": "",
    "program_parameters": "",
    "symbol_analysis": None,  # Inizializzato a None
    "actions": [DEFAULT_ACTION_CONFIG.copy()],  # Inizia con una azione di default
}


class ProfileManagerWindow(tk.Toplevel):
    # (Costruttore e metodi come prima, con correzioni agli import e type hints)
    def __init__(
        self, parent: "GDBGui", app_settings: "AppSettings"
    ):  # Parent è GDBGui
        super().__init__(parent)
        self.parent_window = parent  # Riferimento al parent (GDBGui)
        self.app_settings: "AppSettings" = app_settings

        self.title("Profile Manager")
        # La geometria potrebbe essere salvata/caricata da AppSettings se desiderato
        # default_geometry = self.app_settings.get_setting("gui", "profile_manager_geometry", "1050x750")
        # self.geometry(default_geometry)
        # Per ora, usiamo una dimensione fissa come nel tuo codice originale

        window_width = 1050
        window_height = 850

        self.parent_window.update_idletasks()
        parent_x = self.parent_window.winfo_x()
        parent_y = self.parent_window.winfo_y()
        parent_width = self.parent_window.winfo_width()
        parent_height = self.parent_window.winfo_height()
        position_x = max(0, parent_x + (parent_width // 2) - (window_width // 2))
        position_y = max(0, parent_y + (parent_height // 2) - (window_height // 2))
        self.geometry(f"{window_width}x{window_height}+{position_x}+{position_y}")

        self.transient(parent)
        self.grab_set()

        self._profiles_data: List[Dict[str, Any]] = []
        self._selected_profile_index: Optional[int] = None
        self._selected_action_index_in_profile: Optional[int] = None

        self._current_profile_modified_in_form: bool = False
        self._profiles_list_changed_overall: bool = (
            False  # Traccia se la lista profili è cambiata
        )

        # Variabili Tkinter per il form del profilo
        self.profile_name_var = tk.StringVar()
        self.target_exe_var = tk.StringVar()
        self.program_params_var = tk.StringVar()

        # Variabili Tkinter per lo stato dell'analisi simboli
        self._current_profile_target_exe_details_label_var = tk.StringVar(
            value="Target Executable: N/A"
        )
        self._current_profile_analysis_status_label_var = tk.StringVar(
            value="Symbol Analysis: Not Performed"
        )
        self.progress_dialog: Optional[SymbolAnalysisProgressDialog] = None

        # Variabili Tkinter per i conteggi dei simboli
        self.functions_count_var = tk.StringVar(value="Functions: N/A")
        self.variables_count_var = tk.StringVar(value="Globals: N/A")
        self.types_count_var = tk.StringVar(value="Types: N/A")
        self.sources_count_var = tk.StringVar(value="Sources: N/A")

        self._load_profiles_from_settings()
        self._create_widgets()
        self._populate_profiles_listbox()  # Popola la listbox con i profili caricati

        if self._profiles_data:  # Se ci sono profili, seleziona il primo
            self._select_profile_by_index(0)
        else:  # Altrimenti, aggiorna lo stato per riflettere nessun profilo selezionato
            self._update_analysis_status_display()  # Questo dovrebbe gestire il caso "N/A"

        self.protocol("WM_DELETE_WINDOW", self._on_closing_button)
        # Aggiungi trace per marcare modifiche nel form
        self.profile_name_var.trace_add("write", self._mark_form_as_modified)
        self.target_exe_var.trace_add(
            "write", lambda *args: self._on_target_exe_changed_in_form()
        )
        self.program_params_var.trace_add("write", self._mark_form_as_modified)
        # self.wait_window() # Rimosso, gestito da GDBGui

    def _mark_form_as_modified(self, *args):
        # (Implementazione come prima)
        self._current_profile_modified_in_form = True

    def _on_target_exe_changed_in_form(self, *args):
        # (Implementazione come prima)
        self._mark_form_as_modified(*args)
        self._update_analysis_status_display()  # Aggiorna lo stato dell'analisi quando il target cambia

    def _load_profiles_from_settings(self) -> None:
        # (Implementazione come prima, ma con logging e deep copy più espliciti)
        self._profiles_data = []  # Resetta la lista interna
        loaded_profiles_from_settings = (
            self.app_settings.get_profiles()
        )  # Questo dovrebbe già restituire una copia

        for profile_dict_original in loaded_profiles_from_settings:
            # Crea una deep copy per evitare modifiche all'oggetto in AppSettings
            # fino a un salvataggio esplicito. json.loads(json.dumps(...)) è un modo semplice per una deep copy.
            try:
                copied_profile = json.loads(json.dumps(profile_dict_original))
            except (TypeError, json.JSONDecodeError) as e:
                logger.error(
                    f"Could not deep copy profile, skipping: {profile_dict_original}. Error: {e}"
                )
                continue

            # Assicura che le chiavi essenziali esistano e siano del tipo corretto
            if "actions" not in copied_profile or not isinstance(
                copied_profile["actions"], list
            ):
                copied_profile["actions"] = []
            if (
                "symbol_analysis" not in copied_profile
            ):  # Può essere None se non ancora analizzato
                copied_profile["symbol_analysis"] = None
            # Assicura che ogni azione sia un dizionario
            copied_profile["actions"] = [
                action
                for action in copied_profile["actions"]
                if isinstance(action, dict)
            ]

            self._profiles_data.append(copied_profile)

        self._profiles_list_changed_overall = (
            False  # Flag resettato dopo il caricamento iniziale
        )
        logger.debug(
            f"Loaded {len(self._profiles_data)} profiles into ProfileManagerWindow from AppSettings."
        )

    def _create_widgets(self) -> None:
        # (Implementazione come prima, ma con parent=self per dialoghi se necessario,
        # e assicurandosi che i type hint per i callback siano corretti)
        main_frame = ttk.Frame(self, padding="10")
        main_frame.pack(expand=True, fill=tk.BOTH)
        main_frame.columnconfigure(
            0, weight=1, minsize=250
        )  # Pane sinistro (lista profili)
        main_frame.columnconfigure(1, weight=3)  # Pane destro (dettagli profilo)
        main_frame.rowconfigure(0, weight=1)  # Riga principale per i due pani
        main_frame.rowconfigure(1, weight=0)  # Riga per i bottoni in basso

        # --- Left Pane: Profiles List and Controls ---
        left_pane = ttk.Frame(main_frame)
        left_pane.grid(row=0, column=0, sticky="nsew", padx=(0, 10))
        left_pane.rowconfigure(0, weight=1)  # Listbox si espande
        left_pane.rowconfigure(1, weight=0)  # Controlli listbox fissi
        left_pane.columnconfigure(0, weight=1)

        profiles_list_frame = ttk.LabelFrame(left_pane, text="Profiles", padding="5")
        profiles_list_frame.grid(row=0, column=0, sticky="nsew", pady=(0, 5))
        profiles_list_frame.rowconfigure(0, weight=1)
        profiles_list_frame.columnconfigure(0, weight=1)

        self.profiles_listbox = tk.Listbox(
            profiles_list_frame, exportselection=False, selectmode=tk.SINGLE, width=30
        )
        self.profiles_listbox.grid(row=0, column=0, sticky="nsew")
        self.profiles_listbox.bind("<<ListboxSelect>>", self._on_profile_select)

        listbox_scrollbar_y = ttk.Scrollbar(
            profiles_list_frame, orient=tk.VERTICAL, command=self.profiles_listbox.yview
        )
        listbox_scrollbar_y.grid(row=0, column=1, sticky="ns")
        self.profiles_listbox.configure(yscrollcommand=listbox_scrollbar_y.set)

        profiles_list_controls_frame = ttk.Frame(left_pane)  # Non un LabelFrame
        profiles_list_controls_frame.grid(row=1, column=0, sticky="ew", pady=5)
        button_width = 10
        ttk.Button(
            profiles_list_controls_frame,
            text="New",
            command=self._new_profile,
            width=button_width,
        ).pack(side=tk.LEFT, padx=2, fill=tk.X, expand=True)
        self.duplicate_button = ttk.Button(
            profiles_list_controls_frame,
            text="Duplicate",
            command=self._duplicate_profile,
            state=tk.DISABLED,
            width=button_width,
        )
        self.duplicate_button.pack(side=tk.LEFT, padx=2, fill=tk.X, expand=True)
        self.delete_button = ttk.Button(
            profiles_list_controls_frame,
            text="Delete",
            command=self._delete_profile,
            state=tk.DISABLED,
            width=button_width,
        )
        self.delete_button.pack(side=tk.LEFT, padx=2, fill=tk.X, expand=True)

        # --- Right Pane: Profile Details, Analysis, Actions ---
        right_pane = ttk.Frame(main_frame)
        right_pane.grid(row=0, column=1, sticky="nsew")
        right_pane.rowconfigure(0, weight=0)  # Details Form
        right_pane.rowconfigure(1, weight=0)  # Analysis Control
        right_pane.rowconfigure(2, weight=0)  # Symbols Summary
        right_pane.rowconfigure(3, weight=1)  # Actions UI (espandibile)
        right_pane.columnconfigure(0, weight=1)

        # Profile Details Form
        details_form_frame = ttk.LabelFrame(
            right_pane, text="Profile Details", padding="10"
        )
        details_form_frame.grid(
            row=0, column=0, sticky="new", pady=(0, 5)
        )  # new = North, East, West
        details_form_frame.columnconfigure(
            1, weight=1
        )  # Colonna degli entry si espande

        ttk.Label(details_form_frame, text="Profile Name:").grid(
            row=0, column=0, sticky=tk.W, padx=5, pady=3
        )
        self.profile_name_entry = ttk.Entry(
            details_form_frame, textvariable=self.profile_name_var, state=tk.DISABLED
        )
        self.profile_name_entry.grid(
            row=0, column=1, columnspan=2, sticky="ew", padx=5, pady=3
        )

        ttk.Label(details_form_frame, text="Target Executable:").grid(
            row=1, column=0, sticky=tk.W, padx=5, pady=3
        )
        self.target_exe_entry = ttk.Entry(
            details_form_frame, textvariable=self.target_exe_var, state=tk.DISABLED
        )
        self.target_exe_entry.grid(row=1, column=1, sticky="ew", padx=5, pady=3)
        self.browse_exe_button = ttk.Button(
            details_form_frame,
            text="Browse...",
            command=self._browse_target_executable,
            state=tk.DISABLED,
        )
        self.browse_exe_button.grid(row=1, column=2, padx=5, pady=3)

        ttk.Label(details_form_frame, text="Program Parameters:").grid(
            row=2, column=0, sticky=tk.W, padx=5, pady=3
        )
        self.program_params_entry = ttk.Entry(
            details_form_frame, textvariable=self.program_params_var, state=tk.DISABLED
        )
        self.program_params_entry.grid(
            row=2, column=1, columnspan=2, sticky="ew", padx=5, pady=3
        )

        # Symbol Analysis Control
        analysis_control_frame = ttk.LabelFrame(
            right_pane, text="Symbol Analysis Status & Control", padding="10"
        )
        analysis_control_frame.grid(row=1, column=0, sticky="new", pady=5)
        analysis_control_frame.columnconfigure(0, weight=1)  # Label di stato si espande
        analysis_control_frame.columnconfigure(1, weight=0)  # Bottone fisso

        self.target_exe_details_label = ttk.Label(
            analysis_control_frame,
            textvariable=self._current_profile_target_exe_details_label_var,
            wraplength=500,
            justify=tk.LEFT,
        )  # wraplength per testo lungo
        self.target_exe_details_label.grid(
            row=0, column=0, columnspan=2, sticky="ew", padx=5, pady=(2, 5)
        )

        self.analysis_status_label = ttk.Label(
            analysis_control_frame,
            textvariable=self._current_profile_analysis_status_label_var,
            foreground="blue",
            wraplength=350,
        )
        self.analysis_status_label.grid(row=1, column=0, sticky="w", padx=5, pady=2)

        self.analyse_symbols_button = ttk.Button(
            analysis_control_frame,
            text="Analyse Target Symbols",
            command=self._trigger_symbol_analysis,
            state=tk.DISABLED,
        )
        self.analyse_symbols_button.grid(row=1, column=1, sticky="e", padx=5, pady=2)

        # Analyzed Symbols Summary
        symbols_summary_frame = ttk.LabelFrame(
            right_pane, text="Analyzed Symbols Summary", padding="10"
        )
        symbols_summary_frame.grid(row=2, column=0, sticky="new", pady=5)
        symbols_summary_frame.columnconfigure(0, weight=1)  # Label si espande
        symbols_summary_frame.columnconfigure(1, weight=0)  # Bottone fisso

        row_s = 0
        ttk.Label(symbols_summary_frame, textvariable=self.functions_count_var).grid(
            row=row_s, column=0, sticky="w", padx=5, pady=2
        )
        self.view_functions_button = ttk.Button(
            symbols_summary_frame,
            text="View...",
            command=self._view_analyzed_functions,
            state=tk.DISABLED,
            width=8,
        )
        self.view_functions_button.grid(
            row=row_s, column=1, padx=(10, 5), pady=2, sticky="w"
        )
        row_s += 1
        ttk.Label(symbols_summary_frame, textvariable=self.variables_count_var).grid(
            row=row_s, column=0, sticky="w", padx=5, pady=2
        )
        self.view_variables_button = ttk.Button(
            symbols_summary_frame,
            text="View...",
            command=self._view_analyzed_variables,
            state=tk.DISABLED,
            width=8,
        )
        self.view_variables_button.grid(
            row=row_s, column=1, padx=(10, 5), pady=2, sticky="w"
        )
        row_s += 1
        ttk.Label(symbols_summary_frame, textvariable=self.types_count_var).grid(
            row=row_s, column=0, sticky="w", padx=5, pady=2
        )
        self.view_types_button = ttk.Button(
            symbols_summary_frame,
            text="View...",
            command=self._view_analyzed_types,
            state=tk.DISABLED,
            width=8,
        )
        self.view_types_button.grid(
            row=row_s, column=1, padx=(10, 5), pady=2, sticky="w"
        )
        row_s += 1
        ttk.Label(symbols_summary_frame, textvariable=self.sources_count_var).grid(
            row=row_s, column=0, sticky="w", padx=5, pady=2
        )
        self.view_sources_button = ttk.Button(
            symbols_summary_frame,
            text="View...",
            command=self._view_analyzed_sources,
            state=tk.DISABLED,
            width=8,
        )
        self.view_sources_button.grid(
            row=row_s, column=1, padx=(10, 5), pady=2, sticky="w"
        )
        # row_s += 1 # Non necessario se è l'ultimo

        # Debug Actions UI
        actions_ui_frame = ttk.LabelFrame(
            right_pane, text="Debug Actions", padding="10"
        )
        actions_ui_frame.grid(
            row=3, column=0, sticky="nsew", pady=5
        )  # Modificato da row=4 a row=3
        actions_ui_frame.rowconfigure(0, weight=1)  # Listbox si espande
        actions_ui_frame.columnconfigure(0, weight=1)  # Listbox si espande
        actions_ui_frame.columnconfigure(1, weight=0)  # Scrollbar
        actions_ui_frame.columnconfigure(2, weight=0)  # Bottoni laterali

        self.actions_listbox = tk.Listbox(
            actions_ui_frame, exportselection=False, selectmode=tk.SINGLE, height=6
        )
        self.actions_listbox.grid(
            row=0, column=0, sticky="nsew", pady=5, padx=(0, 5)
        )  # padx a dx per scrollbar
        self.actions_listbox.bind(
            "<<ListboxSelect>>", self._on_action_select_in_listbox
        )

        actions_listbox_scrolly = ttk.Scrollbar(
            actions_ui_frame, orient=tk.VERTICAL, command=self.actions_listbox.yview
        )
        actions_listbox_scrolly.grid(
            row=0, column=1, sticky="ns", pady=5
        )  # pady=5 per allineare
        self.actions_listbox.configure(yscrollcommand=actions_listbox_scrolly.set)

        action_buttons_frame = ttk.Frame(actions_ui_frame)  # Non LabelFrame
        action_buttons_frame.grid(
            row=0, column=2, sticky="ns", padx=(5, 0), pady=5
        )  # Allineato con listbox
        action_btn_width = 8
        self.add_action_button = ttk.Button(
            action_buttons_frame,
            text="Add...",
            command=self._add_action,
            state=tk.DISABLED,
            width=action_btn_width,
        )
        self.add_action_button.pack(fill=tk.X, pady=2, anchor="n")
        self.edit_action_button = ttk.Button(
            action_buttons_frame,
            text="Edit...",
            command=self._edit_action,
            state=tk.DISABLED,
            width=action_btn_width,
        )
        self.edit_action_button.pack(fill=tk.X, pady=2, anchor="n")
        self.remove_action_button = ttk.Button(
            action_buttons_frame,
            text="Remove",
            command=self._remove_action,
            state=tk.DISABLED,
            width=action_btn_width,
        )
        self.remove_action_button.pack(fill=tk.X, pady=2, anchor="n")

        # --- Bottom Buttons (Save All, Close) ---
        bottom_buttons_frame = ttk.Frame(main_frame)
        bottom_buttons_frame.grid(
            row=1, column=0, columnspan=2, sticky="sew", pady=(10, 0)
        )  # columnspan=2 per coprire entrambi i pani
        # Frame interno per allineare i bottoni a destra
        bottom_buttons_inner_frame = ttk.Frame(bottom_buttons_frame)
        bottom_buttons_inner_frame.pack(
            side=tk.RIGHT
        )  # Allinea il frame dei bottoni a destra

        self.save_button = ttk.Button(
            bottom_buttons_inner_frame,
            text="Save All Changes",
            command=self._save_all_profiles_to_settings,
        )
        self.save_button.pack(
            side=tk.LEFT, padx=5, pady=5
        )  # Usare LEFT per ordine corretto se il frame è pack(RIGHT)
        ttk.Button(
            bottom_buttons_inner_frame, text="Close", command=self._on_closing_button
        ).pack(side=tk.LEFT, padx=5, pady=5)

    def _populate_profiles_listbox(self) -> None:
        # (Implementazione come prima)
        self.profiles_listbox.delete(0, tk.END)
        for i, profile in enumerate(self._profiles_data):
            display_name = profile.get("profile_name", f"Unnamed Profile {i+1}")
            self.profiles_listbox.insert(tk.END, display_name)
        self._update_profile_action_buttons_state()  # Aggiorna stato bottoni New/Dup/Del
        self._update_action_buttons_state()  # Aggiorna stato bottoni Add/Edit/Remove Action

    def _on_profile_select(self, event: Optional[tk.Event] = None) -> None:
        # (Implementazione come prima)
        selection_indices = self.profiles_listbox.curselection()
        new_selected_index: Optional[int] = None
        if selection_indices:
            new_selected_index = selection_indices[0]

        if (
            self._selected_profile_index is not None
            and self._selected_profile_index != new_selected_index
            and self._current_profile_modified_in_form
        ):
            current_profile_name_in_form = self.profile_name_var.get()
            if (
                not current_profile_name_in_form
                and self._profiles_data
                and 0 <= self._selected_profile_index < len(self._profiles_data)
            ):
                current_profile_name_in_form = self._profiles_data[
                    self._selected_profile_index
                ].get("profile_name", "the previously selected profile")

            prompt = (
                f"Profile '{current_profile_name_in_form}' has unsaved changes in the form.\n"
                "Do you want to apply these changes before switching?"
            )
            response = messagebox.askyesnocancel(
                "Unsaved Changes", prompt, parent=self, default=messagebox.CANCEL
            )

            if response is True:  # Yes, save
                if not self._save_current_form_to_profile_data(
                    self._selected_profile_index
                ):
                    # Save failed, revert selection in listbox
                    self.profiles_listbox.selection_clear(0, tk.END)
                    if (
                        self._selected_profile_index is not None
                    ):  # Check again as it might have been cleared
                        self.profiles_listbox.selection_set(
                            self._selected_profile_index
                        )
                        self.profiles_listbox.activate(self._selected_profile_index)
                    return  # Abort switch
            elif response is None:  # Cancelled switch
                # Revert selection in listbox to the previously selected one
                self.profiles_listbox.selection_clear(0, tk.END)
                if self._selected_profile_index is not None:
                    self.profiles_listbox.selection_set(self._selected_profile_index)
                    self.profiles_listbox.activate(self._selected_profile_index)
                return  # Abort switch
            # If response is False (No, discard), proceed with selection change

        # Proceed with selection change
        if new_selected_index is not None:
            self._select_profile_by_index(new_selected_index)
        else:  # No selection or selection cleared
            self._clear_profile_form()
            self._selected_profile_index = None  # Explicitly set to None

        self._update_profile_action_buttons_state()
        self._update_analysis_status_display()  # Aggiorna anche qui

    def _get_data_from_form(
        self,
    ) -> Dict[str, Any]:  # Non usata direttamente, ma utile per coerenza
        # (Implementazione come prima)
        return {
            "profile_name": self.profile_name_var.get(),
            "target_executable": self.target_exe_var.get(),
            "program_parameters": self.program_params_var.get(),
        }

    def _select_profile_by_index(self, index: int) -> None:
        # (Implementazione come prima)
        if not (0 <= index < len(self._profiles_data)):
            logger.warning(f"Attempted to select invalid profile index: {index}")
            self._clear_profile_form()
            self._selected_profile_index = None
            return

        self._selected_profile_index = index
        profile = self._profiles_data[index]

        self.profile_name_var.set(profile.get("profile_name", ""))
        self.target_exe_var.set(profile.get("target_executable", ""))
        self.program_params_var.set(profile.get("program_parameters", ""))

        self._populate_actions_listbox()  # Popola azioni per il profilo selezionato
        self._enable_profile_form_editing(True)
        self._current_profile_modified_in_form = False  # Resetta flag dopo caricamento

        # Sincronizza la selezione della listbox se non già fatto dall'evento
        if (
            not self.profiles_listbox.curselection()
            or self.profiles_listbox.curselection()[0] != index
        ):
            self.profiles_listbox.selection_clear(0, tk.END)
            self.profiles_listbox.selection_set(index)
            self.profiles_listbox.activate(index)
            self.profiles_listbox.see(index)

        self.add_action_button.config(state=tk.NORMAL)  # Abilita aggiunta azioni
        self._update_analysis_status_display()  # Aggiorna stato analisi

    def _clear_profile_form(self) -> None:
        # (Implementazione come prima)
        self.profile_name_var.set("")
        self.target_exe_var.set("")
        self.program_params_var.set("")
        self._populate_actions_listbox()  # Svuota la lista azioni
        self._enable_profile_form_editing(False)
        self._current_profile_modified_in_form = False

        self.add_action_button.config(state=tk.DISABLED)
        self.edit_action_button.config(state=tk.DISABLED)
        self.remove_action_button.config(state=tk.DISABLED)
        self._update_analysis_status_display()  # Resetta anche lo stato dell'analisi

    def _enable_profile_form_editing(self, enable: bool) -> None:
        # (Implementazione come prima)
        state = tk.NORMAL if enable else tk.DISABLED
        self.profile_name_entry.config(state=state)
        self.target_exe_entry.config(state=state)
        self.browse_exe_button.config(state=state)
        self.program_params_entry.config(state=state)

    def _update_profile_action_buttons_state(self) -> None:
        # (Implementazione come prima)
        profile_selected = self._selected_profile_index is not None
        self.duplicate_button.config(
            state=tk.NORMAL if profile_selected else tk.DISABLED
        )
        self.delete_button.config(state=tk.NORMAL if profile_selected else tk.DISABLED)

    def _browse_target_executable(self) -> None:
        # (Implementazione come prima)
        current_path = self.target_exe_var.get()
        initial_dir = (
            os.path.dirname(current_path)
            if current_path and os.path.exists(os.path.dirname(current_path))
            else None
        )
        path = filedialog.askopenfilename(
            title="Select Target Executable",
            filetypes=[("Executable files", ("*.exe", "*")), ("All files", "*.*")],
            initialdir=initial_dir,
            parent=self,
        )
        if path:
            self.target_exe_var.set(
                path
            )  # Questo attiverà _on_target_exe_changed_in_form

    def _save_current_form_to_profile_data(self, profile_index: int) -> bool:
        # (Implementazione come prima, ma con controllo indice migliorato)
        if not (self._profiles_data and 0 <= profile_index < len(self._profiles_data)):
            logger.error(f"Invalid profile index {profile_index} for saving form data.")
            return False

        profile_name = self.profile_name_var.get().strip()
        if not profile_name:
            messagebox.showerror(
                "Validation Error", "Profile Name cannot be empty.", parent=self
            )
            self.profile_name_entry.focus_set()
            return False

        for i, p_item in enumerate(self._profiles_data):
            if i != profile_index and p_item.get("profile_name") == profile_name:
                messagebox.showerror(
                    "Validation Error",
                    f"Profile name '{profile_name}' already exists. Names must be unique.",
                    parent=self,
                )
                self.profile_name_entry.focus_set()
                return False

        target_profile_in_list = self._profiles_data[profile_index]
        old_name = target_profile_in_list.get("profile_name")
        old_target_exe = target_profile_in_list.get("target_executable")

        new_target_exe = self.target_exe_var.get().strip()

        target_profile_in_list["profile_name"] = profile_name
        target_profile_in_list["target_executable"] = new_target_exe
        target_profile_in_list["program_parameters"] = (
            self.program_params_var.get()
        )  # Strip non necessario se è vuoto

        self._current_profile_modified_in_form = False  # Form salvato nei dati interni
        self._profiles_list_changed_overall = True

        if old_name != profile_name:  # Aggiorna la listbox se il nome è cambiato
            self.profiles_listbox.delete(profile_index)
            self.profiles_listbox.insert(profile_index, profile_name)
            self.profiles_listbox.selection_set(profile_index)  # Riseleziona

        if (
            old_target_exe != new_target_exe
        ):  # Se il target exe è cambiato, l'analisi precedente è invalidata
            logger.info(
                f"Target executable changed for profile '{profile_name}'. Invalidating previous symbol analysis."
            )
            target_profile_in_list["symbol_analysis"] = (
                None  # Invalida i dati di analisi
            )
            self._update_analysis_status_display()  # Aggiorna UI per riflettere questo

        logger.info(
            f"Profile '{profile_name}' (index {profile_index}) details from form saved to internal list."
        )
        return True

    def _new_profile(self) -> None:
        # (Implementazione come prima)
        if (
            self._selected_profile_index is not None
            and self._current_profile_modified_in_form
        ):
            current_profile_name_display = self.profile_name_var.get() or (
                self._profiles_data[self._selected_profile_index].get("profile_name")
                if self._profiles_data
                and 0 <= self._selected_profile_index < len(self._profiles_data)
                else "current profile"
            )
            prompt = (
                f"Profile '{current_profile_name_display}' has unsaved changes in the form.\n"
                "Do you want to save them before creating a new profile?"
            )
            response = messagebox.askyesnocancel(
                "Unsaved Changes", prompt, default=messagebox.CANCEL, parent=self
            )
            if response is True:
                if not self._save_current_form_to_profile_data(
                    self._selected_profile_index
                ):
                    return
            elif response is None:
                return

        new_p = json.loads(
            json.dumps(DEFAULT_PROFILE_CONFIG)
        )  # Usa DEFAULT_PROFILE_CONFIG
        base_name = new_p["profile_name"]
        name_candidate = base_name
        count = 1
        existing_names = {p.get("profile_name") for p in self._profiles_data}
        while name_candidate in existing_names:
            name_candidate = f"{base_name} ({count})"
            count += 1
        new_p["profile_name"] = name_candidate

        self._profiles_data.append(new_p)
        self._profiles_list_changed_overall = True
        self._populate_profiles_listbox()
        self._select_profile_by_index(len(self._profiles_data) - 1)
        self.profile_name_entry.focus_set()
        self.profile_name_entry.selection_range(0, tk.END)
        self._mark_form_as_modified()

    def _duplicate_profile(self) -> None:
        # (Implementazione come prima)
        if self._selected_profile_index is None:
            return
        if self._current_profile_modified_in_form:
            if not self._save_current_form_to_profile_data(
                self._selected_profile_index
            ):
                return

        original_profile = self._profiles_data[self._selected_profile_index]
        duplicated_profile = json.loads(json.dumps(original_profile))
        base_name = f"{original_profile.get('profile_name', 'Profile')}_copy"
        name_candidate = base_name
        count = 1
        existing_names = {p.get("profile_name") for p in self._profiles_data}
        while name_candidate in existing_names:
            name_candidate = f"{base_name}_{count}"
            count += 1
        duplicated_profile["profile_name"] = name_candidate

        self._profiles_data.append(duplicated_profile)
        self._profiles_list_changed_overall = True
        self._populate_profiles_listbox()
        self._select_profile_by_index(len(self._profiles_data) - 1)
        self._mark_form_as_modified()

    def _delete_profile(self) -> None:
        # (Implementazione come prima)
        if self._selected_profile_index is None or not self._profiles_data:
            return
        profile_to_delete = self._profiles_data[self._selected_profile_index]
        profile_name = profile_to_delete.get("profile_name", "this profile")
        if not messagebox.askyesno(
            "Confirm Delete",
            f"Are you sure you want to delete profile '{profile_name}'?",
            parent=self,
        ):
            return

        idx_to_delete = self._selected_profile_index
        del self._profiles_data[idx_to_delete]
        self._profiles_list_changed_overall = True
        new_list_size = len(self._profiles_data)
        self._selected_profile_index = None  # Resetta prima di ripopolare
        self._populate_profiles_listbox()
        if new_list_size == 0:
            self._clear_profile_form()
        else:
            new_selection_idx = min(idx_to_delete, new_list_size - 1)
            if new_selection_idx >= 0:
                self._select_profile_by_index(new_selection_idx)
            else:
                self._clear_profile_form()

    def _save_all_profiles_to_settings(self) -> None:
        # (Implementazione come prima)
        if (
            self._selected_profile_index is not None
            and self._current_profile_modified_in_form
        ):
            if not self._save_current_form_to_profile_data(
                self._selected_profile_index
            ):
                messagebox.showerror(
                    "Save Error",
                    "Could not save changes from the form for the selected profile. Aborting save to settings.",
                    parent=self,
                )
                return

        profile_names_seen = set()
        for i, profile in enumerate(self._profiles_data):
            name = profile.get("profile_name", "").strip()
            if not name:
                messagebox.showerror(
                    "Validation Error",
                    f"Profile at index {i} (0-based) has an empty name.",
                    parent=self,
                )
                if self._selected_profile_index != i:
                    self._select_profile_by_index(i)
                self.profile_name_entry.focus_set()
                return
            if name in profile_names_seen:
                messagebox.showerror(
                    "Validation Error",
                    f"Duplicate profile name '{name}' found.",
                    parent=self,
                )
                first_occurrence_index = next(
                    (
                        idx
                        for idx, p in enumerate(self._profiles_data)
                        if p.get("profile_name") == name
                    ),
                    -1,
                )
                if (
                    first_occurrence_index != -1
                    and self._selected_profile_index != first_occurrence_index
                ):
                    self._select_profile_by_index(first_occurrence_index)
                self.profile_name_entry.focus_set()
                return
            profile_names_seen.add(name)
            actions = profile.get("actions")
            if not isinstance(actions, list):
                messagebox.showerror(
                    "Data Error",
                    f"Profile '{name}' has malformed actions (not a list).",
                    parent=self,
                )
                return
            for idx_a, action in enumerate(actions):
                if not isinstance(action, dict):
                    messagebox.showerror(
                        "Data Error",
                        f"Profile '{name}', action #{idx_a+1} is malformed (not a dict).",
                        parent=self,
                    )
                    return

        self.app_settings.set_profiles(self._profiles_data)
        if self.app_settings.save_settings():
            logger.info(
                f"All {len(self._profiles_data)} profiles saved to AppSettings."
            )
            messagebox.showinfo(
                "Profiles Saved", "All profile changes have been saved.", parent=self
            )
            self._current_profile_modified_in_form = False
            self._profiles_list_changed_overall = False
        else:
            messagebox.showerror(
                "Save Error",
                "Could not save profiles to the settings file. Check logs.",
                parent=self,
            )

    def _on_closing_button(self) -> None:
        # (Implementazione come prima)
        needs_save_prompt = False
        prompt_message = ""
        if (
            self._selected_profile_index is not None
            and self._current_profile_modified_in_form
        ):
            needs_save_prompt = True
            profile_name = self.profile_name_var.get() or (
                self._profiles_data[self._selected_profile_index].get("profile_name")
                if self._profiles_data
                and 0 <= self._selected_profile_index < len(self._profiles_data)
                else "current profile"
            )
            prompt_message = (
                f"Profile '{profile_name}' has unsaved changes in the form.\n"
            )
        if self._profiles_list_changed_overall:
            needs_save_prompt = True
            prompt_message += (
                "Additionally, the overall list of profiles (or their content) has changed.\n"
                if prompt_message
                else "The list of profiles (or their content) has changed.\n"
            )

        if needs_save_prompt:
            prompt_message += "Do you want to save all changes before closing?"
            response = messagebox.askyesnocancel(
                "Unsaved Changes",
                prompt_message,
                default=messagebox.CANCEL,
                parent=self,
            )
            if response is True:
                self._save_all_profiles_to_settings()
            elif response is None:
                return

        if self.progress_dialog and self.progress_dialog.winfo_exists():
            logger.warning(
                "Closing ProfileManagerWindow while symbol analysis dialog open."
            )

        if hasattr(self.parent_window, "focus_set") and callable(
            self.parent_window.focus_set
        ):
            self.parent_window.focus_set()
        self.destroy()

    # --- Metodi per la gestione delle Azioni (Listbox, Add, Edit, Remove) ---
    def _populate_actions_listbox(self) -> None:
        # (Implementazione come prima)
        self.actions_listbox.delete(0, tk.END)
        self._selected_action_index_in_profile = None
        if (
            self._selected_profile_index is not None
            and 0 <= self._selected_profile_index < len(self._profiles_data)
        ):
            profile = self._profiles_data[self._selected_profile_index]
            actions = profile.get("actions", [])
            for i, action in enumerate(actions):
                bp = action.get("breakpoint_location", "N/A")[:30]
                num_vars = len(action.get("variables_to_dump", []))
                fmt = action.get("output_format", "N/A")
                cont = "Yes" if action.get("continue_after_dump", False) else "No"
                dump_freq = (
                    "Always" if action.get("dump_on_every_hit", True) else "Once"
                )
                summary = (
                    f"BP: {bp}{'...' if len(action.get('breakpoint_location', '')) > 30 else ''} "
                    f"(Vars:{num_vars}, Fmt:{fmt}, Cont:{cont}, Hit:{dump_freq})"
                )
                self.actions_listbox.insert(tk.END, summary)
        self._update_action_buttons_state()

    def _on_action_select_in_listbox(self, event: Optional[tk.Event] = None) -> None:
        # (Implementazione come prima)
        selection_indices = self.actions_listbox.curselection()
        if selection_indices:
            self._selected_action_index_in_profile = selection_indices[0]
        else:
            self._selected_action_index_in_profile = None
        self._update_action_buttons_state()

    def _update_action_buttons_state(self) -> None:
        # (Implementazione come prima)
        profile_selected = self._selected_profile_index is not None
        action_selected = self._selected_action_index_in_profile is not None
        self.add_action_button.config(
            state=tk.NORMAL if profile_selected else tk.DISABLED
        )
        self.edit_action_button.config(
            state=tk.NORMAL if action_selected else tk.DISABLED
        )
        self.remove_action_button.config(
            state=tk.NORMAL if action_selected else tk.DISABLED
        )

    def _add_action(self) -> None:
        # (Implementazione come prima, ma con type hint e logica AppSettings corretta)
        if self._selected_profile_index is None:
            return
        if self._current_profile_modified_in_form:
            if not self._save_current_form_to_profile_data(
                self._selected_profile_index
            ):
                return

        current_profile = self._profiles_data[self._selected_profile_index]
        current_profile_target_exe = current_profile.get("target_executable", "")
        current_profile_program_params = current_profile.get("program_parameters", "")
        symbol_analysis_data = current_profile.get("symbol_analysis")

        editor = ActionEditorWindow(
            self,
            action_data=None,
            is_new=True,
            target_executable_path=current_profile_target_exe,
            app_settings=self.app_settings,  # Passa l'istanza corretta
            symbol_analysis_data=symbol_analysis_data,
            program_parameters_for_scope=current_profile_program_params,
        )
        self.wait_window(editor)  # Rendi modale
        new_action_data = editor.get_result()

        if new_action_data:
            if "actions" not in current_profile or not isinstance(
                current_profile["actions"], list
            ):
                current_profile["actions"] = []
            current_profile["actions"].append(new_action_data)
            self._profiles_list_changed_overall = True
            self._populate_actions_listbox()
            self.actions_listbox.selection_set(tk.END)
            self._on_action_select_in_listbox()

    def _edit_action(self) -> None:
        # (Implementazione come prima, ma con type hint e logica AppSettings corretta)
        if (
            self._selected_profile_index is None
            or self._selected_action_index_in_profile is None
        ):
            return
        if self._current_profile_modified_in_form:
            if not self._save_current_form_to_profile_data(
                self._selected_profile_index
            ):
                return

        current_profile = self._profiles_data[self._selected_profile_index]
        actions_list = current_profile.get("actions", [])
        if not (0 <= self._selected_action_index_in_profile < len(actions_list)):
            logger.error("Selected action index out of bounds.")
            return

        action_to_edit = actions_list[self._selected_action_index_in_profile]
        current_profile_target_exe = current_profile.get("target_executable", "")
        current_profile_program_params = current_profile.get("program_parameters", "")
        symbol_analysis_data = current_profile.get("symbol_analysis")

        editor = ActionEditorWindow(
            self,
            action_data=action_to_edit,
            is_new=False,
            target_executable_path=current_profile_target_exe,
            app_settings=self.app_settings,  # Passa l'istanza corretta
            symbol_analysis_data=symbol_analysis_data,
            program_parameters_for_scope=current_profile_program_params,
        )
        self.wait_window(editor)  # Rendi modale
        updated_action_data = editor.get_result()

        if updated_action_data:
            current_profile["actions"][
                self._selected_action_index_in_profile
            ] = updated_action_data
            self._profiles_list_changed_overall = True
            idx_to_reselect = self._selected_action_index_in_profile
            self._populate_actions_listbox()
            if (
                idx_to_reselect is not None
                and 0 <= idx_to_reselect < self.actions_listbox.size()
            ):
                self.actions_listbox.selection_set(idx_to_reselect)
            self._on_action_select_in_listbox()

    def _remove_action(self) -> None:
        # (Implementazione come prima)
        if (
            self._selected_profile_index is None
            or self._selected_action_index_in_profile is None
        ):
            return
        profile = self._profiles_data[self._selected_profile_index]
        action_summary_to_delete = self.actions_listbox.get(
            self._selected_action_index_in_profile
        )
        if not messagebox.askyesno(
            "Confirm Delete Action",
            f"Delete action?\n\n{action_summary_to_delete}",
            parent=self,
        ):
            return

        actions_list = profile.get("actions")
        if isinstance(
            actions_list, list
        ) and 0 <= self._selected_action_index_in_profile < len(actions_list):
            del actions_list[self._selected_action_index_in_profile]
            self._profiles_list_changed_overall = True
            idx_to_reselect_after_delete = self._selected_action_index_in_profile
            self._populate_actions_listbox()
            num_actions_remaining = len(actions_list)
            if num_actions_remaining > 0:
                new_selection = min(
                    idx_to_reselect_after_delete, num_actions_remaining - 1
                )
                if new_selection >= 0:
                    self.actions_listbox.selection_set(new_selection)
            self._on_action_select_in_listbox()
        else:
            logger.error("Could not remove action: list missing or index invalid.")

    # --- Metodi per Symbol Analysis (come prima, ma con type hint e logica AppSettings corretta) ---
    def _update_analysis_status_display(self) -> None:
        # (Implementazione come prima, ma assicurati che file_utils sia importato per calculate_file_checksum)
        if self._selected_profile_index is None or not (
            0 <= self._selected_profile_index < len(self._profiles_data)
        ):
            self._current_profile_target_exe_details_label_var.set(
                "Target Executable: N/A (No profile selected)"
            )
            self._current_profile_analysis_status_label_var.set(
                "Symbol Analysis: Select a profile."
            )
            self.analyse_symbols_button.config(state=tk.DISABLED)
            self.functions_count_var.set("Functions: N/A")
            self.view_functions_button.config(state=tk.DISABLED)
            self.variables_count_var.set("Globals: N/A")
            self.view_variables_button.config(state=tk.DISABLED)
            self.types_count_var.set("Types: N/A")
            self.view_types_button.config(state=tk.DISABLED)
            self.sources_count_var.set("Sources: N/A")
            self.view_sources_button.config(state=tk.DISABLED)
            return

        profile = self._profiles_data[self._selected_profile_index]
        target_exe_in_form = self.target_exe_var.get().strip()

        exe_display_name = (
            os.path.basename(target_exe_in_form) if target_exe_in_form else "N/A"
        )
        details_text_lines = [f"Target in Form: {exe_display_name}"]
        status_text = "Symbol Analysis: "
        status_color = "blue"  # Default color

        funcs_count_text = "Functions: N/A"
        view_funcs_btn_state = tk.DISABLED
        vars_count_text = "Globals: N/A"
        view_vars_btn_state = tk.DISABLED
        types_count_text = "Types: N/A"
        view_types_btn_state = tk.DISABLED
        sources_count_text = "Sources: N/A"
        view_sources_btn_state = tk.DISABLED
        analysis_button_state = tk.DISABLED

        if target_exe_in_form and os.path.isfile(target_exe_in_form):
            analysis_button_state = tk.NORMAL  # Can analyse if exe path is valid

        if not target_exe_in_form:
            status_text += "Target executable not specified in form."
        elif not os.path.isfile(target_exe_in_form):
            status_text += f"Target '{exe_display_name}' (from form) not found on disk."
            status_color = "red"
        else:  # Target in form is a valid file
            analysis_data = profile.get("symbol_analysis")
            if analysis_data and isinstance(analysis_data, dict):
                symbols_dict = analysis_data.get("symbols", {})
                num_functions = symbols_dict.get("functions_count", 0)
                funcs_count_text = f"Functions: {num_functions}"
                if num_functions > 0:
                    view_funcs_btn_state = tk.NORMAL
                num_variables = symbols_dict.get("global_variables_count", 0)
                vars_count_text = f"Globals: {num_variables}"
                if num_variables > 0:
                    view_vars_btn_state = tk.NORMAL
                num_types = symbols_dict.get("types_count", 0)
                types_count_text = f"Types: {num_types}"
                if num_types > 0:
                    view_types_btn_state = tk.NORMAL
                num_sources = symbols_dict.get("source_files_count", 0)
                sources_count_text = f"Sources: {num_sources}"
                if num_sources > 0:
                    view_sources_btn_state = tk.NORMAL

                saved_exe_at_analysis = analysis_data.get(
                    "analyzed_executable_path", "Unknown"
                )
                details_text_lines.append(
                    f"Last Analysis on File: {os.path.basename(saved_exe_at_analysis)}"
                )
                details_text_lines.append(
                    f"  File Timestamp (at analysis): {analysis_data.get('executable_timestamp', 'N/A')}"
                )
                details_text_lines.append(
                    f"  Analysis Date: {analysis_data.get('analysis_timestamp', 'N/A')}"
                )
                saved_checksum = analysis_data.get("executable_checksum")
                details_text_lines.append(
                    f"  Saved Checksum: {saved_checksum or 'N/A'}"
                )

                current_checksum_for_form_exe = file_utils.calculate_file_checksum(
                    target_exe_in_form
                )
                details_text_lines.append(
                    f"  Current Form Exe Checksum: {current_checksum_for_form_exe or 'N/A (calc failed)'}"
                )

                if os.path.normpath(saved_exe_at_analysis) != os.path.normpath(
                    target_exe_in_form
                ):
                    status_text += (
                        "TARGET CHANGED since last analysis. RE-ANALYSIS RECOMMENDED."
                    )
                    status_color = "orange red"
                    view_funcs_btn_state = view_vars_btn_state = (
                        view_types_btn_state
                    ) = view_sources_btn_state = tk.DISABLED
                elif (
                    saved_checksum
                    and current_checksum_for_form_exe
                    and saved_checksum == current_checksum_for_form_exe
                ):
                    status_text += "Up-to-date."
                    status_color = "dark green"
                elif saved_checksum and current_checksum_for_form_exe:  # Mismatch
                    status_text += (
                        "EXECUTABLE CHANGED (checksum mismatch). RE-ANALYSIS REQUIRED."
                    )
                    status_color = "red"
                    view_funcs_btn_state = view_vars_btn_state = (
                        view_types_btn_state
                    ) = view_sources_btn_state = tk.DISABLED
                else:  # Checksum missing or calc failed for current
                    status_text += "Status unclear (checksums). Consider re-analysing."
                    status_color = "orange"
                    # Mantieni i bottoni view abilitati se c'è data, ma con avviso
            else:
                status_text += "Not performed. Click 'Analyse' to generate."
                status_color = "blue"

        self.analyse_symbols_button.config(state=analysis_button_state)
        self._current_profile_target_exe_details_label_var.set(
            "\n".join(details_text_lines)
        )
        self._current_profile_analysis_status_label_var.set(status_text)
        self.analysis_status_label.config(foreground=status_color)
        self.functions_count_var.set(funcs_count_text)
        self.view_functions_button.config(state=view_funcs_btn_state)
        self.variables_count_var.set(vars_count_text)
        self.view_variables_button.config(state=view_vars_btn_state)
        self.types_count_var.set(types_count_text)
        self.view_types_button.config(state=view_types_btn_state)
        self.sources_count_var.set(sources_count_text)
        self.view_sources_button.config(state=view_sources_btn_state)

    def _trigger_symbol_analysis(self) -> None:
        # (Implementazione come prima, assicurati che SymbolAnalyzer sia importato correttamente)
        if self._selected_profile_index is None:
            messagebox.showerror("Error", "No profile selected.", parent=self)
            return
        target_exe_for_analysis = self.target_exe_var.get().strip()
        if not target_exe_for_analysis or not os.path.isfile(target_exe_for_analysis):
            messagebox.showerror(
                "Error", "Target executable path in the form is invalid.", parent=self
            )
            self._update_analysis_status_display()
            return

        gdb_exe_path = self.app_settings.get_setting("general", "gdb_executable_path")
        if not gdb_exe_path or not os.path.isfile(gdb_exe_path):
            messagebox.showerror(
                "GDB Error",
                f"GDB executable not configured or not found: {gdb_exe_path}",
                parent=self,
            )
            return

        profile_to_update = self._profiles_data[self._selected_profile_index]
        self.progress_dialog = SymbolAnalysisProgressDialog(
            self
        )  # parent è self (ProfileManagerWindow)

        # Passa GDBMISession come classe per l'analisi
        symbol_analyzer = SymbolAnalyzer(
            gdb_exe_path, self.app_settings, gdb_session_class=GDBMISession
        )

        analysis_thread = threading.Thread(
            target=self._perform_symbol_analysis_thread,
            args=(
                profile_to_update,
                target_exe_for_analysis,
                symbol_analyzer,
                self.progress_dialog,
            ),
            daemon=True,
        )
        analysis_thread.start()

    def _perform_symbol_analysis_thread(
        self,
        profile_to_update: Dict[str, Any],
        target_exe_path: str,
        symbol_analyzer: SymbolAnalyzer,
        progress_dialog: SymbolAnalysisProgressDialog,
    ):
        # (Implementazione come prima)
        analysis_data_dict: Dict[str, Any] = {}
        analysis_succeeded_overall = False

        def gui_log(msg: str):
            if progress_dialog and progress_dialog.winfo_exists():
                self.after(0, progress_dialog.log_message, msg)

        def gui_set_status(msg: str):
            if progress_dialog and progress_dialog.winfo_exists():
                self.after(0, progress_dialog.set_status, msg)

        try:
            gui_log(
                f"Starting symbol analysis for: {os.path.basename(target_exe_path)}"
            )
            gui_set_status(f"Analyzing {os.path.basename(target_exe_path)}...")
            analysis_data_dict = symbol_analyzer.analyze(
                target_exe_path=target_exe_path,
                progress_callback=gui_log,
                status_callback=gui_set_status,
            )
            if analysis_data_dict:
                analysis_succeeded_overall = True
                gui_set_status("Symbol analysis successfully completed.")
                gui_log("\nSymbol analysis successfully completed.")
            else:
                gui_set_status("Symbol analysis failed. Check logs.")
                gui_log("\nSymbol analysis failed.")
        except Exception as e:
            logger.error(
                f"Error in symbol analysis thread for '{target_exe_path}': {e}",
                exc_info=True,
            )
            error_msg = f"THREAD ERROR: {type(e).__name__} - {e}"
            gui_log(f"\n{error_msg}")
            gui_set_status(error_msg)
        finally:
            if self.winfo_exists():  # Assicura che ProfileManagerWindow esista ancora
                self.after(
                    0,
                    self._finalize_symbol_analysis,
                    profile_to_update,
                    analysis_data_dict,
                    analysis_succeeded_overall,
                    progress_dialog,
                )

    def _finalize_symbol_analysis(
        self,
        profile_to_update: Dict[str, Any],
        analysis_data: Dict[str, Any],
        success: bool,
        progress_dialog: SymbolAnalysisProgressDialog,
    ):
        # (Implementazione come prima)
        if success:
            profile_to_update["symbol_analysis"] = analysis_data
            self._profiles_list_changed_overall = True
            logger.info(
                f"Symbol analysis data updated for profile: '{profile_to_update.get('profile_name')}'."
            )
            if self.winfo_exists():
                messagebox.showinfo(
                    "Analysis Complete",
                    "Symbol analysis has finished successfully.",
                    parent=self,
                )
        else:
            logger.error(
                f"Symbol analysis failed for profile: '{profile_to_update.get('profile_name')}'."
            )
            if self.winfo_exists():
                messagebox.showerror(
                    "Analysis Failed",
                    "Symbol analysis did not complete successfully. Check logs.",
                    parent=self,
                )

        if progress_dialog and progress_dialog.winfo_exists():
            progress_dialog.analysis_complete_or_failed(success)
        if self.winfo_exists():
            self._update_analysis_status_display()

    def _get_symbols_for_display(
        self, category: str
    ) -> List[Union[str, Dict[str, Any]]]:
        # (Implementazione come prima)
        if self._selected_profile_index is None or not (
            0 <= self._selected_profile_index < len(self._profiles_data)
        ):
            return []
        profile = self._profiles_data[self._selected_profile_index]
        analysis_data = profile.get("symbol_analysis")
        if not analysis_data or not isinstance(analysis_data.get("symbols"), dict):
            return []
        return analysis_data["symbols"].get(category, [])

    def _view_analyzed_functions(self) -> None:  # (Come prima)
        functions_list = self._get_symbols_for_display("functions")
        if not functions_list:
            messagebox.showinfo(
                "No Functions", "No functions from analysis.", parent=self
            )
            return
        self._show_symbol_list_dialog("Functions", functions_list)

    def _view_analyzed_variables(self) -> None:  # (Come prima)
        variables_list = self._get_symbols_for_display("global_variables")
        if not variables_list:
            messagebox.showinfo(
                "No Global Variables", "No globals from analysis.", parent=self
            )
            return
        self._show_symbol_list_dialog("Global Variables", variables_list)

    def _view_analyzed_types(self) -> None:  # (Come prima)
        types_list = self._get_symbols_for_display("types")
        if not types_list:
            messagebox.showinfo("No Types", "No types from analysis.", parent=self)
            return
        self._show_symbol_list_dialog("Types", types_list)

    def _view_analyzed_sources(self) -> None:  # (Come prima)
        source_files_list = self._get_symbols_for_display("source_files")
        if not source_files_list:
            messagebox.showinfo(
                "No Source Files", "No sources from analysis.", parent=self
            )
            return
        self._show_symbol_list_dialog("Source Files", source_files_list)

    def _show_symbol_list_dialog(
        self, symbol_type: str, symbols: List[Union[str, Dict[str, Any]]]
    ) -> None:
        # (Implementazione come prima, ma assicurati che file_utils sia importato per calculate_file_checksum)
        if self._selected_profile_index is None:
            return
        target_exe_in_form = self.target_exe_var.get().strip()  # Usa il valore del form
        profile = self._profiles_data[self._selected_profile_index]
        analysis_data = profile.get("symbol_analysis")
        if not analysis_data:
            messagebox.showerror("Error", "No analysis data available.", parent=self)
            return

        analyzed_exe_path = analysis_data.get("analyzed_executable_path", "")
        exe_name_for_title = (
            os.path.basename(target_exe_in_form)
            if target_exe_in_form
            else "Unknown Target"
        )
        is_obsolete = True
        if (
            target_exe_in_form
            and os.path.isfile(target_exe_in_form)
            and os.path.normpath(analyzed_exe_path)
            == os.path.normpath(target_exe_in_form)
        ):
            current_checksum = file_utils.calculate_file_checksum(
                target_exe_in_form
            )  # USA file_utils
            saved_checksum = analysis_data.get("executable_checksum")
            if (
                current_checksum
                and saved_checksum
                and current_checksum == saved_checksum
            ):
                is_obsolete = False

        title_suffix = " (Analysis data might be obsolete)" if is_obsolete else ""
        dialog_title = (
            f"Analyzed {symbol_type} for '{exe_name_for_title}'{title_suffix}"
        )

        # SymbolListViewerDialog è già importato
        SymbolListViewerDialog(self, symbols, title=dialog_title)
