# File: cpp_python_debug/core/gdb_controller.py
# Manages the GDB process and interaction, using PEXPECT correctly for cross-platform support.

import os
import re
import pexpect
import logging
import json
import time
import sys
import subprocess
from tkinter import messagebox
from typing import Optional, Dict, Any, List, Tuple

# Conditional import for pexpect depending on the platform
if sys.platform == "win32":
    from pexpect.popen_spawn import PopenSpawn
    spawn = PopenSpawn
else:
    # On Linux/macOS, pexpect.spawn is the right choice
    spawn = pexpect.spawn

# Absolute imports
from cpp_python_debug.core.frozen_utils import find_resource

logger = logging.getLogger(__name__)

DEFAULT_GDB_OPERATION_TIMEOUT = 30
DEFAULT_LONG_GDB_OPERATION_TIMEOUT = 120


class GDBSession:
    """
    Manages a GDB subprocess, providing methods to send commands,
    set breakpoints, run the target, and dump variables using a custom GDB Python script.
    """

    def __init__(
        self,
        gdb_path: str,
        executable_path: str,
        gdb_script_full_path: Optional[str] = None,
        dumper_options: Optional[Dict[str, Any]] = None,
        app_log_directory_path: Optional[str] = None,
    ):
        if not os.path.exists(gdb_path):
            msg = f"GDB executable not found at: {gdb_path}"
            logger.error(msg)
            raise FileNotFoundError(msg)
        if not os.path.exists(executable_path):
            msg = f"Target executable not found at: {executable_path}"
            logger.error(msg)
            raise FileNotFoundError(msg)

        self.gdb_path = os.path.normpath(gdb_path)
        self.executable_path = os.path.normpath(executable_path)
        self.gdb_script_path: Optional[str] = None
        self.dumper_options = dumper_options if dumper_options else {}

        self.app_log_dir_for_dumper: Optional[str] = None
        if app_log_directory_path and os.path.isdir(app_log_directory_path):
            self.app_log_dir_for_dumper = os.path.normpath(app_log_directory_path)
        else:
            logger.warning(
                f"app_log_directory_path ('{app_log_directory_path}') not provided or invalid to GDBSession. Dumper script may log to default location."
            )

        if gdb_script_full_path:
            normalized_gdb_script_path = os.path.normpath(gdb_script_full_path)
            if os.path.exists(normalized_gdb_script_path):
                self.gdb_script_path = normalized_gdb_script_path
            else:
                logger.warning(
                    f"GDB Python dumper script not found at: {normalized_gdb_script_path}."
                )

        self.child: Optional[pexpect.spawn] = None
        self.gdb_prompt = "(gdb) "
        self.gdb_mi_prompt = "(gdb) " # Kept for consistency, though not used in this class
        self.gdb_script_sourced_successfully = False
        self.symbols_found = False

        logger.info(
            f"GDBSession initialized. GDB: '{self.gdb_path}', Executable: '{self.executable_path}', "
            f"DumperScript: '{self.gdb_script_path or 'N/A'}', DumperLogDir (target): '{self.app_log_dir_for_dumper or 'Default/Fallback'}'"
        )
        if self.dumper_options:
            logger.info(f"General dumper options for GDB script: {self.dumper_options}")

    def is_alive(self) -> bool:
        """
        Checks if the GDB subprocess is currently running.
        This is a cross-platform safe way to check the status.
        """
        if self.child is None:
            return False
        # The 'closed' attribute is a reliable way to check if the stream is dead.
        # It exists on both pexpect.spawn and pexpect.popen_spawn.
        return not self.child.closed

    def _force_close_child(self):
        """
        Forces the child process to terminate in a cross-platform manner.
        """
        if not self.is_alive():
            return
            
        logger.warning("Forcing termination of GDB child process.")
        try:
            if hasattr(self.child, 'proc'):
                # This is a PopenSpawn object (Windows), use the underlying Popen process
                self.child.proc.terminate()
            else:
                # This is a standard pexpect.spawn object (Linux/macOS)
                self.child.close(force=True)
        except Exception as e:
            logger.error(f"Exception during force close of GDB process: {e}", exc_info=True)
        finally:
            self.child = None

    def get_gdb_version(
        self, timeout: int = DEFAULT_GDB_OPERATION_TIMEOUT
    ) -> Optional[str]:
        if not self.is_alive():
            logger.error("Cannot get GDB version: session not active.")
            return None
            
        command = "show version"
        logger.info(f"Requesting GDB version: '{command}'")
        try:
            output = self.send_cmd(command, expect_prompt=True, timeout=timeout)
            if output:
                match = re.search(r"^(GNU gdb.*?)$", output, re.MULTILINE)
                first_line = (
                    match.group(1).strip()
                    if match
                    else next(
                        (ln.strip() for ln in output.splitlines() if ln.strip()), ""
                    )
                )
                logger.info(f"GDB version string: {first_line}")
                return first_line
            logger.warning("No output for 'show version'.")
            return None
        except (ConnectionError, TimeoutError) as e:
            logger.error(f"Error getting GDB version: {e}", exc_info=True)
            return None
        except Exception as ep:
            logger.error(f"Error parsing 'show version' output: {ep}", exc_info=True)
            return None

    def start(self, timeout: int = DEFAULT_GDB_OPERATION_TIMEOUT) -> None:
        gdb_dir_for_test = os.path.dirname(self.gdb_path)
        command_test_list = [self.gdb_path, "--version"]
        
        logger.info(
            f"[SUBPROCESS_TEST] Attempting GDB version check with subprocess: "
            f"{command_test_list}, cwd={gdb_dir_for_test if os.path.isdir(gdb_dir_for_test) else 'Default CWD'}"
        )
        try:
            process = subprocess.Popen(
                command_test_list,
                cwd=gdb_dir_for_test if os.path.isdir(gdb_dir_for_test) else None,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True,
                encoding='utf-8', 
                errors='replace', 
                creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == "win32" else 0
            )
            stdout, stderr = process.communicate(timeout=15)
            logger.info(f"[SUBPROCESS_TEST] GDB --version stdout: {stdout.strip() if stdout else '<empty>'}")
            if stderr: 
                logger.error(f"[SUBPROCESS_TEST] GDB --version stderr: {stderr.strip()}")
            if process.returncode != 0:
                raise ConnectionError(f"GDB --version check failed with return code {process.returncode}. Stderr: {stderr.strip()}")
        except Exception as e_generic_subproc:
            logger.error(f"[SUBPROCESS_TEST] Generic Exception during GDB --version test: {type(e_generic_subproc).__name__} - {e_generic_subproc}", exc_info=True)
            raise RuntimeError(f"Unexpected error during GDB --version pre-check: {e_generic_subproc}") from e_generic_subproc

        logger.info("[SUBPROCESS_TEST] Phase completed. Proceeding to pexpect spawn...")

        gdb_command_parts = [self.gdb_path, "--nx", "--quiet", self.executable_path]
        gdb_exe_dir = os.path.dirname(self.gdb_path)
        spawn_cwd_for_gdb = gdb_exe_dir if os.path.isdir(gdb_exe_dir) else None
            
        logger.info(
            f"Preparing to spawn GDB (via pexpect): {gdb_command_parts} with startup timeout: {timeout}s"
        )
        
        try:
            if sys.platform == "win32":
                command_str = subprocess.list2cmdline(gdb_command_parts)
                logger.debug(f"Using PopenSpawn with command string: {command_str}")
                self.child = spawn(command_str, timeout=timeout, encoding='utf-8', cwd=spawn_cwd_for_gdb)
            else:
                logger.debug(f"Using pexpect.spawn with command list: {gdb_command_parts}")
                self.child = spawn(gdb_command_parts[0], args=gdb_command_parts[1:], timeout=timeout, encoding='utf-8', cwd=spawn_cwd_for_gdb)

            initial_prompt_timeout = max(timeout, 15) 
            self.child.expect_exact(self.gdb_prompt, timeout=initial_prompt_timeout)
            output_at_startup = str(self.child.before or "") 
            logger.debug(f"GDB output at pexpect startup (before first prompt):\n{output_at_startup}")
            
            no_symbols_message = "No debugging symbols found"
            if no_symbols_message in output_at_startup:
                self.symbols_found = False
                logger.warning(f"'{no_symbols_message}' detected in GDB startup.")
            else:
                self.symbols_found = True
                logger.info("Debugging symbols appear to be loaded.")
            logger.info("GDB started successfully via pexpect and prompt received.")
            
            pagination_timeout = max(5, timeout // 2)
            logger.info(f"Disabling GDB pagination ('set pagination off'), timeout: {pagination_timeout}s.")
            self.send_cmd("set pagination off", expect_prompt=True, timeout=pagination_timeout)
            logger.info("GDB pagination disabled.")

            if sys.platform == "win32":
                logger.info("Platform is Windows, attempting to 'set new-console on'.")
                try:
                    set_console_timeout = max(5, timeout // 3)
                    output_new_console = self.send_cmd("set new-console on", expect_prompt=True, timeout=set_console_timeout)
                    if "error" in output_new_console.lower() or "unknown" in output_new_console.lower():
                        logger.warning(f"'set new-console on' might have failed. GDB output: {output_new_console.strip()}")
                    else:
                        logger.info("'set new-console on' command sent successfully.")
                except Exception as e_new_console:
                    logger.warning(f"Error sending 'set new-console on': {e_new_console}", exc_info=True)

            if self.gdb_script_path:
                self._set_general_dumper_options_in_gdb(timeout=pagination_timeout)
                self._source_gdb_dumper_script(timeout=pagination_timeout)
            else:
                logger.info("No GDB dumper script path provided; skipping sourcing.")
                self.gdb_script_sourced_successfully = False

        except pexpect.TIMEOUT as e_pexpect_timeout:
            err_msg_detail = f"Timeout ({timeout}s) during GDB session startup with pexpect."
            logger.error(err_msg_detail, exc_info=False)
            current_buffer_content = str(e_pexpect_timeout.get_trace())
            logger.error(f"GDB output details before pexpect timeout:\n{current_buffer_content.strip()}")
            self._force_close_child()
            raise TimeoutError(err_msg_detail) from e_pexpect_timeout
        
        except pexpect.exceptions.ExceptionPexpect as e_fatal_pexpect_start:
            logger.error(f"Unexpected critical error during GDB pexpect startup: {type(e_fatal_pexpect_start).__name__}: {e_fatal_pexpect_start}", exc_info=True)
            self._force_close_child()
            raise ConnectionError(f"Failed to start GDB process: {e_fatal_pexpect_start}") from e_fatal_pexpect_start
            
    def _set_general_dumper_options_in_gdb(
        self, timeout: int = DEFAULT_GDB_OPERATION_TIMEOUT
    ) -> None:
        if not self.dumper_options:
            logger.debug("No general dumper options provided to GDBSession to set in GDB.")
            # Do not return early, still need to set log dir

        if not self.is_alive():
            logger.warning("Cannot set dumper options: GDB session not active.")
            return

        settings_to_set_in_gdb: Dict[str, Any] = self.dumper_options.copy()

        if self.app_log_dir_for_dumper and os.path.isdir(self.app_log_dir_for_dumper):
            settings_to_set_in_gdb["script_log_dir"] = self.app_log_dir_for_dumper
            logger.info(
                f"Will attempt to set $py_dumper_script_log_dir to: {self.app_log_dir_for_dumper}"
            )
        else:
            logger.warning(
                "$py_dumper_script_log_dir not set: app_log_dir_for_dumper is invalid or None."
            )

        if not settings_to_set_in_gdb:
            logger.debug(
                "No effective dumper options (including log dir) to set in GDB."
            )
            return

        logger.info(
            f"Setting GDB dumper options with timeout: {timeout}s. Options: {settings_to_set_in_gdb}"
        )
        for key_suffix, value in settings_to_set_in_gdb.items():
            # These are set per-dump, not globally at startup.
            if key_suffix in ["target_output_filepath", "target_output_format"]:
                continue

            gdb_var_name = f"$py_dumper_{key_suffix}"
            set_command = ""
            if isinstance(value, bool):
                set_command = f"set {gdb_var_name} = {1 if value else 0}"
            elif isinstance(value, (int, float)):
                set_command = f"set {gdb_var_name} = {value}"
            elif isinstance(value, str):
                normalized_value_for_gdb = str(value).replace("\\", "/")
                set_command = f'set {gdb_var_name} = "{normalized_value_for_gdb}"'
            else:
                logger.warning(
                    f"Skipping GDB var for dumper option '{key_suffix}': value '{value}' type {type(value)} not directly supported for GDB set."
                )
                continue

            try:
                logger.debug(f"Setting GDB dumper option: {set_command}")
                self.send_cmd(set_command, expect_prompt=True, timeout=timeout)
            except Exception as e_set:
                logger.error(
                    f"Failed to set GDB dumper option '{gdb_var_name}' to '{value}': {e_set}",
                    exc_info=True,
                )

    def _set_per_dump_gdb_variables(
        self,
        target_filepath: Optional[str],
        target_format: Optional[str],
        timeout: int = DEFAULT_GDB_OPERATION_TIMEOUT,
    ) -> None:
        if not self.is_alive():
            logger.warning("Cannot set per-dump variables: GDB session not active.")
            return

        settings_to_apply: List[Tuple[str, str]] = []
        if target_filepath is not None:
            normalized_path = target_filepath.replace("\\", "/")
            settings_to_apply.append(
                ("$py_dumper_target_output_filepath", f'"{normalized_path}"')
            )
        else:
            settings_to_apply.append(("$py_dumper_target_output_filepath", '""'))
            
        if target_format is not None:
            settings_to_apply.append(
                ("$py_dumper_target_output_format", f'"{target_format.lower()}"')
            )
        else:
            settings_to_apply.append(("$py_dumper_target_output_format", '"json"'))
            
        if not settings_to_apply:
            return
            
        logger.info(f"Setting per-dump GDB variables with timeout: {timeout}s")
        for var_name, value_str in settings_to_apply:
            set_command = f"set {var_name} = {value_str}"
            try:
                logger.debug(f"Setting GDB per-dump variable: {set_command}")
                self.send_cmd(set_command, expect_prompt=True, timeout=timeout)
            except Exception as e:
                logger.error(
                    f"Failed to set GDB per-dump variable '{var_name}': {e}",
                    exc_info=True,
                )

    def _source_gdb_dumper_script(self, timeout: int = DEFAULT_GDB_OPERATION_TIMEOUT) -> None:
        if not self.is_alive():
            logger.warning("Cannot source GDB script: GDB session not active.")
            self.gdb_script_sourced_successfully = False
            return
            
        if not self.gdb_script_path:
            logger.info("No GDB dumper script path provided, skipping sourcing.")
            self.gdb_script_sourced_successfully = False
            return

        normalized_script_path = self.gdb_script_path.replace("\\", "/")
        logger.info(f"Sourcing GDB Python script: {normalized_script_path} with timeout: {timeout}s")
        source_command = f"source {normalized_script_path}"

        try:
            raw_output_from_source = self.send_cmd(
                source_command, expect_prompt=True, timeout=timeout
            )
            error_detected = False
            python_exception_patterns = [
                r"Traceback \(most recent call last\):",
                r"Python Exception <class '.*'>:",
                r"Error occurred in Python:",
                r"SyntaxError:",
                r"IndentationError:",
            ]
            gdb_error_patterns = [
                r"No such file or directory",
                r"^Error:", 
            ]
            for pattern in python_exception_patterns:
                if re.search(pattern, raw_output_from_source, re.MULTILINE | re.IGNORECASE):
                    logger.error(f"Python error detected while sourcing GDB script '{normalized_script_path}'.")
                    error_detected = True
                    break
            
            if not error_detected:
                for pattern in gdb_error_patterns:
                    for line_content in raw_output_from_source.splitlines():
                        if re.search(pattern, line_content.strip(), re.IGNORECASE):
                            logger.error(f"GDB error pattern '{pattern}' detected sourcing script.")
                            error_detected = True
                            break
                    if error_detected:
                        break
            
            success_marker = "GDB_DUMPER_SCRIPT: End of script reached"
            
            if not error_detected and success_marker in raw_output_from_source:
                logger.info(f"GDB script '{normalized_script_path}' sourced successfully (success marker found).")
                self.gdb_script_sourced_successfully = True
            else:
                log_reason = "error pattern detected" if error_detected else "success marker not found"
                logger.error(
                    f"GDB dumper script '{normalized_script_path}' FAILED to source ({log_reason}). "
                    f"Full output from GDB:\n{raw_output_from_source.strip()}"
                )
                self.gdb_script_sourced_successfully = False

        except (ConnectionError, TimeoutError) as e:
            logger.error(
                f"A communication error occurred during 'source' of GDB script '{normalized_script_path}': {e}",
                exc_info=True,
            )
            self.gdb_script_sourced_successfully = False
        except Exception as e:
            logger.error(
                f"An unexpected exception occurred during 'source' of GDB script '{normalized_script_path}': {e}",
                exc_info=True,
            )
            self.gdb_script_sourced_successfully = False

    def send_cmd(
        self,
        command: str,
        expect_prompt: bool = True,
        timeout: int = DEFAULT_GDB_OPERATION_TIMEOUT,
    ) -> str:
        if not self.is_alive():
            raise ConnectionError("GDB session is not active.")
            
        logger.debug(f"Sending GDB cmd: '{command}', timeout: {timeout}s")
        try:
            self.child.sendline(command)
        except Exception as e_send:
            logger.error(f"Error sending '{command}' to GDB process: {e_send}")
            raise ConnectionError(
                f"Failed to send command to GDB: {e_send}"
            ) from e_send
            
        if expect_prompt:
            try:
                index = self.child.expect_exact(
                    [self.gdb_prompt, pexpect.EOF, pexpect.TIMEOUT], timeout=timeout
                )
                output_before = str(self.child.before or "")
                if index == 0:
                    logger.debug(
                        f"GDB output for '{command}':\n{output_before.strip() or '<empty>'}"
                    )
                    return output_before
                elif index == 1:
                    logger.error(
                        f"GDB session ended (EOF) after command: '{command}'. Output: {output_before.strip() or '<empty>'}"
                    )
                    self._force_close_child()
                    raise pexpect.EOF(f"GDB session ended (EOF) after command: {command}")
                elif index == 2:
                    current_output = output_before + str(self.child.buffer or "")
                    logger.error(
                        f"Timeout ({timeout}s) waiting for prompt after GDB command: '{command}'. Partial output: {current_output.strip() or '<empty>'}"
                    )
                    raise TimeoutError(
                        f"Timeout ({timeout}s) on GDB command: '{command}'. Partial output: {current_output.strip() or '<empty>'}"
                    )
            except (pexpect.TIMEOUT, TimeoutError) as eto:
                raise TimeoutError(f"Timeout on GDB command: {command}") from eto
            except pexpect.EOF as eeof:
                logger.error(f"GDB session ended unexpectedly (EOF) during command '{command}': {eeof}", exc_info=True)
                self._force_close_child()
                raise
            except Exception as e:
                logger.error(f"Generic error waiting for prompt after GDB command '{command}': {e}", exc_info=True)
                raise ConnectionError(f"Error interacting with GDB after command '{command}': {e}") from e
        return ""

    def set_breakpoint(
        self, location: str, timeout: int = DEFAULT_GDB_OPERATION_TIMEOUT
    ) -> str:
        logger.info(f"Setting breakpoint at: {location}, timeout {timeout}s")
        return self.send_cmd(f"break {location}", timeout=timeout)

    def run_program(
        self, params: str = "", timeout: int = DEFAULT_LONG_GDB_OPERATION_TIMEOUT
    ) -> str:
        cmd = "run"
        if params:
            cmd += f" {params.strip()}"
        logger.info(f"Running program in GDB: '{cmd}', timeout {timeout}s")
        return self.send_cmd(cmd, timeout=timeout)

    def continue_execution(
        self, timeout: int = DEFAULT_LONG_GDB_OPERATION_TIMEOUT
    ) -> str:
        logger.info(f"Continuing GDB execution, timeout {timeout}s.")
        return self.send_cmd("continue", timeout=timeout)

    def dump_variable_to_json(
        self,
        var_name: str,
        timeout: int = DEFAULT_GDB_OPERATION_TIMEOUT,
        target_output_filepath: Optional[str] = None,
        target_output_format: Optional[str] = "json",
    ) -> Dict[str, Any]:
        if not self.gdb_script_sourced_successfully:
            return {
                "gdb_tool_error": "GDB dumper script not loaded.",
                "status": "error",
                "variable_dumped": var_name,
            }
        logger.info(
            f"Dumping '{var_name}'. Target: '{target_output_filepath}', Format: '{target_output_format}'. Timeout: {timeout}s."
        )
        try:
            self._set_per_dump_gdb_variables(
                target_filepath=target_output_filepath,
                target_format=target_output_format,
                timeout=max(5, timeout // 3),
            )
        except Exception as e_setvar:
            return {
                "gdb_tool_error": "Failed to set GDB vars for dumper",
                "status": "error",
                "variable_dumped": var_name,
                "details": str(e_setvar),
            }
        try:
            raw_gdb_output = self.send_cmd(
                f"dump_json {var_name}", expect_prompt=True, timeout=timeout
            )
            logger.debug(
                f"Full raw_gdb_output for 'dump_json {var_name}':\n{raw_gdb_output}"
            )
            start_delim_esc = re.escape("START_JSON_OUTPUT_DELIMITER_SEQUENCE_v1")
            end_delim_esc = re.escape("END_JSON_OUTPUT_DELIMITER_SEQUENCE_v1")
            match = re.search(
                f"{start_delim_esc}\\s*([\\s\\S]*?)\\s*{end_delim_esc}",
                raw_gdb_output,
                re.DOTALL,
            )
            if match:
                json_str_candidate = match.group(1)
                json_str = json_str_candidate.strip()
                try:
                    parsed_data = json.loads(json_str)
                    if not isinstance(parsed_data, dict) or "status" not in parsed_data:
                        logger.error(f"Unexpected JSON structure from 'dump_json' for '{var_name}'.")
                        return {"gdb_tool_error": "Unexpected JSON from dumper", "status": "error", "variable_dumped": var_name, "raw_response": json_str}
                    if parsed_data.get("status") == "error":
                        logger.error(f"Error from GDB dumper for '{var_name}': {parsed_data.get('message','N/A')}")
                    return parsed_data
                except json.JSONDecodeError as jde:
                    logger.error(f"Failed to decode JSON from 'dump_json {var_name}'. Error: {jde}. Raw: '{json_str}'")
                    return {"gdb_tool_error": "JSONDecodeError from GDB status", "status": "error", "variable_dumped": var_name, "details": str(jde), "raw_response": json_str}
            else:
                logger.error(f"Delimiters not found in 'dump_json {var_name}'. Raw output was:\n{raw_gdb_output}")
                return {"gdb_tool_error": "JSON delimiters not found", "status": "error", "variable_dumped": var_name, "raw_gdb_output": raw_gdb_output}
        except TimeoutError:
            logger.error(f"Timeout dumping '{var_name}'.")
            return {"gdb_tool_error": f"Timeout on GDB 'dump_json {var_name}' command", "status": "error", "variable_dumped": var_name}
        except Exception as e:
            logger.error(f"Generic exception dumping '{var_name}': {e}", exc_info=True)
            return {"gdb_tool_error": f"Generic exception on 'dump_json {var_name}': {str(e)}", "status": "error", "variable_dumped": var_name}

    def kill_program(self, timeout: int = DEFAULT_GDB_OPERATION_TIMEOUT) -> str:
        logger.info(f"Sending 'kill' command to GDB, timeout {timeout}s.")
        full_out = ""
        if not self.is_alive():
            logger.warning("Cannot send 'kill', GDB session is not active.")
            return "<kill_cmd_err_no_session>"
        try:
            self.child.sendline("kill")
            full_out += "kill\n"
            pats = [
                re.compile(r"Kill the program.*\(y or n\)\s*"),
                re.compile(re.escape(self.gdb_prompt)),
                pexpect.EOF,
                pexpect.TIMEOUT,
            ]
            conf_to = max(5, timeout // 2)
            logger.debug(f"Kill: Expecting confirmation or prompt, timeout {conf_to}s")
            idx = self.child.expect(pats, timeout=conf_to)
            out_seg = str(self.child.before or "")
            full_out += out_seg
            if idx == 0:
                logger.info("Kill: GDB confirmation prompt received. Sending 'y'.")
                self.child.sendline("y")
                full_out += "y\n"
                logger.debug(f"Kill: Expecting prompt after 'y', timeout {conf_to}s")
                self.child.expect_exact(self.gdb_prompt, timeout=conf_to)
                full_out += str(self.child.before or "")
                logger.info("Kill: Program kill confirmed and acknowledged by GDB.")
            elif idx == 1:
                logger.info("Kill: GDB returned to prompt directly (program may not have been running).")
            elif idx == 2:
                logger.warning("Kill: GDB session ended (EOF) during 'kill' command.")
                self.child = None
                full_out += "<EOF_during_kill>"
            elif idx == 3:
                logger.error(f"Kill: Timeout waiting for confirmation. Last output: {out_seg.strip()}")
                full_out += "<Timeout_kill_confirm>"
            return full_out.strip()
        except (TimeoutError, pexpect.EOF, ConnectionError) as e:
            logger.warning(f"Kill: Communication error '{type(e).__name__}': {e}. Output so far: {full_out.strip()}")
            return f"<kill_exc:{type(e).__name__}:{e}. Output:{full_out.strip()}>"
        except Exception as e:
            logger.error(f"Kill: Unexpected error: {e}. Output so far: {full_out.strip()}", exc_info=True)
            return f"<kill_unexpected_err:{e}. Output:{full_out.strip()}>"

    def quit(self, timeout: int = DEFAULT_GDB_OPERATION_TIMEOUT) -> None:
        if self.is_alive():
            logger.info(f"Attempting graceful GDB quit, timeout {timeout}s.")
            phase_to = max(3, timeout // 3)
            try:
                self.child.sendline("quit")
                logger.debug("Quit: Sent 'quit' command to GDB.")
                patterns_for_quit = [
                    re.compile(re.escape(self.gdb_prompt)),
                    re.compile(r"Quit anyway\s*\?\s*\(y or n\)\s*"),
                    pexpect.EOF,
                    pexpect.TIMEOUT,
                ]
                logger.debug(f"Quit: Waiting for GDB response, timeout {phase_to}s")
                index = self.child.expect(patterns_for_quit, timeout=phase_to)
                if index == 1:
                    logger.info("Quit: GDB is asking for confirmation. Sending 'y'.")
                    self.child.sendline("y")
                    # After sending 'y', we expect EOF
                    self.child.expect(pexpect.EOF, timeout=phase_to)
            except Exception as e_quit_sequence:
                logger.error(f"Quit: Exception during GDB quit sequence: {e_quit_sequence}", exc_info=True)
            finally:
                if self.is_alive():
                    self._force_close_child()
                self.gdb_script_sourced_successfully = False
                logger.info("Quit: GDB session resources have been released.")
        else:
            logger.info("Quit: Called, but no active GDB session to quit.")