"""
Main GUI window for HBAT application.

This module provides the main tkinter interface for the HBAT application,
allowing users to load PDB files, configure analysis parameters, and view results.
"""

import asyncio
import csv
import json
import os
import tkinter as tk
import webbrowser
from pathlib import Path
from tkinter import filedialog, messagebox, ttk
from typing import Any, Dict, Optional

import tk_async_execute as tae

from ..constants import APP_NAME, APP_VERSION, GUIDefaults
from ..constants.parameters import ParametersDefault
from ..core.analysis import (
    AnalysisParameters,
    NPMolecularInteractionAnalyzer,
)
from .geometry_cutoffs_dialog import GeometryCutoffsDialog
from .pdb_fixing_dialog import PDBFixingDialog
from .results_panel import ResultsPanel


class MainWindow:
    """Main application window for HBAT.

    This class provides the primary GUI interface for HBAT, including
    file loading, parameter configuration, analysis execution, and
    results visualization.

    :param None: This class takes no parameters during initialization
    """

    def __init__(self) -> None:
        """Initialize the main window.

        Sets up the complete GUI interface including menus, toolbar,
        main content area, and status bar.

        :returns: None
        :rtype: None
        """
        # Initialize HBAT environment first
        try:
            from ..core.app_config import get_hbat_config, initialize_hbat_environment

            initialize_hbat_environment(verbose=True)
            self.hbat_config = get_hbat_config()
        except ImportError:
            self.hbat_config = None

        self.root = tk.Tk()
        self.root.title(f"{APP_NAME} v{APP_VERSION}")
        self.root.geometry(f"{GUIDefaults.WINDOW_WIDTH}x{GUIDefaults.WINDOW_HEIGHT}")
        self.root.minsize(GUIDefaults.MIN_WINDOW_WIDTH, GUIDefaults.MIN_WINDOW_HEIGHT)

        # Analysis components
        self.analyzer: Optional[NPMolecularInteractionAnalyzer] = None
        self.current_file: Optional[str] = None
        self.analysis_running = False

        # Create UI components
        self._create_menu()
        self._create_toolbar()
        self._create_main_content()
        self._create_status_bar()

        # Set up event handlers
        self.root.protocol("WM_DELETE_WINDOW", self._on_closing)

        # Initialize async executor
        tae.start()

    def _create_menu(self) -> None:
        """Create the menu bar.

        Sets up the application menu with File, Analysis, Tools, and Help menus,
        including keyboard shortcuts and event bindings.

        :returns: None
        :rtype: None
        """
        menubar = tk.Menu(self.root)
        self.root.config(menu=menubar)
        self.menubar = menubar  # Store reference for state updates

        # File menu
        file_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="File", menu=file_menu)
        file_menu.add_command(
            label="Open PDB File...", accelerator="Ctrl+O", command=self._open_file
        )
        file_menu.add_separator()
        file_menu.add_command(
            label="Save Results...", accelerator="Ctrl+S", command=self._save_results
        )
        file_menu.add_command(
            label="Save Fixed PDB...",
            accelerator="Ctrl+Shift+S",
            command=self._save_fixed_pdb,
        )
        file_menu.add_separator()
        file_menu.add_command(
            label="Export All...", accelerator="Ctrl+E", command=self._export_all
        )
        file_menu.add_separator()
        file_menu.add_command(
            label="Exit", accelerator="Ctrl+Q", command=self._on_closing
        )

        # Analysis menu
        analysis_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="Analysis", menu=analysis_menu)
        self.analysis_menu = analysis_menu  # Store reference
        analysis_menu.add_command(
            label="Run Analysis",
            accelerator="F5",
            command=self._run_analysis,
            state=tk.DISABLED,
        )
        self.run_analysis_index = 0  # Index of "Run Analysis" menu item
        analysis_menu.add_command(label="Clear Results", command=self._clear_results)

        # Settings menu
        settings_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="Settings", menu=settings_menu)
        settings_menu.add_command(
            label="Geometry Cutoffs...", command=self._open_parameters_window
        )
        settings_menu.add_command(
            label="PDB Fixing...", command=self._open_pdb_fixing_window
        )
        settings_menu.add_separator()
        settings_menu.add_command(
            label="Manage Presets...", command=self._open_preset_manager
        )
        settings_menu.add_separator()
        settings_menu.add_command(
            label="Reset Parameters", command=self._reset_parameters
        )

        # Help menu
        help_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="Help", menu=help_menu)
        help_menu.add_command(label="About", command=self._show_about)
        help_menu.add_command(label="User Guide", command=self._show_help)

        # Bind keyboard shortcuts
        self.root.bind("<Control-o>", lambda e: self._open_file())
        self.root.bind("<Control-s>", lambda e: self._save_results())
        self.root.bind("<Control-e>", lambda e: self._export_all())
        self.root.bind("<Control-q>", lambda e: self._on_closing())
        self.root.bind("<F5>", lambda e: self._run_analysis())
        self.root.bind("<Control-Shift-S>", lambda e: self._save_fixed_pdb())

    def _create_toolbar(self) -> None:
        """Create the toolbar.

        Creates a toolbar with progress bar and performance indicator.
        The toolbar is hidden by default and only shown during operations.

        :returns: None
        :rtype: None
        """
        self.toolbar = ttk.Frame(self.root)
        # Don't pack the toolbar initially - it will be shown when needed

        # Performance indicator
        self.performance_label = ttk.Label(
            self.toolbar,
            text="⚡",
            foreground="green",
        )

        # Progress bar
        self.progress_var = tk.DoubleVar(master=self.root)
        self.progress_bar = ttk.Progressbar(
            self.toolbar, variable=self.progress_var, mode="indeterminate"
        )

    def _create_main_content(self) -> None:
        """Create the main content area.

        Sets up the main interface with a vertical paned window containing PDB file
        content on top (30%) and results display area below (70%).

        :returns: None
        :rtype: None
        """
        # Create main paned window with vertical orientation
        main_paned = ttk.PanedWindow(self.root, orient=tk.VERTICAL)
        main_paned.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

        # Top panel - PDB File content (30% of height)
        top_frame = ttk.Frame(main_paned)
        main_paned.add(top_frame, weight=3)  # 30% weight ratio

        # Create notebook for PDB file tabs
        self.left_notebook = ttk.Notebook(top_frame)
        self.left_notebook.pack(fill=tk.BOTH, expand=True)

        # File content tab
        file_frame = ttk.Frame(self.left_notebook)
        self.left_notebook.add(file_frame, text="PDB File")

        # Create text widget with both vertical and horizontal scrollbars
        text_frame = ttk.Frame(file_frame)
        text_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

        self.file_text = tk.Text(text_frame, wrap=tk.NONE, font=("Courier", 12))
        file_v_scrollbar = ttk.Scrollbar(
            text_frame, orient=tk.VERTICAL, command=self.file_text.yview
        )
        file_h_scrollbar = ttk.Scrollbar(
            text_frame, orient=tk.HORIZONTAL, command=self.file_text.xview
        )
        self.file_text.configure(
            yscrollcommand=file_v_scrollbar.set, xscrollcommand=file_h_scrollbar.set
        )

        self.file_text.grid(row=0, column=0, sticky="nsew")
        file_v_scrollbar.grid(row=0, column=1, sticky="ns")
        file_h_scrollbar.grid(row=1, column=0, sticky="ew")

        text_frame.grid_rowconfigure(0, weight=1)
        text_frame.grid_columnconfigure(0, weight=1)

        # Fixed PDB content tab
        fixed_file_frame = ttk.Frame(self.left_notebook)
        self.left_notebook.add(fixed_file_frame, text="Fixed PDB")

        # Create text widget with both vertical and horizontal scrollbars
        fixed_text_frame = ttk.Frame(fixed_file_frame)
        fixed_text_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

        self.fixed_file_text = tk.Text(
            fixed_text_frame, wrap=tk.NONE, font=("Courier", 12)
        )
        fixed_v_scrollbar = ttk.Scrollbar(
            fixed_text_frame, orient=tk.VERTICAL, command=self.fixed_file_text.yview
        )
        fixed_h_scrollbar = ttk.Scrollbar(
            fixed_text_frame, orient=tk.HORIZONTAL, command=self.fixed_file_text.xview
        )
        self.fixed_file_text.configure(
            yscrollcommand=fixed_v_scrollbar.set, xscrollcommand=fixed_h_scrollbar.set
        )

        self.fixed_file_text.grid(row=0, column=0, sticky="nsew")
        fixed_v_scrollbar.grid(row=0, column=1, sticky="ns")
        fixed_h_scrollbar.grid(row=1, column=0, sticky="ew")

        fixed_text_frame.grid_rowconfigure(0, weight=1)
        fixed_text_frame.grid_columnconfigure(0, weight=1)

        # Add context menu for Fixed PDB tab
        self._create_fixed_pdb_context_menu()

        # Store parameters for session persistence
        self.session_parameters = None

        # Bottom panel - Results (70% of height)
        bottom_frame = ttk.Frame(main_paned)
        main_paned.add(bottom_frame, weight=7)  # 70% weight ratio

        self.results_panel = ResultsPanel(bottom_frame)

        # Set initial pane positions (30% for top, 70% for bottom)
        # Position the sash at 30% of the total height
        main_paned.update_idletasks()  # Ensure the window is rendered
        total_height = self.root.winfo_height() - 60  # Account for padding and menu
        if total_height > 0:
            sash_position = int(total_height * 0.3)
            main_paned.sashpos(0, sash_position)

    def _create_status_bar(self) -> None:
        """Create the status bar.

        Creates a status bar at the bottom of the window to display
        application state and progress information.

        :returns: None
        :rtype: None
        """
        self.status_var = tk.StringVar(master=self.root)
        self.status_var.set("Ready")

        status_frame = ttk.Frame(self.root)
        status_frame.pack(fill=tk.X, side=tk.BOTTOM)

        ttk.Label(
            status_frame, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W
        ).pack(fill=tk.X, padx=2, pady=1)

    def _open_file(self) -> None:
        """Open a PDB file.

        Displays a file dialog to select a PDB file, loads its content,
        and enables analysis functionality.

        :returns: None
        :rtype: None
        """
        filename = filedialog.askopenfilename(
            title="Open PDB File",
            filetypes=[("PDB files", "*.pdb"), ("All files", "*.*")],
        )

        if filename:
            try:
                self.current_file = filename
                self._load_file_content(filename)
                # Enable "Run Analysis" menu item
                self.analysis_menu.entryconfig(self.run_analysis_index, state=tk.NORMAL)
                self.status_var.set(f"Loaded: {os.path.basename(filename)}")
                self._clear_results()
                self._clear_fixed_pdb_content()

            except Exception as e:
                messagebox.showerror("Error", f"Failed to load file:\n{str(e)}")
                self.status_var.set("Error loading file")

    def _load_file_content(self, filename: str) -> None:
        """Load and display file content.

        Reads the PDB file content and displays it in the text widget
        with syntax highlighting for PDB record types.

        :param filename: Path to the PDB file to load
        :type filename: str
        :returns: None
        :rtype: None
        :raises Exception: If file cannot be read
        """
        try:
            # Show progress for large files
            self._show_loading_progress("Loading file...")

            # Load file in chunks to prevent GUI freezing
            self._load_file_in_chunks(filename)

            # Add to recent files if config is available
            if self.hbat_config:
                self.hbat_config.add_recent_file(filename)

        except Exception as e:
            raise Exception(f"Cannot read file: {e}")
        finally:
            self._hide_loading_progress()

    def _load_file_in_chunks(self, filename: str) -> None:
        """Load file content in chunks to prevent GUI freezing.

        :param filename: Path to the PDB file to load
        :type filename: str
        :returns: None
        :rtype: None
        """
        chunk_size = 50000  # Process in 50KB chunks
        self.file_text.delete(1.0, tk.END)

        try:
            with open(filename, "r") as file:
                while True:
                    chunk = file.read(chunk_size)
                    if not chunk:
                        break

                    # Insert chunk and update display
                    self.file_text.insert(tk.END, chunk)
                    self.root.update_idletasks()  # Process pending GUI events

            # Apply syntax highlighting after loading
            self.root.after_idle(self._highlight_pdb_records)

        except Exception as e:
            raise Exception(f"Cannot read file: {e}")

    def _show_loading_progress(self, message: str) -> None:
        """Show loading progress indicator.

        :param message: Status message to display
        :type message: str
        :returns: None
        :rtype: None
        """
        self.status_var.set(message)
        if not self.toolbar.winfo_ismapped():
            self.toolbar.pack(fill=tk.X, padx=5, pady=2)
        self.progress_bar.pack(fill=tk.BOTH, padx=5, expand=True)
        self.progress_bar.config(mode="indeterminate")
        self.progress_bar.start(GUIDefaults.PROGRESS_BAR_INTERVAL)
        self.root.update_idletasks()

    def _hide_loading_progress(self) -> None:
        """Hide loading progress indicator.

        :returns: None
        :rtype: None
        """
        self.progress_bar.stop()
        self.progress_bar.pack_forget()
        if self.toolbar.winfo_ismapped():
            self.toolbar.pack_forget()

    def _highlight_pdb_records(self) -> None:
        """Highlight important PDB record types.

        Applies color coding to different PDB record types (ATOM, HETATM,
        HEADER, etc.) for better readability.

        :returns: None
        :rtype: None
        """
        # Configure text tags
        self.file_text.tag_configure("atom", foreground="blue")
        self.file_text.tag_configure("hetatm", foreground="red")
        self.file_text.tag_configure(
            "header", foreground="green", font=("Courier", 12, "bold")
        )

        content = self.file_text.get(1.0, tk.END)
        lines = content.split("\n")

        for i, line in enumerate(lines):
            line_start = f"{i+1}.0"
            line_end = f"{i+1}.end"

            if line.startswith("ATOM"):
                self.file_text.tag_add("atom", line_start, line_end)
            elif line.startswith("HETATM"):
                self.file_text.tag_add("hetatm", line_start, line_end)
            elif line.startswith(("HEADER", "TITLE", "COMPND")):
                self.file_text.tag_add("header", line_start, line_end)

    def _run_analysis(self) -> None:
        """Run the molecular interaction analysis.

        Initiates analysis using async/await pattern to keep GUI responsive.

        :returns: None
        :rtype: None
        """
        if not self.current_file:
            messagebox.showwarning("Warning", "Please open a PDB file first.")
            return

        if self.analysis_running:
            messagebox.showinfo("Info", "Analysis is already running.")
            return

        # Get parameters from session storage or use defaults
        if self.session_parameters:
            params = self.session_parameters
        else:
            # Use default parameters if none have been set
            params = AnalysisParameters()
            self.session_parameters = params

        # Apply PDB fixing parameters if available
        # Note: Default AnalysisParameters() should have PDB fixing enabled by default
        if (
            hasattr(self, "session_pdb_fixing_params")
            and self.session_pdb_fixing_params
        ):
            # Update the params object with session PDB fixing settings
            params.fix_pdb_enabled = self.session_pdb_fixing_params["enabled"]
            params.fix_pdb_method = self.session_pdb_fixing_params["method"]
            params.fix_pdb_add_hydrogens = self.session_pdb_fixing_params[
                "add_hydrogens"
            ]
            params.fix_pdb_add_heavy_atoms = self.session_pdb_fixing_params[
                "add_heavy_atoms"
            ]
            params.fix_pdb_replace_nonstandard = self.session_pdb_fixing_params[
                "replace_nonstandard"
            ]
            params.fix_pdb_remove_heterogens = self.session_pdb_fixing_params[
                "remove_heterogens"
            ]
            params.fix_pdb_keep_water = self.session_pdb_fixing_params["keep_water"]

        # Start async analysis without popup window
        tae.async_execute(
            self._perform_analysis_async(params), visible=False, show_exceptions=False
        )

    async def _perform_analysis_async(self, params: AnalysisParameters) -> None:
        """Perform the analysis asynchronously to keep GUI responsive.

        :param params: Analysis parameters to use
        :type params: AnalysisParameters
        :returns: None
        :rtype: None
        """
        try:
            # Set up UI for analysis
            self.analysis_running = True
            self.analysis_menu.entryconfig(self.run_analysis_index, state=tk.DISABLED)

            # Show toolbar and progress bar
            if not self.toolbar.winfo_ismapped():
                self.toolbar.pack(fill=tk.X, padx=5, pady=2)
            self.performance_label.pack(side=tk.LEFT, padx=5)
            self.progress_bar.pack(fill=tk.BOTH, padx=5, expand=True)
            self.progress_bar.config(mode="indeterminate")
            self.progress_bar.start(GUIDefaults.PROGRESS_BAR_INTERVAL)
            self.status_var.set("Running analysis...")

            # Create analyzer
            self.analyzer = NPMolecularInteractionAnalyzer(params)

            # Set up progress callback for direct GUI updates
            def progress_callback(message: str) -> None:
                # Update progress directly without creating new async task
                self.root.after(0, lambda: self.status_var.set(message))

            self.analyzer.progress_callback = progress_callback

            # Run analysis in executor to avoid blocking
            success = await asyncio.get_event_loop().run_in_executor(
                None, self.analyzer.analyze_file, self.current_file
            )

            if success:
                await self._analysis_complete_async()
            else:
                await self._analysis_error_async("Analysis failed")

        except Exception as e:
            await self._analysis_error_async(str(e))

    async def _update_progress_async(self, message: str) -> None:
        """Update progress message asynchronously.

        :param message: Progress message to display
        :type message: str
        :returns: None
        :rtype: None
        """
        self.status_var.set(message)

    async def _analysis_complete_async(self) -> None:
        """Handle successful analysis completion asynchronously.

        :returns: None
        :rtype: None
        """
        self.analysis_running = False
        self.progress_bar.stop()
        self.progress_bar.config(mode="determinate")
        self.progress_var.set(0)
        # Hide progress bar, performance label, and toolbar
        self.progress_bar.pack_forget()
        self.performance_label.pack_forget()
        self.toolbar.pack_forget()
        self.analysis_menu.entryconfig(self.run_analysis_index, state=tk.NORMAL)

        # Update results panel
        self.results_panel.update_results(self.analyzer)

        # Update Fixed PDB tab if PDB fixing was applied
        self._update_fixed_pdb_content()

        # Update status
        summary = self.analyzer.get_summary()
        self.status_var.set(
            f"Analysis complete - H-bonds: {summary['hydrogen_bonds']['count']}, "
            f"X-bonds: {summary['halogen_bonds']['count']}, π-interactions: {summary['pi_interactions']['count']}"
        )

        messagebox.showinfo("Success", "Analysis completed successfully!")

    async def _analysis_error_async(self, error_msg: str) -> None:
        """Handle analysis error asynchronously.

        :param error_msg: Error message to display
        :type error_msg: str
        :returns: None
        :rtype: None
        """
        self.analysis_running = False
        self.progress_bar.stop()
        self.progress_bar.config(mode="determinate")
        self.progress_var.set(0)
        # Hide progress bar, performance label, and toolbar
        self.progress_bar.pack_forget()
        self.performance_label.pack_forget()
        self.toolbar.pack_forget()
        self.analysis_menu.entryconfig(self.run_analysis_index, state=tk.NORMAL)
        self.status_var.set("Analysis failed")
        messagebox.showerror("Analysis Error", f"Analysis failed:\n{error_msg}")

    def _clear_results(self) -> None:
        """Clear analysis results.

        Clears all analysis results from the interface and resets
        the analyzer state.

        :returns: None
        :rtype: None
        """
        self.results_panel.clear_results()
        self.analyzer = None
        self._clear_fixed_pdb_content()
        self.status_var.set("Results cleared")

    def _save_results(self) -> None:
        """Save analysis results to file.

        Displays a file dialog to save analysis results in CSV or JSON format.
        Requires completed analysis to function.

        :returns: None
        :rtype: None
        """
        if not self.analyzer:
            messagebox.showwarning("Warning", "No results to save. Run analysis first.")
            return

        filename = filedialog.asksaveasfilename(
            title="Save Results",
            defaultextension=".csv",
            filetypes=[
                ("CSV files", "*.csv"),
                ("JSON files", "*.json"),
            ],
        )

        if filename:
            try:
                file_path = Path(filename)
                extension = file_path.suffix.lower()

                self._export_results_to_file(filename)

                # Show appropriate success message based on format
                if extension in [".csv", ".json"]:
                    base_name = file_path.stem
                    directory = file_path.parent
                    messagebox.showinfo(
                        "Success",
                        f"Results saved as separate {extension.upper()} files:\n"
                        f"- {base_name}_h_bonds{extension}\n"
                        f"- {base_name}_x_bonds{extension}\n"
                        f"- {base_name}_pi_interactions{extension}\n"
                        f"- {base_name}_cooperativity_chains{extension}\n\n"
                        f"Location: {directory}",
                    )
                    self.status_var.set(
                        f"Results saved as multiple {extension.upper()} files"
                    )
                else:
                    messagebox.showinfo("Success", f"Results saved to {filename}")
                    self.status_var.set(
                        f"Results saved to {os.path.basename(filename)}"
                    )
            except Exception as e:
                messagebox.showerror("Error", f"Failed to save results:\n{str(e)}")

    def _export_results_to_file(self, filename: str) -> None:
        """Export results to a file.

        Supports multiple formats:
        - .csv: Separate CSV files for each interaction type (default)
        - .json: Separate JSON files for each interaction type

        :param filename: Path to the output file
        :type filename: str
        :returns: None
        :rtype: None
        """
        file_path = Path(filename)
        extension = file_path.suffix.lower()

        if extension == ".json":
            self._export_json_files(file_path)
        else:
            # Default to CSV format
            self._export_csv_files(file_path)

    def _export_text_file(self, filename: str) -> None:
        """Export results to a single text file.

        :param filename: Path to the output file
        :type filename: str
        """
        with open(filename, "w") as f:
            f.write("HBAT Analysis Results\n")
            f.write("=" * 50 + "\n\n")

            if self.current_file:
                f.write(f"Input file: {self.current_file}\n")

            f.write(f"Analysis engine: HBAT\n")
            f.write(f"HBAT version: {APP_VERSION}\n\n")

            # Write summary
            summary = self.analyzer.get_summary()
            f.write("Summary:\n")
            f.write(f"  Hydrogen bonds: {summary['hydrogen_bonds']['count']}\n")
            f.write(f"  Halogen bonds: {summary['halogen_bonds']['count']}\n")
            f.write(f"  π interactions: {summary['pi_interactions']['count']}\n")
            f.write(f"  Total interactions: {summary['total_interactions']}\n\n")

            # Write detailed results
            f.write("Hydrogen Bonds:\n")
            f.write("-" * 30 + "\n")
            for hb in self.analyzer.hydrogen_bonds:
                f.write(f"{hb}\n")

            f.write("\nHalogen Bonds:\n")
            f.write("-" * 30 + "\n")
            for xb in self.analyzer.halogen_bonds:
                f.write(f"{xb}\n")

            f.write("\nπ Interactions:\n")
            f.write("-" * 30 + "\n")
            for pi in self.analyzer.pi_interactions:
                f.write(f"{pi}\n")

            # Write cooperativity chains if available
            if (
                hasattr(self.analyzer, "cooperativity_chains")
                and self.analyzer.cooperativity_chains
            ):
                f.write("\nCooperativity Chains:\n")
                f.write("-" * 30 + "\n")
                for chain in self.analyzer.cooperativity_chains:
                    f.write(f"{chain}\n")

    def _export_csv_files(self, base_path: Path) -> None:
        """Export results to separate CSV files for each interaction type.

        :param base_path: Base path for the files (extension will be replaced)
        :type base_path: Path
        """
        base_name = base_path.stem
        directory = base_path.parent

        # Export hydrogen bonds
        if self.analyzer.hydrogen_bonds:
            hb_file = directory / f"{base_name}_h_bonds.csv"
            self._write_hydrogen_bonds_csv(hb_file)

        # Export halogen bonds
        if self.analyzer.halogen_bonds:
            xb_file = directory / f"{base_name}_x_bonds.csv"
            self._write_halogen_bonds_csv(xb_file)

        # Export π interactions
        if self.analyzer.pi_interactions:
            pi_file = directory / f"{base_name}_pi_interactions.csv"
            self._write_pi_interactions_csv(pi_file)

        # Export cooperativity chains if available
        if (
            hasattr(self.analyzer, "cooperativity_chains")
            and self.analyzer.cooperativity_chains
        ):
            chains_file = directory / f"{base_name}_cooperativity_chains.csv"
            self._write_cooperativity_chains_csv(chains_file)

    def _export_json_files(self, base_path: Path) -> None:
        """Export results to separate JSON files for each interaction type.

        :param base_path: Base path for the files (extension will be replaced)
        :type base_path: Path
        """
        base_name = base_path.stem
        directory = base_path.parent

        # Export hydrogen bonds
        if self.analyzer.hydrogen_bonds:
            hb_file = directory / f"{base_name}_h_bonds.json"
            self._write_hydrogen_bonds_json(hb_file)

        # Export halogen bonds
        if self.analyzer.halogen_bonds:
            xb_file = directory / f"{base_name}_x_bonds.json"
            self._write_halogen_bonds_json(xb_file)

        # Export π interactions
        if self.analyzer.pi_interactions:
            pi_file = directory / f"{base_name}_pi_interactions.json"
            self._write_pi_interactions_json(pi_file)

        # Export cooperativity chains if available
        if (
            hasattr(self.analyzer, "cooperativity_chains")
            and self.analyzer.cooperativity_chains
        ):
            chains_file = directory / f"{base_name}_cooperativity_chains.json"
            self._write_cooperativity_chains_json(chains_file)

    def _write_hydrogen_bonds_csv(self, filename: Path) -> None:
        """Write hydrogen bonds to CSV file."""
        with open(filename, "w", newline="", encoding="utf-8") as csvfile:
            writer = csv.writer(csvfile)
            writer.writerow(
                [
                    "Donor_Residue",
                    "Donor_Atom",
                    "Hydrogen_Atom",
                    "Acceptor_Residue",
                    "Acceptor_Atom",
                    "Distance_Angstrom",
                    "Angle_Degrees",
                    "Donor_Acceptor_Distance_Angstrom",
                    "Bond_Type",
                    "B/S_Interaction",
                    "D-A_Properties",
                ]
            )
            for hb in self.analyzer.hydrogen_bonds:
                writer.writerow(
                    [
                        hb.donor_residue,
                        hb.donor.name,
                        hb.hydrogen.name,
                        hb.acceptor_residue,
                        hb.acceptor.name,
                        f"{hb.distance:.3f}",
                        f"{hb.angle * 180 / 3.14159:.1f}",
                        f"{hb.donor_acceptor_distance:.3f}",
                        hb.bond_type,
                        hb.get_backbone_sidechain_interaction(),
                        hb.donor_acceptor_properties,
                    ]
                )

    def _write_halogen_bonds_csv(self, filename: Path) -> None:
        """Write halogen bonds to CSV file."""
        with open(filename, "w", newline="", encoding="utf-8") as csvfile:
            writer = csv.writer(csvfile)
            writer.writerow(
                [
                    "Halogen_Residue",
                    "Halogen_Atom",
                    "Acceptor_Residue",
                    "Acceptor_Atom",
                    "Distance_Angstrom",
                    "Angle_Degrees",
                    "Bond_Type",
                    "B/S_Interaction",
                    "D-A_Properties",
                ]
            )
            for xb in self.analyzer.halogen_bonds:
                bs_interaction = xb.get_backbone_sidechain_interaction()
                da_properties = xb.donor_acceptor_properties

                writer.writerow(
                    [
                        xb.donor_residue,
                        xb.halogen.name,
                        xb.acceptor_residue,
                        xb.acceptor.name,
                        f"{xb.distance:.3f}",
                        f"{xb.angle * 180 / 3.14159:.1f}",
                        xb.bond_type,
                        bs_interaction,
                        da_properties,
                    ]
                )

    def _write_pi_interactions_csv(self, filename: Path) -> None:
        """Write π interactions to CSV file."""
        with open(filename, "w", newline="", encoding="utf-8") as csvfile:
            writer = csv.writer(csvfile)
            writer.writerow(
                [
                    "Donor_Residue",
                    "Donor_Atom",
                    "Hydrogen_Atom",
                    "Pi_Residue",
                    "Distance_Angstrom",
                    "Angle_Degrees",
                    "B/S_Interaction",
                    "D-A_Properties",
                ]
            )
            for pi in self.analyzer.pi_interactions:
                writer.writerow(
                    [
                        pi.donor_residue,
                        pi.donor.name,
                        pi.hydrogen.name,
                        pi.pi_residue,
                        f"{pi.distance:.3f}",
                        f"{pi.angle * 180 / 3.14159:.1f}",
                        pi.get_backbone_sidechain_interaction(),
                        pi.donor_acceptor_properties,
                    ]
                )

    def _write_cooperativity_chains_csv(self, filename: Path) -> None:
        """Write cooperativity chains to CSV file."""
        with open(filename, "w", newline="", encoding="utf-8") as csvfile:
            writer = csv.writer(csvfile)
            writer.writerow(["Chain_ID", "Chain_Length", "Chain_Type", "Interactions"])
            for i, chain in enumerate(self.analyzer.cooperativity_chains):
                interactions_str = " -> ".join(
                    [
                        f"{interaction.get_donor_residue()}({interaction.get_donor_atom().name if interaction.get_donor_atom() else '?'})"
                        for interaction in chain.interactions
                    ]
                )
                writer.writerow(
                    [i + 1, chain.chain_length, chain.chain_type, interactions_str]
                )

    def _write_hydrogen_bonds_json(self, filename: Path) -> None:
        """Write hydrogen bonds to JSON file."""
        data = {
            "metadata": {
                "input_file": self.current_file,
                "analysis_engine": "HBAT",
                "version": APP_VERSION,
                "interaction_type": "Hydrogen Bonds",
            },
            "interactions": [],
        }

        for hb in self.analyzer.hydrogen_bonds:
            data["interactions"].append(
                {
                    "donor_residue": hb.donor_residue,
                    "donor_atom": hb.donor.name,
                    "hydrogen_atom": hb.hydrogen.name,
                    "acceptor_residue": hb.acceptor_residue,
                    "acceptor_atom": hb.acceptor.name,
                    "distance_angstrom": round(hb.distance, 3),
                    "angle_degrees": round(hb.angle * 180 / 3.14159, 1),
                    "donor_acceptor_distance_angstrom": round(
                        hb.donor_acceptor_distance, 3
                    ),
                    "bond_type": hb.bond_type,
                    "backbone_sidechain_interaction": hb.get_backbone_sidechain_interaction(),
                    "donor_acceptor_properties": hb.donor_acceptor_properties,
                }
            )

        with open(filename, "w", encoding="utf-8") as jsonfile:
            json.dump(data, jsonfile, indent=2, ensure_ascii=False)

    def _write_halogen_bonds_json(self, filename: Path) -> None:
        """Write halogen bonds to JSON file."""
        data = {
            "metadata": {
                "input_file": self.current_file,
                "analysis_engine": "HBAT",
                "version": APP_VERSION,
                "interaction_type": "Halogen Bonds",
            },
            "interactions": [],
        }

        for xb in self.analyzer.halogen_bonds:
            bs_interaction = xb.get_backbone_sidechain_interaction()
            da_properties = xb.donor_acceptor_properties

            data["interactions"].append(
                {
                    "halogen_residue": xb.donor_residue,
                    "halogen_atom": xb.halogen.name,
                    "acceptor_residue": xb.acceptor_residue,
                    "acceptor_atom": xb.acceptor.name,
                    "distance_angstrom": round(xb.distance, 3),
                    "angle_degrees": round(xb.angle * 180 / 3.14159, 1),
                    "bond_type": xb.bond_type,
                    "backbone_sidechain_interaction": bs_interaction,
                    "donor_acceptor_properties": da_properties,
                }
            )

        with open(filename, "w", encoding="utf-8") as jsonfile:
            json.dump(data, jsonfile, indent=2, ensure_ascii=False)

    def _write_pi_interactions_json(self, filename: Path) -> None:
        """Write π interactions to JSON file."""
        data = {
            "metadata": {
                "input_file": self.current_file,
                "analysis_engine": "HBAT",
                "version": APP_VERSION,
                "interaction_type": "Pi Interactions",
            },
            "interactions": [],
        }

        for pi in self.analyzer.pi_interactions:
            data["interactions"].append(
                {
                    "donor_residue": pi.donor_residue,
                    "donor_atom": pi.donor.name,
                    "hydrogen_atom": pi.hydrogen.name,
                    "pi_residue": pi.pi_residue,
                    "distance_angstrom": round(pi.distance, 3),
                    "angle_degrees": round(pi.angle * 180 / 3.14159, 1),
                    "backbone_sidechain_interaction": pi.get_backbone_sidechain_interaction(),
                    "donor_acceptor_properties": pi.donor_acceptor_properties,
                }
            )

        with open(filename, "w", encoding="utf-8") as jsonfile:
            json.dump(data, jsonfile, indent=2, ensure_ascii=False)

    def _write_cooperativity_chains_json(self, filename: Path) -> None:
        """Write cooperativity chains to JSON file."""
        data = {
            "metadata": {
                "input_file": self.current_file,
                "analysis_engine": "HBAT",
                "version": APP_VERSION,
                "interaction_type": "Cooperativity Chains",
            },
            "chains": [],
        }

        for i, chain in enumerate(self.analyzer.cooperativity_chains):
            chain_data = {
                "chain_id": i + 1,
                "chain_length": chain.chain_length,
                "chain_type": chain.chain_type,
                "interactions": [],
            }

            for interaction in chain.interactions:
                interaction_data = {
                    "donor_residue": interaction.get_donor_residue(),
                    "acceptor_residue": interaction.get_acceptor_residue(),
                    "interaction_type": interaction.get_interaction_type(),
                }

                # Add donor atom if available
                donor_atom = interaction.get_donor_atom()
                if donor_atom:
                    interaction_data["donor_atom"] = donor_atom.name

                # Add acceptor atom if available
                acceptor_atom = interaction.get_acceptor_atom()
                if acceptor_atom:
                    interaction_data["acceptor_atom"] = acceptor_atom.name

                chain_data["interactions"].append(interaction_data)

            data["chains"].append(chain_data)

        with open(filename, "w", encoding="utf-8") as jsonfile:
            json.dump(data, jsonfile, indent=2, ensure_ascii=False)

    def _export_all(self) -> None:
        """Export all results in multiple formats.

        Exports analysis results to a directory in multiple file formats
        for comprehensive data preservation.

        :returns: None
        :rtype: None
        """
        if not self.analyzer:
            messagebox.showwarning(
                "Warning", "No results to export. Run analysis first."
            )
            return

        directory = filedialog.askdirectory(title="Select Export Directory")
        if directory:
            try:
                base_name = (
                    os.path.splitext(os.path.basename(self.current_file))[0]
                    if self.current_file
                    else "hbat_results"
                )

                # Export text summary
                self._export_results_to_file(
                    os.path.join(directory, f"{base_name}_summary.txt")
                )

                # Export Fixed PDB if available
                summary = self.analyzer.get_summary()
                pdb_info = summary.get("pdb_fixing", {})
                if pdb_info.get("applied", False):
                    fixed_pdb_path = os.path.join(directory, f"{base_name}_fixed.pdb")
                    try:
                        fixed_content = self._generate_pdb_content_from_atoms()
                        with open(fixed_pdb_path, "w") as f:
                            f.write(fixed_content)
                    except Exception as e:
                        print(f"Warning: Could not export fixed PDB: {e}")

                messagebox.showinfo("Success", f"Results exported to {directory}")
                self.status_var.set("All results exported")
            except Exception as e:
                messagebox.showerror("Error", f"Failed to export results:\n{str(e)}")

    def _reset_parameters(self) -> None:
        """Reset analysis parameters to defaults.

        Restores all analysis parameters to their default values
        as defined in the application constants.

        :returns: None
        :rtype: None
        """
        # Reset session parameters to defaults
        self.session_parameters = AnalysisParameters()
        self.status_var.set("Parameters reset to defaults")

    def _show_about(self) -> None:
        """Show about dialog.

        Displays application information including version, authors,
        and institutional affiliation.

        :returns: None
        :rtype: None
        """
        about_text = f"""
{APP_NAME} v{APP_VERSION}

A high-performance tool for analyzing molecular interactions in protein structures.

Features:
• Hydrogen bond detection
• Halogen bond analysis  
• π-interaction identification
• High-performance analysis engine
• Comprehensive visualization and export options

Author: Abhishek Tiwari
        """
        messagebox.showinfo("About HBAT", about_text.strip())

    def _open_parameters_window(self) -> None:
        """Open geometry cutoffs configuration dialog.

        Creates a modal dialog for configuring geometry cutoff parameters,
        preserving any existing parameter values for the session.

        :returns: None
        :rtype: None
        """
        # Get current session parameters or defaults
        current_params = self.session_parameters or AnalysisParameters()

        # Create dialog
        dialog = GeometryCutoffsDialog(self.root, current_params)

        # Get results
        result = dialog.get_result()

        if result:
            # Store the updated parameters in session
            self.session_parameters = result
            self.status_var.set("Geometry cutoffs updated")

    def _open_pdb_fixing_window(self) -> None:
        """Open PDB fixing settings dialog.

        Creates a modal dialog for configuring PDB fixing parameters.

        :returns: None
        :rtype: None
        """
        # Create dialog
        dialog = PDBFixingDialog(self.root)

        # Set current parameters if available
        if (
            hasattr(self, "session_pdb_fixing_params")
            and self.session_pdb_fixing_params
        ):
            dialog.set_parameters(self.session_pdb_fixing_params)

        # Get results
        result = dialog.get_parameters()

        if result:
            # Store the PDB fixing parameters
            self.session_pdb_fixing_params = result

            # Update the status bar if appropriate
            if hasattr(self, "status_bar"):
                fixing_status = "enabled" if result["enabled"] else "disabled"
                self.status_bar.config(
                    text=f"PDB fixing {fixing_status} ({result['method']})"
                )
                # Clear status after 3 seconds
                self.root.after(3000, lambda: self.status_bar.config(text="Ready"))

    def _show_help(self) -> None:
        """Show help dialog.

        Opens the HBAT documentation website in the default web browser.

        :returns: None
        :rtype: None
        """
        webbrowser.open("https://hbat.abhishek-tiwari.com")

    def _update_fixed_pdb_content(self) -> None:
        """Update the Fixed PDB tab with the processed structure content.

        Shows the PDB structure after any fixing has been applied,
        or indicates that no fixing was done.

        :returns: None
        :rtype: None
        """
        if not self.analyzer:
            self._clear_fixed_pdb_content()
            return

        summary = self.analyzer.get_summary()
        pdb_info = summary.get("pdb_fixing", {})

        self.fixed_file_text.delete(1.0, tk.END)

        if pdb_info.get("applied", False):
            # PDB fixing was applied - show the fixed structure from saved file
            try:
                fixed_file_path = pdb_info.get("fixed_file_path")
                if fixed_file_path and os.path.exists(fixed_file_path):
                    # Read content from the saved fixed PDB file
                    with open(fixed_file_path, "r") as f:
                        fixed_content = f.read()
                    self.fixed_file_text.insert(1.0, fixed_content)
                    self._highlight_pdb_records_in_widget(self.fixed_file_text)
                else:
                    # Fallback to generating content from atoms
                    fixed_content = self._generate_pdb_content_from_atoms()
                    self.fixed_file_text.insert(1.0, fixed_content)
                    self._highlight_pdb_records_in_widget(self.fixed_file_text)

                # Show tab title indicating changes
                self.left_notebook.tab(1, text="Fixed PDB ✓")

            except Exception as e:
                self.fixed_file_text.insert(
                    tk.END, f"Error loading fixed PDB content: {e}\n"
                )
                self.fixed_file_text.insert(
                    tk.END, "Original structure was used for analysis."
                )
        else:
            # No PDB fixing applied
            if "error" in pdb_info:
                self.fixed_file_text.insert(tk.END, "PDB Fixing Status: Failed\n")
                self.fixed_file_text.insert(tk.END, f"Error: {pdb_info['error']}\n\n")
                self.fixed_file_text.insert(
                    tk.END, "The original structure was used for analysis.\n"
                )
                self.fixed_file_text.insert(
                    tk.END, "Consider enabling PDB fixing in the analysis parameters."
                )
            else:
                self.fixed_file_text.insert(
                    tk.END, "PDB Fixing Status: Not Applied\n\n"
                )
                self.fixed_file_text.insert(
                    tk.END,
                    "The original structure was used for analysis without modifications.\n\n",
                )
                self.fixed_file_text.insert(tk.END, "To apply PDB fixing:\n")
                self.fixed_file_text.insert(
                    tk.END, "1. Open Settings → Geometry Cutoffs\n"
                )
                self.fixed_file_text.insert(tk.END, "2. Enable 'Fix PDB' option\n")
                self.fixed_file_text.insert(
                    tk.END, "3. Select fixing method (OpenBabel or PDBFixer)\n"
                )
                self.fixed_file_text.insert(tk.END, "4. Re-run the analysis")

            # Show tab title indicating no changes
            self.left_notebook.tab(1, text="Fixed PDB")

    def _clear_fixed_pdb_content(self) -> None:
        """Clear the Fixed PDB tab content.

        :returns: None
        :rtype: None
        """
        self.fixed_file_text.delete(1.0, tk.END)
        self.fixed_file_text.insert(tk.END, "No analysis results available.\n\n")
        self.fixed_file_text.insert(
            tk.END,
            "Please load a PDB file and run analysis to see the processed structure.",
        )
        self.left_notebook.tab(1, text="Fixed PDB")

    def _generate_pdb_content_from_atoms(self) -> str:
        """Generate PDB format content from the analyzer's atoms.

        Creates PDB format text from the processed atom list, which may
        include atoms added during PDB fixing.

        :returns: PDB format content
        :rtype: str
        :raises Exception: If atoms cannot be converted to PDB format
        """
        if not self.analyzer or not self.analyzer.parser.atoms:
            return "No atoms available in processed structure."

        lines = []

        # Add header information
        lines.append("REMARK   1 PROCESSED BY HBAT")
        lines.append(
            "REMARK   1 THIS STRUCTURE MAY INCLUDE MODIFICATIONS FROM PDB FIXING"
        )
        lines.append("REMARK   1")

        # Add PDB fixing information to header
        summary = self.analyzer.get_summary()
        pdb_info = summary.get("pdb_fixing", {})

        if pdb_info.get("applied", False):
            lines.append(f"REMARK   2 PDB FIXING METHOD: {pdb_info['method'].upper()}")
            lines.append(f"REMARK   2 ORIGINAL ATOMS: {pdb_info['original_atoms']}")
            lines.append(f"REMARK   2 FIXED ATOMS: {pdb_info['fixed_atoms']}")
            if pdb_info.get("added_hydrogens", 0) > 0:
                lines.append(
                    f"REMARK   2 ADDED HYDROGENS: {pdb_info['added_hydrogens']}"
                )
            lines.append(f"REMARK   2 REDETECTED BONDS: {pdb_info['redetected_bonds']}")

        lines.append("REMARK   2")

        # Convert atoms to PDB format
        for atom in self.analyzer.parser.atoms:
            line = self._atom_to_pdb_line(atom)
            lines.append(line)

        lines.append("END")

        return "\n".join(lines)

    def _atom_to_pdb_line(self, atom) -> str:
        """Convert an Atom object to PDB format line.

        :param atom: Atom object to convert
        :type atom: Atom
        :returns: PDB format line
        :rtype: str
        """
        # PDB format: ATOM/HETATM with specific column positions
        return (
            f"{atom.record_type:<6}"  # Record type (ATOM/HETATM)
            f"{atom.serial:>5} "  # Atom serial number
            f"{atom.name:<4}"  # Atom name
            f"{atom.alt_loc:>1}"  # Alternate location
            f"{atom.res_name:>3} "  # Residue name
            f"{atom.chain_id:>1}"  # Chain ID
            f"{atom.res_seq:>4}"  # Residue sequence number
            f"{atom.i_code:>1}   "  # Insertion code
            f"{atom.coords.x:>8.3f}"  # X coordinate
            f"{atom.coords.y:>8.3f}"  # Y coordinate
            f"{atom.coords.z:>8.3f}"  # Z coordinate
            f"{atom.occupancy:>6.2f}"  # Occupancy
            f"{atom.temp_factor:>6.2f}"  # Temperature factor
            f"          "  # Blank spaces
            f"{atom.element:>2}"  # Element symbol
            f"{atom.charge:>2}"  # Charge
        )

    def _highlight_pdb_records_in_widget(self, text_widget) -> None:
        """Apply PDB record highlighting to a specific text widget.

        :param text_widget: Text widget to apply highlighting to
        :type text_widget: tk.Text
        :returns: None
        :rtype: None
        """
        # Configure text tags
        text_widget.tag_configure("atom", foreground="blue")
        text_widget.tag_configure("hetatm", foreground="red")
        text_widget.tag_configure(
            "remark", foreground="green", font=("Courier", 12, "bold")
        )

        content = text_widget.get(1.0, tk.END)
        lines = content.split("\n")

        for i, line in enumerate(lines):
            line_start = f"{i+1}.0"
            line_end = f"{i+1}.end"

            if line.startswith("ATOM"):
                text_widget.tag_add("atom", line_start, line_end)
            elif line.startswith("HETATM"):
                text_widget.tag_add("hetatm", line_start, line_end)
            elif line.startswith("REMARK"):
                text_widget.tag_add("remark", line_start, line_end)

    def _create_fixed_pdb_context_menu(self) -> None:
        """Create context menu for the Fixed PDB text widget.

        :returns: None
        :rtype: None
        """
        # Create context menu
        self.fixed_pdb_context_menu = tk.Menu(self.root, tearoff=0)
        self.fixed_pdb_context_menu.add_command(
            label="Save Fixed PDB...", command=self._save_fixed_pdb
        )
        self.fixed_pdb_context_menu.add_separator()
        self.fixed_pdb_context_menu.add_command(
            label="Select All",
            command=lambda: self.fixed_file_text.tag_add(tk.SEL, "1.0", tk.END),
        )
        self.fixed_pdb_context_menu.add_command(
            label="Copy",
            command=lambda: self.root.clipboard_clear()
            or self.root.clipboard_append(self.fixed_file_text.selection_get()),
        )

        # Bind right-click to show context menu
        self.fixed_file_text.bind("<Button-3>", self._show_fixed_pdb_context_menu)

    def _show_fixed_pdb_context_menu(self, event) -> None:
        """Show context menu for Fixed PDB tab.

        :param event: Mouse event
        :type event: tkinter.Event
        :returns: None
        :rtype: None
        """
        try:
            self.fixed_pdb_context_menu.tk_popup(event.x_root, event.y_root)
        finally:
            self.fixed_pdb_context_menu.grab_release()

    def _save_fixed_pdb(self) -> None:
        """Save the Fixed PDB content to a file.

        :returns: None
        :rtype: None
        """
        if not self.analyzer:
            messagebox.showwarning("Warning", "No analysis results available to save.")
            return

        # Check if PDB fixing was actually applied
        summary = self.analyzer.get_summary()
        pdb_info = summary.get("pdb_fixing", {})

        if not pdb_info.get("applied", False):
            result = messagebox.askyesno(
                "No PDB Fixing Applied",
                "PDB fixing was not applied to this structure. "
                "The saved file will be identical to the original PDB.\n\n"
                "Do you want to continue?",
            )
            if not result:
                return

        # Get save filename
        filename = filedialog.asksaveasfilename(
            title="Save Fixed PDB",
            defaultextension=".pdb",
            filetypes=[
                ("PDB files", "*.pdb"),
                ("Text files", "*.txt"),
                ("All files", "*.*"),
            ],
            initialname=f"{os.path.splitext(os.path.basename(self.current_file or 'structure'))[0]}_fixed.pdb",
        )

        if filename:
            try:
                # Try to copy from the saved fixed file first
                fixed_file_path = pdb_info.get("fixed_file_path")
                if (
                    pdb_info.get("applied", False)
                    and fixed_file_path
                    and os.path.exists(fixed_file_path)
                ):
                    # Copy the existing fixed file
                    import shutil

                    shutil.copy2(fixed_file_path, filename)
                else:
                    # Fallback to saving from text widget content
                    content = self.fixed_file_text.get(1.0, tk.END)
                    with open(filename, "w") as f:
                        f.write(content)

                messagebox.showinfo("Success", f"Fixed PDB saved to {filename}")
                self.status_var.set(f"Fixed PDB saved to {os.path.basename(filename)}")

            except Exception as e:
                messagebox.showerror("Error", f"Failed to save fixed PDB:\n{str(e)}")

    def _open_preset_manager(self) -> None:
        """Open the preset manager dialog.

        Creates a standalone preset manager that can load/save presets
        and apply them to the current session parameters.

        :returns: None
        :rtype: None
        """
        from .preset_manager_dialog import PresetManagerDialog

        # Get current session parameters or defaults
        current_params = self.session_parameters or AnalysisParameters()

        # Open preset manager
        dialog = PresetManagerDialog(self.root, current_params)
        result = dialog.get_result()

        if result:
            # Apply the loaded preset to session parameters
            self._apply_preset_to_session(result)
            self.status_var.set("Preset loaded and applied to session")

    def _apply_preset_to_session(self, preset_data: Dict[str, Any]) -> None:
        """Apply preset data to session parameters.

        :param preset_data: Preset data to apply
        :type preset_data: Dict[str, Any]
        :returns: None
        :rtype: None
        """
        if "parameters" not in preset_data:
            messagebox.showerror(
                "Error", "Invalid preset format: missing 'parameters' section"
            )
            return

        params_data = preset_data["parameters"]

        # Create new AnalysisParameters with preset values
        kwargs = {}

        # Apply hydrogen bond parameters
        if "hydrogen_bonds" in params_data:
            hb = params_data["hydrogen_bonds"]
            kwargs.update(
                {
                    "hb_distance_cutoff": hb.get(
                        "h_a_distance_cutoff", ParametersDefault.HB_DISTANCE_CUTOFF
                    ),
                    "hb_angle_cutoff": hb.get(
                        "dha_angle_cutoff", ParametersDefault.HB_ANGLE_CUTOFF
                    ),
                    "hb_donor_acceptor_cutoff": hb.get(
                        "d_a_distance_cutoff", ParametersDefault.HB_DA_DISTANCE
                    ),
                }
            )

        # Apply halogen bond parameters
        if "halogen_bonds" in params_data:
            xb = params_data["halogen_bonds"]
            kwargs.update(
                {
                    "xb_distance_cutoff": xb.get(
                        "x_a_distance_cutoff", ParametersDefault.XB_DISTANCE_CUTOFF
                    ),
                    "xb_angle_cutoff": xb.get(
                        "dxa_angle_cutoff", ParametersDefault.XB_ANGLE_CUTOFF
                    ),
                }
            )

        # Apply π interaction parameters
        if "pi_interactions" in params_data:
            pi = params_data["pi_interactions"]
            kwargs.update(
                {
                    "pi_distance_cutoff": pi.get(
                        "h_pi_distance_cutoff", ParametersDefault.PI_DISTANCE_CUTOFF
                    ),
                    "pi_angle_cutoff": pi.get(
                        "dh_pi_angle_cutoff", ParametersDefault.PI_ANGLE_CUTOFF
                    ),
                }
            )

        # Apply general parameters
        if "general" in params_data:
            gen = params_data["general"]
            kwargs.update(
                {
                    "covalent_cutoff_factor": gen.get(
                        "covalent_cutoff_factor",
                        ParametersDefault.COVALENT_CUTOFF_FACTOR,
                    ),
                    "analysis_mode": gen.get(
                        "analysis_mode", ParametersDefault.ANALYSIS_MODE
                    ),
                }
            )

        # Create new parameters object and store in session
        self.session_parameters = AnalysisParameters(**kwargs)

    def _on_closing(self) -> None:
        """Handle window closing event.

        Handles application shutdown, checking for running analysis
        and prompting user confirmation if needed.

        :returns: None
        :rtype: None
        """
        if self.analysis_running:
            result = messagebox.askyesno(
                "Confirm Exit", "Analysis is running. Are you sure you want to exit?"
            )
            if not result:
                return

        # Stop async executor
        tae.stop()
        self.root.destroy()

    def run(self) -> None:
        """Start the GUI application.

        Enters the main GUI event loop to begin accepting user interactions.
        This method blocks until the application is closed.

        :returns: None
        :rtype: None
        """
        self.root.mainloop()
