# File: cpp_python_debug/core/profile_executor.py
# (Assicurati che tutti gli import necessari siano presenti all'inizio del file)
import logging
import os
import time 
import json
import re
import shutil
from datetime import datetime
from typing import Dict, Any, Optional, Callable, List, Tuple, Union

from cpp_python_debug.core.gdb_controller import GDBSession
from cpp_python_debug.core.config_manager import AppSettings 
from cpp_python_debug.core.output_formatter import save_to_json as save_data_to_json_file 
from cpp_python_debug.core.output_formatter import save_to_csv as save_data_to_csv_file
from cpp_python_debug.core.file_utils import sanitize_filename_component

logger = logging.getLogger(__name__)

ExecutionLogEntry = Dict[str, str] 
GDB_DUMP_EXTENSION = ".gdbdump.json"


class ProfileExecutor:
    def __init__(
        self,
        profile_data: Dict[str, Any],
        app_settings: AppSettings,
        app_log_directory_path: str,
        status_update_callback: Optional[Callable[[str], None]] = None,
        gdb_output_callback: Optional[Callable[[str], None]] = None,
        json_output_callback: Optional[Callable[[Any], None]] = None,
        execution_log_callback: Optional[Callable[[ExecutionLogEntry], None]] = None,
    ):
        self.profile: Dict[str, Any] = profile_data
        self.app_settings: AppSettings = app_settings
        self.app_log_dir_for_gdb_dumper: str = app_log_directory_path
        self.gdb_session: Optional[GDBSession] = None
        self.is_running: bool = False
        self._stop_requested: bool = False
        self.profile_execution_summary: Dict[str, Any] = {}
        self.produced_files_log: List[ExecutionLogEntry] = []
        self.execution_event_log: List[str] = []
        self.gdb_bp_num_to_details_map: Dict[int, Dict[str, Any]] = {}
        self.address_to_action_indices_map: Dict[str, List[int]] = {}
        self.current_run_output_path: Optional[str] = None
        self.status_updater: Callable[[str], None] = status_update_callback if status_update_callback else self._default_status_update
        self.gdb_output_writer: Callable[[str], None] = gdb_output_callback if gdb_output_callback else self._default_gdb_output
        self.json_data_handler: Callable[[Any], None] = json_output_callback if json_output_callback else self._default_json_data
        self.execution_log_adder: Callable[[ExecutionLogEntry], None] = execution_log_callback if execution_log_callback else self._default_execution_log
        logger.info(f"ProfileExecutor initialized for profile: '{self.profile.get('profile_name', 'Unnamed Profile')}' "
                    f"Targeting dumper logs to dir (via GDBSession): '{self.app_log_dir_for_gdb_dumper}'")

    def _default_status_update(self, msg: str): logger.info(f"ProfileExec Status: {msg}")
    def _default_gdb_output(self, msg: str): logger.debug(f"ProfileExec GDB Output: {msg[:1000]}...")
    def _default_json_data(self, data: Any): logger.debug(f"ProfileExec Dumper Status/JSON: {str(data)[:500]}...")
    def _default_execution_log(self, entry: ExecutionLogEntry): logger.info(f"ProfileExec FileLog Entry: {entry}")

    def _log_event(self, message: str, is_status_update: bool = True) -> None:
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S"); log_message = f"[{timestamp}] {message}"
        self.execution_event_log.append(log_message)
        if is_status_update: self.status_updater(message)
        # Usa logger.info per gli eventi principali, logger.debug per dettagli interni
        if "DEBUG:" in message.upper() or "TRACE:" in message.upper():
            logger.debug(message)
        else:
            logger.info(message)


    def _add_produced_file_entry(
        self, breakpoint_loc_spec: str, variable_name: str, final_file_path_abs: str, status: str,
        gdb_bp_num: Optional[int] = None, address: Optional[str] = None, details: str = "", 
        gdb_dump_file_kept_abs: Optional[str] = None
    ) -> None:
        entry: ExecutionLogEntry = {
            "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            "breakpoint_spec": breakpoint_loc_spec, "gdb_bp_num": str(gdb_bp_num) if gdb_bp_num is not None else "N/A",
            "address": address if address else "N/A", "variable": variable_name,
            "file_produced": os.path.basename(final_file_path_abs) if final_file_path_abs else "N/A",
            "full_path": final_file_path_abs if final_file_path_abs else "N/A", "status": status, "details": details,
            "raw_gdb_dump_file": gdb_dump_file_kept_abs if gdb_dump_file_kept_abs else "N/A (or processed)"
        }
        self.produced_files_log.append(entry); self.execution_log_adder(entry)

    def _get_setting(self, category: str, key: str, default: Optional[Any] = None) -> Any:
        return self.app_settings.get_setting(category, key, default)

    def _get_dumper_options(self) -> Dict[str, Any]:
        return self.app_settings.get_category_settings("dumper_options", {})

    def _generate_filename_base_and_extensions(
        self, pattern: str, profile_name: str, bp_loc_spec: str, var_name: str,
        final_target_format_extension_str: str
    ) -> Tuple[str, str, str]:
        timestamp_str = datetime.now().strftime("%Y%m%d_%H%M%S_%f")[:-3]

        # Use the enhanced sanitize function with reasonable length limits
        placeholders = {
            "{profile_name}": sanitize_filename_component(profile_name, max_len=30),
            "{app_name}": sanitize_filename_component(os.path.basename(self.profile.get("target_executable", "app")), max_len=30),
            "{breakpoint}": sanitize_filename_component(bp_loc_spec, max_len=40),
            "{variable}": sanitize_filename_component(var_name, max_len=40),
            "{timestamp}": timestamp_str,
        }

        # The {format} placeholder is handled separately as it's just the extension
        filename_base_template = pattern.replace(".{format}", "").replace("{format}", "")

        filename_base = filename_base_template
        for ph, val in placeholders.items():
            filename_base = filename_base.replace(ph, val)

        # Final cleanup for the entire base name
        filename_base = filename_base.rstrip('._- ')
        if not filename_base:
            filename_base = f"dump_{placeholders['{variable}']}_{placeholders['{timestamp}']}"

        gdb_dumper_actual_file_extension = GDB_DUMP_EXTENSION
        final_user_file_extension = f".{final_target_format_extension_str.lower().lstrip('.')}"
        
        return filename_base, gdb_dumper_actual_file_extension, final_user_file_extension

    def _prepare_output_directory(self, base_output_dir_from_action: str, profile_name: str) -> Optional[str]:
        if not base_output_dir_from_action: self._log_event(f"Error: Output dir not specified in profile action.", True); return None
        abs_base_output_dir = os.path.abspath(base_output_dir_from_action) 
        sane_profile_name = sanitize_filename_component(profile_name)
        timestamp_dir = datetime.now().strftime("%Y%m%d_%H%M%S")
        specific_run_dirname = f"{sane_profile_name}_{timestamp_dir}"
        profile_execution_path = os.path.join(abs_base_output_dir, specific_run_dirname)
        try:
            os.makedirs(profile_execution_path, exist_ok=True)
            self._log_event(f"Output directory for this run: {profile_execution_path}", False)
            return profile_execution_path
        except OSError as e: self._log_event(f"Error creating output directory '{profile_execution_path}': {e}", True); logger.error(f"OSError creating output dir: {e}", exc_info=True); return None

    def _parse_gdb_set_breakpoint_output(self, gdb_output: str) -> Optional[Tuple[int, str]]: 
        if not gdb_output: return None
        m = re.search(r"Breakpoint\s+(\d+)\s+at\s+(0x[0-9a-fA-F]+)", gdb_output, re.I); 
        if m: return int(m.group(1)), m.group(2).lower()
        m_pend = re.search(r"Breakpoint\s+(\d+)\s+pending", gdb_output, re.I); 
        if m_pend: return int(m_pend.group(1)), "pending"
        logger.warning(f"Could not parse GDB BP num/addr from: '{gdb_output[:100]}...'"); return None

    def _parse_breakpoint_hit_output(self, gdb_output: str) -> Optional[int]:
        if not gdb_output: return None
        m_thread = re.search(r"Thread\s+\S+\s+hit\s+Breakpoint\s+(\d+)", gdb_output, re.I); 
        if m_thread: return int(m_thread.group(1))
        m_simple = re.search(r"Breakpoint\s+(\d+)[,\s]", gdb_output); 
        if m_simple: return int(m_simple.group(1))
        logger.debug(f"No GDB BP num from hit output: '{gdb_output[:100]}...'"); return None

    def _check_program_exited_from_output(self, gdb_output: str) -> bool:
        if not gdb_output:
            return False
        definitive_exit_patterns = [
            r"\[Inferior\s+\d+\s+\(process\s+\d+\)\s+exited",
            r"Program exited normally\.",
            r"Program exited with code .*\.",
            r"Program terminated with signal .*\.",
            r"Remote communication error",
            r"The program is not being run\.",
            r"quit\s*$",
        ]
        for pattern_str in definitive_exit_patterns:
            if re.search(pattern_str, gdb_output, re.IGNORECASE | re.MULTILINE):
                logger.info(
                    f"Detected definitive program exit via pattern: '{pattern_str}' in GDB output snippet: '{gdb_output[:250]}...'"
                )
                return True
        return False

    def run(self) -> None:
        profile_name = self.profile.get("profile_name", "Unnamed Profile")
        self._log_event(f"Starting profile execution: '{profile_name}'...", True)
        self.is_running = True
        self._stop_requested = False
        self.produced_files_log.clear()
        self.execution_event_log.clear()
        self.gdb_bp_num_to_details_map.clear()
        self.address_to_action_indices_map.clear()
        self.profile_execution_summary = {
            "profile_name": profile_name,
            "target_executable": self.profile.get("target_executable"),
            "program_parameters": self.profile.get("program_parameters"),
            "start_time": datetime.now().isoformat(),
            "end_time": None,
            "status": "Initialized",
            "actions_summary": [
                {
                    "action_index": i,
                    "breakpoint_spec": action.get("breakpoint_location", "N/A"),
                    "gdb_bp_num_assigned": None,
                    "address_resolved": None,
                    "variables_dumped_count": 0,
                    "hit_count": 0,
                    "status": "Pending",
                }
                for i, action in enumerate(self.profile.get("actions", []))
            ],
            "execution_log": [],
            "files_produced_detailed": [],
        }

        gdb_exe = self._get_setting("general", "gdb_executable_path")
        target_exe = self.profile.get("target_executable")
        gdb_script_path = self._get_setting("general", "gdb_dumper_script_path")
        
        if not target_exe or not os.path.exists(target_exe):
            msg = f"Error: Target '{target_exe or 'N/A'}' not found for profile '{profile_name}'."; 
            self._log_event(msg, True)
            self.profile_execution_summary["status"] = "Error: Target not found"
            self._finalize_run()
            return

        actions = self.profile.get("actions", [])
        if not actions:
            self._log_event(f"Profile '{profile_name}' has no actions.", True)
            self.profile_execution_summary["status"] = "Error: No actions"
            self._finalize_run()
            return

        base_output_dir_cfg = actions[0].get("output_directory") or self._get_setting("general", "default_dump_output_dir", "./gdb_profile_dumps")
        self.current_run_output_path = self._prepare_output_directory(base_output_dir_cfg, profile_name)
        if not self.current_run_output_path:
            self.profile_execution_summary["status"] = "Error: Output dir creation failed"
            self._finalize_run()
            return

        default_timeouts = self.app_settings._get_default_settings().get("timeouts", {})
        cmd_timeout = self._get_setting("timeouts", "gdb_command", default_timeouts.get("gdb_command", 30))
        run_timeout = self._get_setting("timeouts", "program_run_continue", default_timeouts.get("program_run_continue", 120))

        try:
            self.gdb_session = GDBSession(
                gdb_path=gdb_exe, 
                executable_path=target_exe, 
                gdb_script_full_path=gdb_script_path, 
                dumper_options=self._get_dumper_options(),
                app_log_directory_path=self.app_log_dir_for_gdb_dumper
            )
            startup_timeout = self._get_setting("timeouts", "gdb_start", default_timeouts.get("gdb_start", 30))
            self._log_event(f"popen_spawning GDB for '{os.path.basename(target_exe)}'...", True)
            self.gdb_session.start(timeout=startup_timeout)
            
            if not self.gdb_session.symbols_found: 
                self._log_event(f"Warning: No debug symbols in '{os.path.basename(target_exe)}'.", True)
            if gdb_script_path and not self.gdb_session.gdb_script_sourced_successfully: 
                self._log_event(f"Warning: Dumper script '{os.path.basename(gdb_script_path)}' failed to load.", False)
            else: 
                self._log_event("GDB session started & dumper script checked.", False)

            num_mapped_bps = 0
            for idx, action_cfg in enumerate(actions):
                if self._stop_requested: break
                bp_spec = action_cfg.get("breakpoint_location")
                action_sum = self.profile_execution_summary["actions_summary"][idx]
                if not bp_spec: 
                    action_sum["status"] = "Skipped (No BP Location)"
                    continue
                self._log_event(f"Setting Action {idx+1} BP: {bp_spec}", False)
                bp_out = self.gdb_session.set_breakpoint(bp_spec, timeout=cmd_timeout)
                self.gdb_output_writer(bp_out)
                parsed_bp = self._parse_gdb_set_breakpoint_output(bp_out)
                if parsed_bp:
                    bp_num, addr = parsed_bp
                    action_sum.update({"gdb_bp_num_assigned":bp_num, "address_resolved":addr})
                    self.gdb_bp_num_to_details_map[bp_num] = {"address":addr, "action_index":idx, "bp_spec":bp_spec}
                    if addr!="pending": 
                        self.address_to_action_indices_map.setdefault(addr,[]).append(idx)
                        self._log_event(f"Action {idx+1} BP {bp_num} at {addr} for '{bp_spec}'.", False)
                        num_mapped_bps+=1
                    else: 
                        action_sum["status"] = "Pending GDB Resolution"
                        self._log_event(f"Action {idx+1} BP {bp_num} for '{bp_spec}' pending GDB resolution.", False)
                else: 
                    action_sum["status"] = "Error (BP Set/Parse Failed)"
                    self._log_event(f"Failed to set/parse BP for Action {idx+1} ('{bp_spec}'). Output: {bp_out[:100]}", True)
            
            if self._stop_requested: 
                raise InterruptedError("Stop requested during breakpoint setup.")
            if num_mapped_bps == 0 and not any(s["status"] == "Pending GDB Resolution" for s in self.profile_execution_summary["actions_summary"]):
                self._log_event("No non-pending breakpoints were set. Aborting profile run.", True)
                self.profile_execution_summary["status"] = "Error: No Breakpoints Mapped"
                self._finalize_run()
                return

            prog_params = self.profile.get("program_parameters", "")
            self._log_event(f"Running '{os.path.basename(target_exe)} {prog_params}'...", True)
            
            gdb_output = self.gdb_session.run_program(prog_params, timeout=run_timeout)
            self.gdb_output_writer(gdb_output)
            
            program_exited = self._check_program_exited_from_output(gdb_output)
            logger.debug(f"ProfileDEBUG: Initial run. program_exited: {program_exited}. GDB output: \n{gdb_output[:500]}")
            if program_exited:
                self._log_event(f"Program exited on initial 'run'.", True)

            hit_details_for_log = "N/A (Before first stop)"
            program_started_once_flag = False # Used locally in loop, self.program_started_once is for manual mode

            while self.gdb_session.is_alive() and not program_exited and not self._stop_requested:
                logger.debug(f"ProfileDEBUG: --- ProfileExecutor WHILE loop iteration ---")
                logger.debug(f"ProfileDEBUG: Current gdb_output at top of loop: \n{gdb_output[:500]}")

                current_hit_bp_num = self._parse_breakpoint_hit_output(gdb_output)
                current_pc: Optional[str] = None
                
                if self.gdb_session.is_alive() and (current_hit_bp_num is not None or not program_started_once_flag):
                    try:
                        temp_pc_output_for_extraction = gdb_output 
                        m_pc = re.search(r"\$\d+\s*=\s*(0x[0-9a-fA-F]+)", temp_pc_output_for_extraction)
                        if not m_pc:
                            m_pc_at = re.search(r"at\s+(0x[0-9a-fA-F]+)", temp_pc_output_for_extraction)
                            if m_pc_at: current_pc = m_pc_at.group(1).lower()
                            elif current_hit_bp_num is not None:
                                pc_query_out = self.gdb_session.send_cmd("p/x $pc", True, cmd_timeout)
                                self.gdb_output_writer(f"$pc query: {pc_query_out}\n")
                                m_pc_query = re.search(r"\$\d+\s*=\s*(0x[0-9a-fA-F]+)", pc_query_out)
                                if m_pc_query: current_pc = m_pc_query.group(1).lower()
                        elif m_pc: current_pc = m_pc.group(1).lower()
                    except Exception as e_pc_log: logger.warning(f"ProfileDEBUG: Could not get $pc for logging: {e_pc_log}")

                if current_hit_bp_num is not None and current_hit_bp_num in self.gdb_bp_num_to_details_map:
                    bp_det = self.gdb_bp_num_to_details_map[current_hit_bp_num]
                    addr_hit = bp_det["address"]
                    hit_details_for_log = f"GDB BP {current_hit_bp_num} ('{bp_det['bp_spec']}') at {addr_hit if addr_hit != 'pending' else 'pending'}"
                    if not current_pc and addr_hit != "pending": current_pc = addr_hit
                elif current_pc:
                    hit_details_for_log = f"stop at PC {current_pc}"
                # else: hit_details_for_log retains its value from previous iteration or initial state if no new info

                logger.debug(f"ProfileDEBUG: Loop start: hit_bp_num={current_hit_bp_num}, pc={current_pc}, hit_details='{hit_details_for_log}'")

                effective_addr = current_pc
                if not effective_addr and current_hit_bp_num is not None and current_hit_bp_num in self.gdb_bp_num_to_details_map:
                     bp_det_for_addr = self.gdb_bp_num_to_details_map[current_hit_bp_num]
                     if bp_det_for_addr["address"] != "pending": effective_addr = bp_det_for_addr["address"]
                
                actions_at_stop: List[int] = []
                if effective_addr and effective_addr != "pending" and effective_addr in self.address_to_action_indices_map:
                    actions_at_stop.extend(self.address_to_action_indices_map[effective_addr])
                
                logger.debug(f"ProfileDEBUG: Effective_addr='{effective_addr}', actions_at_stop={actions_at_stop}")

                if actions_at_stop:
                    self._log_event(f"Processing stop at {hit_details_for_log} (Effective Addr: {effective_addr}). Actions: {actions_at_stop}", True)
                    action_summary_list_for_profile = self.profile_execution_summary["actions_summary"]
                    
                    continue_after_all_actions_this_stop = True 
                    for action_idx in sorted(list(set(actions_at_stop))):
                        if self._stop_requested:
                            logger.debug("ProfileDEBUG: Stop requested by user during action processing.")
                            break 
                        
                        action_cfg = actions[action_idx]
                        action_sum_in_summary = action_summary_list_for_profile[action_idx]
                        
                        first_hit_for_this_action = action_sum_in_summary["hit_count"] == 0
                        action_sum_in_summary["hit_count"] += 1
                        
                        if not (action_cfg.get("dump_on_every_hit", True) or first_hit_for_this_action):
                            self._log_event(f"Action {action_idx+1} ('{action_cfg.get('breakpoint_location')}'): Skipped dump (hit {action_sum_in_summary['hit_count']}).", False)
                            if not action_cfg.get("continue_after_dump", True):
                                continue_after_all_actions_this_stop = False
                            continue

                        action_sum_in_summary["status"] = "Processing Dumps"
                        DEFAULT_FILENAME_PATTERN_EXECUTOR = "{profile_name}_{app_name}_{breakpoint}_{variable}_{timestamp}.{format}" # Default for profile executor
                        DEFAULT_OUTPUT_FORMAT_EXECUTOR = "json"
                        vars_to_dump = action_cfg.get("variables_to_dump", [])
                        fname_patt = action_cfg.get("filename_pattern", DEFAULT_FILENAME_PATTERN_EXECUTOR)
                        out_fmt = action_cfg.get("output_format", DEFAULT_OUTPUT_FORMAT_EXECUTOR).lower()
                        bp_spec_cfg = action_cfg.get("breakpoint_location", "unknown_bp_spec")
                        
                        success_dumps_this_action_hit = 0
                        for var_name in vars_to_dump:
                            if self._stop_requested: break
                            dump_to = self._get_setting("timeouts","dump_variable",default_timeouts.get("dump_variable",60))
                            base_fn, gdb_d_ext, final_u_ext = self._generate_filename_base_and_extensions(fname_patt, profile_name, bp_spec_cfg, var_name, out_fmt)
                            abs_run_path = os.path.abspath(self.current_run_output_path) if self.current_run_output_path else os.path.abspath(".")
                            gdb_dump_fpath_abs = os.path.join(abs_run_path, f"{base_fn}{gdb_d_ext}")
                            final_out_fpath_abs = os.path.join(abs_run_path, f"{base_fn}{final_u_ext}")

                            status_p = self.gdb_session.dump_variable_to_json(var_name, timeout=dump_to, target_output_filepath=gdb_dump_fpath_abs, target_output_format=out_fmt)
                            self.json_data_handler(status_p); log_stat="Fail"; log_det=""; gdb_dump_kept_path=None
                            if status_p.get("status") == "success":
                                written_fpath = status_p.get("filepath_written")
                                if not written_fpath or not os.path.exists(written_fpath): 
                                    log_stat="Error"; log_det="Dumper success but .gdbdump.json missing."; 
                                    self._log_event(f"Err dumping '{var_name}': {log_det} Expected Path: {written_fpath or gdb_dump_fpath_abs}", True); 
                                    gdb_dump_kept_path = written_fpath or gdb_dump_fpath_abs
                                else:
                                    gdb_dump_kept_path = written_fpath
                                    if out_fmt == "json":
                                        if gdb_dump_fpath_abs.lower() != final_out_fpath_abs.lower():
                                            try: 
                                                shutil.move(gdb_dump_fpath_abs, final_out_fpath_abs)
                                                self._log_event(f"Renamed '{os.path.basename(gdb_dump_fpath_abs)}' to '{os.path.basename(final_out_fpath_abs)}'.",False)
                                                gdb_dump_kept_path=None
                                            except Exception as emv: 
                                                log_stat="RenameFail";log_det=f"RenameErr:{emv}"
                                                self._log_event(f"Failed to rename dump file for '{var_name}': {emv}",True)
                                        log_stat="Success"; success_dumps_this_action_hit+=1
                                        self._log_event(f"Saved '{var_name}' to '{os.path.basename(final_out_fpath_abs)}' (JSON by GDB).",False)
                                    elif out_fmt == "csv":
                                        try:
                                            # Leggi il contenuto JSON strutturato dal file temporaneo
                                            data_from_json = None
                                            with open(gdb_dump_fpath_abs, 'r', encoding='utf-8') as fj:
                                                structured_json = json.load(fj)
                                            
                                            # Estrai i metadati e i dati principali
                                            metadata = structured_json.get("metadata", {})
                                            data_from_json = structured_json.get("data")

                                            if data_from_json is None:
                                                raise ValueError("JSON structure from dumper is missing 'data' key.")

                                            # Passa sia i dati che i metadati a save_data_to_csv_file
                                            # La funzione si occuperà di formattare tutto correttamente.
                                            save_data_to_csv_file(
                                                data_to_save=data_from_json, 
                                                filepath=final_out_fpath_abs,
                                                metadata=metadata  # Passiamo il nuovo argomento
                                            )
                                            
                                            log_stat = "Success"
                                            success_dumps_this_action_hit += 1
                                            self._log_event(f"Converted '{var_name}' to CSV: '{os.path.basename(final_out_fpath_abs)}'.", False)
                                            
                                            # Pulisci il file temporaneo
                                            try:
                                                if os.path.exists(gdb_dump_fpath_abs):
                                                    os.remove(gdb_dump_fpath_abs)
                                                gdb_dump_kept_path = None
                                            except Exception as edel:
                                                self._log_event(f"Warning: Could not delete intermediate GDB dump '{gdb_dump_fpath_abs}': {edel}", False)
                                        except Exception as ecsv: 
                                            log_stat = "CSVConvFail"
                                            log_det = f"CSVErr:{ecsv}"
                                            self._log_event(f"Error converting dump of '{var_name}' to CSV: {ecsv}", True)
                                    else: 
                                        log_stat="Error";log_det=f"Unsupported output format '{out_fmt}'.";
                                        self._log_event(log_det,True)
                            else: 
                                log_stat="GDBDumpFail"; log_det=status_p.get("message","GDB dumper error")
                                if status_p.get("details"):log_det+=f" (Details: {status_p.get('details')})"
                                self._log_event(f"Error dumping '{var_name}' (from GDB script): {log_det}",True)
                                gdb_dump_kept_path=status_p.get("filepath_written") # Potrebbe esserci un file parziale o di errore
                            self._add_produced_file_entry(bp_spec_cfg,var_name,final_out_fpath_abs if log_stat=="Success" else (gdb_dump_kept_path or gdb_dump_fpath_abs),log_stat,current_hit_bp_num,effective_addr,log_det,gdb_dump_file_kept_abs=gdb_dump_kept_path)
                        
                        action_sum_in_summary["variables_dumped_count"] += success_dumps_this_action_hit
                        if success_dumps_this_action_hit == len(vars_to_dump) and vars_to_dump:
                            action_sum_in_summary["status"]="CompletedThisHit"
                        elif not vars_to_dump:
                            action_sum_in_summary["status"]="Completed (No Vars Defined)"
                        else:
                            action_sum_in_summary["status"]="CompletedThisHitWithErrors"

                        if not action_cfg.get("continue_after_dump", True):
                            logger.debug(f"ProfileDEBUG: Action {action_idx} configured to NOT continue. Setting continue_after_all_actions_this_stop=False.")
                            continue_after_all_actions_this_stop = False
                            break 
                    
                    if self._stop_requested:
                        logger.debug("ProfileDEBUG: Stop requested, breaking from while loop after action processing loop.")
                        break 

                    if continue_after_all_actions_this_stop:
                        self._log_event(f"Continuing GDB after processing actions for {hit_details_for_log}...", True)
                        gdb_output = self.gdb_session.continue_execution(timeout=run_timeout) # Update gdb_output
                        self.gdb_output_writer(gdb_output)
                        logger.debug(f"ProfileDEBUG: Output after continue from actions: \n{gdb_output[:500]}")
                        if self._check_program_exited_from_output(gdb_output):
                            program_exited = True
                            self._log_event(f"Program exited after 'continue' from {hit_details_for_log}.", True)
                            logger.debug(f"ProfileDEBUG: Program exited after continue. program_exited={program_exited}")
                    else: 
                        self._log_event(f"Execution intentionally halted by profile action configuration at {hit_details_for_log}.", True)
                        program_exited=True
                        logger.debug(f"ProfileDEBUG: Execution halted by action config. program_exited={program_exited}")
                
                elif self._check_program_exited_from_output(gdb_output):
                    program_exited=True
                    self._log_event(f"Program exited (no actions for current stop '{hit_details_for_log}'). Output: {gdb_output[:200]}", True)
                    logger.debug(f"ProfileDEBUG: Program exited (no actions). program_exited={program_exited}")
                
                elif "received signal" in gdb_output.lower() and "SIGINT" not in gdb_output.upper():
                    program_exited=True
                    self._log_event(f"Program signal/crash detected at {hit_details_for_log}. GDB Output: {gdb_output[:150]}...", True)
                    self.profile_execution_summary["status"]="Completed (Program Signalled/Crashed)"
                    logger.debug(f"ProfileDEBUG: Program signalled. program_exited={program_exited}")
                
                elif not self._stop_requested:
                    logger.warning(f"ProfileDEBUG: GDB in unexpected state or no action for current stop: {hit_details_for_log}. Last GDB output: {gdb_output[:300]}");
                    self._log_event(f"GDB in unexpected state ({hit_details_for_log}). Attempting to continue...",True)
                    try: 
                        if self.gdb_session.is_alive():
                            gdb_output = self.gdb_session.continue_execution(timeout=cmd_timeout) # Update gdb_output
                            self.gdb_output_writer(gdb_output)
                            logger.debug(f"ProfileDEBUG: Output after probe-continue: \n{gdb_output[:500]}")
                            if self._check_program_exited_from_output(gdb_output):
                                program_exited=True
                                self._log_event(f"Program exited after probe-continue from {hit_details_for_log}.",True)
                        else: 
                            program_exited=True
                            self._log_event(f"GDB session no longer alive during probe at {hit_details_for_log}. Ending profile run.",True)
                        logger.debug(f"ProfileDEBUG: After probe: program_exited={program_exited}")
                    except Exception as e_probe:
                        program_exited=True
                        self._log_event(f"Error probing GDB status from {hit_details_for_log}: {e_probe}. Ending profile run.",True)
                        logger.debug(f"ProfileDEBUG: Exception during probe. program_exited={program_exited}")
                
                if not program_started_once_flag and not program_exited: # Mark that the program has started and run at least once
                    program_started_once_flag = True
                
                if program_exited:
                    logger.debug(f"ProfileDEBUG: program_exited is True at end of loop, breaking. Final hit_details_for_log='{hit_details_for_log}'")
                    break
                logger.debug(f"ProfileDEBUG: --- End of ProfileExecutor WHILE loop iteration. program_exited={program_exited} ---")

            # Determine final status after loop
            if self._stop_requested:
                 self.profile_execution_summary["status"] = "Completed (User Stopped)"
            elif program_exited: # Program exited for some reason (could be normal, crash, or due to action)
                # Check if any actions were still pending (implies premature exit relative to profile goals)
                if any(s["status"] == "Pending" or s["status"] == "Pending GDB Resolution" for s in self.profile_execution_summary["actions_summary"]):
                     self.profile_execution_summary["status"] = "Completed (Program Exited Prematurely)"
                elif self.profile_execution_summary["status"] == "Initialized": # If status hasn't been updated by signal/crash
                     self.profile_execution_summary["status"] = "Completed (Program Exited)"
            elif not (self.gdb_session and self.gdb_session.is_alive()): # GDB died
                self.profile_execution_summary["status"]="Error: GDB Session Died Unexpectedly"
                self._log_event("Error: GDB session died unexpectedly during profile execution.",True)
            else: # Loop finished but not due to stop_requested, program_exited, or GDB death (should be rare)
                if any(s["status"] == "Pending" for s in self.profile_execution_summary["actions_summary"]):
                     self.profile_execution_summary["status"] = "Completed (Some Actions Remain Pending)"
                else:
                     self.profile_execution_summary["status"] = "Completed (All Triggered Actions Processed)"

        except InterruptedError as ie: 
            self.profile_execution_summary["status"]="Interrupted (User Stop)"
            self._log_event(f"Profile execution interrupted: {ie}", True)
        except FileNotFoundError as fnf: 
            msg=f"Error running profile '{profile_name}': File not found - {fnf}"
            self._log_event(msg,True); 
            self.profile_execution_summary["status"]=f"Error: File Not Found - {fnf}"
        except (ConnectionError, TimeoutError) as se: 
            msg=f"Session error running profile '{profile_name}': {type(se).__name__} - {se}"
            self._log_event(msg,True); 
            self.profile_execution_summary["status"]=f"Error: GDB Session/Timeout - {se}"
        except Exception as e: 
            msg=f"Unexpected error running profile '{profile_name}': {type(e).__name__} - {e}"
            self._log_event(msg,True); 
            logger.critical(f"ProfileExecutionRun: {msg}",exc_info=True)
            self.profile_execution_summary["status"]=f"Critical Error: {e}"
        finally:
            self._finalize_run()

    def _finalize_run(self):
        self.profile_execution_summary["end_time"] = datetime.now().isoformat()
        self.profile_execution_summary["execution_log"] = self.execution_event_log
        self.profile_execution_summary["files_produced_detailed"] = self.produced_files_log
        self._cleanup_session()
        summary_file = self._finalize_summary_report(self.current_run_output_path)
        final_msg = (f"Profile '{self.profile.get('profile_name')}' finished. Status: {self.profile_execution_summary.get('status', 'Unknown')}. "
                     f"Summary: {summary_file or 'N/A (log only)'}.")
        self._log_event(final_msg, True); self.is_running = False

    def _finalize_summary_report(self, run_output_path_abs: Optional[str]) -> Optional[str]:
        if not run_output_path_abs: 
            logger.warning("No valid run output path for summary report.")
            logger.info(f"Execution Summary (not saved to file) for '{self.profile.get('profile_name')}':\n{json.dumps(self.profile_execution_summary, indent=2)}")
            return None
        sane_name = sanitize_filename_component(self.profile.get("profile_name", "profile_run_unnamed"))
        summary_fname = f"_{sane_name}_execution_summary_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
        summary_fpath = os.path.join(run_output_path_abs, summary_fname)
        try:
            with open(summary_fpath, 'w', encoding='utf-8') as f: 
                json.dump(self.profile_execution_summary, f, indent=2, ensure_ascii=False)
            logger.info(f"Execution summary report saved to: {summary_fpath}"); 
            return summary_fpath
        except Exception as e: 
            logger.error(f"Failed to save execution summary to '{summary_fpath}': {e}")
            logger.info(f"Execution Summary (failed to save to file) for '{self.profile.get('profile_name')}':\n{json.dumps(self.profile_execution_summary, indent=2)}")
            return None

    def request_stop(self) -> None:
        if self.is_running: 
            self._log_event("User requested stop for profile execution...", True)
            self._stop_requested = True
        else: 
            self._log_event("Stop requested, but no profile is currently running.", False)

    def _cleanup_session(self) -> None:
        if self.gdb_session and self.gdb_session.is_alive():
            self._log_event("Cleaning up GDB session for profile executor...", False) # False per non aggiornare status principale
            default_timeouts = self.app_settings._get_default_settings().get("timeouts", {})
            quit_timeout = self._get_setting("timeouts", "gdb_quit", default_timeouts.get("gdb_quit",10))
            try: 
                # Non serve kill_program qui, GDB quit dovrebbe gestire l'inferior se ancora attivo
                self.gdb_session.quit(timeout=quit_timeout)
            except Exception as e: 
                logger.error(f"Exception during GDB quit in profile executor cleanup: {e}", exc_info=True)
            finally: 
                self.gdb_session = None
        elif self.gdb_session: # Sessione non viva ma l'oggetto esiste
            self.gdb_session = None 
        logger.info("ProfileExecutor GDB resources cleanup sequence completed.")

# ... (eventuali altre funzioni o codice a livello di modulo se ce ne fossero) ...