"""PySide2 GUI version of docscan application."""

from __future__ import annotations

import contextlib
import json
import logging
import os
import sys
from datetime import datetime
from pathlib import Path
from typing import Any, ClassVar

import PySide2
from PySide2.QtCore import QObject, QThread, Signal
from PySide2.QtWidgets import (
    QAction,
    QApplication,
    QCheckBox,
    QComboBox,
    QDialog,
    QDialogButtonBox,
    QFileDialog,
    QFormLayout,
    QGroupBox,
    QHBoxLayout,
    QLabel,
    QLineEdit,
    QMainWindow,
    QMessageBox,
    QProgressBar,
    QPushButton,
    QSpinBox,
    QTableWidget,
    QTableWidgetItem,
    QTextEdit,
    QVBoxLayout,
    QWidget,
)

# Import from docscan module
try:
    from docscan import DocumentScanner, Rule
except ImportError:
    try:
        from sfi.docscan.docscan import DocumentScanner, Rule
    except ImportError:
        from src.docscan.docscan import DocumentScanner, Rule

# Import translations
try:
    from sfi.docscan.lang.zhcn import TRANSLATIONS
except ImportError:
    try:
        from src.docscan.lang.zhcn import TRANSLATIONS
    except ImportError:
        TRANSLATIONS = {}

logging.basicConfig(level=logging.INFO, format="%(message)s")
logger = logging.getLogger(__name__)

# Language support
LANGUAGE = "zh_CN"  # Default to Chinese
USE_CHINESE = True  # Toggle for Chinese/English

qt_dir = Path(PySide2.__file__).parent
plugin_path = str(qt_dir / "plugins" / "platforms")
os.environ["QT_QPA_PLATFORM_PLUGIN_PATH"] = plugin_path


def t(key: str, **kwargs) -> str:
    """Get translated text for the given key.

    Args:
        key: Translation key
        **kwargs: Arguments for string formatting

    Returns:
        Translated text
    """
    if not USE_CHINESE:
        # Return English default values
        try:
            from sfi.docscan.lang.eng import ENGLISH_DEFAULTS
        except ImportError:
            try:
                from src.docscan.lang.eng import ENGLISH_DEFAULTS
            except ImportError:
                ENGLISH_DEFAULTS = {}

        text = ENGLISH_DEFAULTS.get(key, key)
    else:
        text = TRANSLATIONS.get(key, key)

    # Format with kwargs if provided
    if kwargs:
        with contextlib.suppress(KeyError, ValueError):
            text = text.format(**kwargs)
    return text


class ConfigManager:
    """Manage GUI configuration persistence."""

    DEFAULT_CONFIG: ClassVar[dict[str, Any]] = {
        "input_directory": str(Path.cwd()),
        "rules_file": "rules.json",
        "file_types": "pdf,docx,xlsx,pptx,txt,odt,rtf,epub,csv,xml,html,md,jpg,jpeg,png,gif,bmp,tiff",
        "use_pdf_ocr": False,
        "use_process_pool": False,
        "threads": 4,
        "batch_size": 50,
        "window_width": 1000,
        "window_height": 700,
        "window_x": 100,
        "window_y": 100,
        "recent_directories": [],
        "recent_rules_files": [],
        "include_image_formats": False,
    }

    MAX_RECENT_ITEMS = 10

    def __init__(self, config_file: Path | None = None):
        """Initialize configuration manager.

        Args:
            config_file: Path to configuration file. If None, uses default location.
        """
        if config_file is None:
            # Use user home directory for config
            config_dir = Path.home() / ".sfi"
            config_dir.mkdir(exist_ok=True)
            config_file = config_dir / "docscan_gui.json"
        self.config_file = config_file
        self.config = self._load_config()

    def _load_config(self) -> dict[str, Any]:
        """Load configuration from file.

        Returns:
            Configuration dictionary
        """
        if self.config_file.exists():
            try:
                with open(self.config_file, encoding="utf-8") as f:
                    config = json.load(f)
                # Merge with defaults to ensure all keys exist
                return {**self.DEFAULT_CONFIG, **config}
            except (OSError, json.JSONDecodeError) as e:
                logger.warning(f"Failed to load config: {e}. Using defaults.")
        return self.DEFAULT_CONFIG.copy()

    def save_config(self) -> None:
        """Save configuration to file."""
        try:
            with open(self.config_file, "w", encoding="utf-8") as f:
                json.dump(self.config, f, indent=2, ensure_ascii=False)
        except OSError as e:
            logger.warning(f"Failed to save config: {e}")

    def get(self, key: str, default: Any = None) -> Any:
        """Get configuration value.

        Args:
            key: Configuration key
            default: Default value if key not found

        Returns:
            Configuration value
        """
        return self.config.get(key, default)

    def set(self, key: str, value: Any) -> None:
        """Set configuration value.

        Args:
            key: Configuration key
            value: Value to set
        """
        self.config[key] = value

    def add_recent_directory(self, directory: str) -> None:
        """Add directory to recent directories list.

        Args:
            directory: Directory path to add
        """
        recent_dirs = self.config.get("recent_directories", [])
        # Remove if already exists
        recent_dirs = [d for d in recent_dirs if d != directory]
        # Add to front
        recent_dirs.insert(0, directory)
        # Keep only MAX_RECENT_ITEMS
        recent_dirs = recent_dirs[: self.MAX_RECENT_ITEMS]
        self.config["recent_directories"] = recent_dirs

    def add_recent_rules_file(self, rules_file: str) -> None:
        """Add rules file to recent rules files list.

        Args:
            rules_file: Rules file path to add
        """
        recent_files = self.config.get("recent_rules_files", [])
        # Remove if already exists
        recent_files = [f for f in recent_files if f != rules_file]
        # Add to front
        recent_files.insert(0, rules_file)
        # Keep only MAX_RECENT_ITEMS
        recent_files = recent_files[: self.MAX_RECENT_ITEMS]
        self.config["recent_rules_files"] = recent_files


class WorkerSignals(QObject):
    """Defines the signals available from a running worker thread."""

    progress = Signal(str)
    finished = Signal(dict)
    error = Signal(str)
    progress_update = Signal(int, int)


class ScanWorker(QThread):
    """Worker thread for running document scan in background."""

    def __init__(self, scanner: DocumentScanner, threads: int):
        """Initialize worker thread."""
        super().__init__()
        self.scanner = scanner
        self.threads = threads
        self.signals = WorkerSignals()

    def run(self):
        """Run the document scan."""
        try:
            # Set up custom logger to capture messages
            class ProgressHandler(logging.Handler):
                def __init__(self, signal):
                    super().__init__()
                    self.signal = signal

                def emit(self, record):
                    self.signal.emit(self.format(record))

            handler = ProgressHandler(self.signals.progress)
            handler.setFormatter(logging.Formatter("%(message)s"))
            logger.addHandler(handler)

            # Set progress callback
            def progress_callback(current, total):
                self.signals.progress_update.emit(current, total)

            self.scanner.set_progress_callback(progress_callback)
            self.signals.progress.emit(t("starting_scan"))

            results = self.scanner.scan(threads=self.threads, show_progress=True)

            logger.removeHandler(handler)
            self.signals.progress.emit(t("scan_complete"))
            self.signals.finished.emit(results)
        except Exception as e:
            self.signals.error.emit(str(e))


class SettingsDialog(QDialog):
    """Settings dialog for scan options."""

    def __init__(self, parent=None, config_manager=None):
        """Initialize settings dialog.

        Args:
            parent: Parent widget (should be DocScanGUI instance)
            config_manager: ConfigManager instance
        """
        super().__init__(parent)
        self.config_manager = config_manager
        self.main_window = parent  # Store reference to main window
        self.setWindowTitle(t("scan_options_tab"))
        self.setModal(True)
        self.resize(500, 400)
        self._create_ui()
        self._load_settings()

    def _create_ui(self):
        """Create settings dialog UI."""
        layout = QVBoxLayout()

        # Language Settings Group
        self.language_group = QGroupBox(t("language_settings", default="Language Settings"))
        language_layout = QHBoxLayout()
        language_layout.setSpacing(10)

        self.lang_label = QLabel(t("language_label", default="Language:"))
        self.lang_combo = QComboBox()
        self.lang_combo.addItem("中文", "zh_CN")
        self.lang_combo.addItem("English", "en")
        self.lang_combo.currentTextChanged.connect(self._on_language_changed)  # type: ignore # Real-time language change
        language_layout.addWidget(self.lang_label)
        language_layout.addWidget(self.lang_combo)
        language_layout.addStretch()
        self.language_group.setLayout(language_layout)
        layout.addWidget(self.language_group)

        # Processing Options Group
        processing_group = QGroupBox(t("processing_options", default="Processing Options"))
        processing_layout = QVBoxLayout()
        processing_layout.setSpacing(10)

        self.ocr_checkbox = QCheckBox(t("use_pdf_ocr"))
        self.ocr_checkbox.setToolTip(
            t("ocr_tooltip", default="Enable OCR for scanned PDF files to extract text from images")
        )

        self.process_pool_checkbox = QCheckBox(t("use_process_pool"))
        self.process_pool_checkbox.setToolTip(
            t(
                "process_pool_tooltip",
                default="Use multiple processes for CPU-intensive operations (may increase memory usage)",
            )
        )

        processing_layout.addWidget(self.ocr_checkbox)
        processing_layout.addWidget(self.process_pool_checkbox)
        processing_group.setLayout(processing_layout)
        layout.addWidget(processing_group)

        # Performance Settings Group
        performance_group = QGroupBox(t("performance_settings", default="Performance Settings"))
        performance_layout = QFormLayout()
        performance_layout.setSpacing(12)

        # Thread count
        self.thread_spin = QSpinBox()
        self.thread_spin.setMinimum(1)
        self.thread_spin.setMaximum(16)
        self.thread_spin.setValue(4)
        self.thread_spin.setToolTip(
            t("threads_tooltip", default="Number of worker threads (higher values may improve speed but use more CPU)")
        )
        performance_layout.addRow(t("threads"), self.thread_spin)

        # Batch size
        self.batch_spin = QSpinBox()
        self.batch_spin.setMinimum(1)
        self.batch_spin.setMaximum(1000)
        self.batch_spin.setValue(50)
        self.batch_spin.setToolTip(
            t(
                "batch_size_tooltip",
                default="Number of files to process in each batch (larger batches may improve throughput)",
            )
        )
        performance_layout.addRow(t("batch_size"), self.batch_spin)

        performance_group.setLayout(performance_layout)
        layout.addWidget(performance_group)

        # Buttons
        button_layout = QHBoxLayout()

        # Apply button for immediate language/application
        self.apply_btn = QPushButton(t("apply", default="Apply"))
        self.apply_btn.clicked.connect(self._apply_settings)  # type: ignore
        button_layout.addWidget(self.apply_btn)

        button_layout.addStretch()

        # OK and Cancel buttons
        button_box = QDialogButtonBox()
        button_box.setStandardButtons(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)  # type: ignore
        button_box.accepted.connect(self.accept)  # type: ignore
        button_box.rejected.connect(self.reject)  # type: ignore
        button_layout.addWidget(button_box)

        layout.addLayout(button_layout)

        self.setLayout(layout)

    def _on_language_changed(self):
        """Handle real-time language change in settings dialog."""
        # Get selected language
        lang = self.lang_combo.currentData()

        # Update global language setting
        global LANGUAGE, USE_CHINESE
        LANGUAGE = lang
        USE_CHINESE = lang == "zh_CN"

        # Update dialog title and UI elements immediately
        self.setWindowTitle(t("scan_options_tab"))
        self.language_group.setTitle(t("language_settings", default="Language Settings"))
        self.lang_label.setText(t("language_label", default="Language:"))

        # Update other group boxes
        for i in range(self.layout().count()):
            item = self.layout().itemAt(i)
            if item.widget() and isinstance(item.widget(), QGroupBox):
                group_box = item.widget()
                if "Language" in group_box.title():  # type: ignore
                    group_box.setTitle(t("language_settings", default="Language Settings"))  # type: ignore
                elif "Processing" in group_box.title():  # type: ignore
                    group_box.setTitle(t("processing_options", default="Processing Options"))  # type: ignore
                elif "Performance" in group_box.title():  # type: ignore
                    group_box.setTitle(t("performance_settings", default="Performance Settings"))  # type: ignore

        # Show temporary message in dialog
        logger.info(f"Language preview: {'中文' if lang == 'zh_CN' else 'English'}")

    def _load_settings(self):
        """Load current settings into dialog."""
        if self.config_manager:
            self.ocr_checkbox.setChecked(self.config_manager.get("use_pdf_ocr", False))
            self.process_pool_checkbox.setChecked(self.config_manager.get("use_process_pool", False))
            self.thread_spin.setValue(self.config_manager.get("threads", 4))
            self.batch_spin.setValue(self.config_manager.get("batch_size", 50))

            # Set language combo
            lang = self.config_manager.get("language", "zh_CN")
            index = self.lang_combo.findData(lang)
            if index >= 0:
                self.lang_combo.setCurrentIndex(index)

    def _apply_settings(self):
        """Apply settings immediately without closing dialog (for preview)."""
        if not self.main_window:
            return

        # Save settings to config
        if self.config_manager:
            self.config_manager.set("use_pdf_ocr", self.ocr_checkbox.isChecked())
            self.config_manager.set("use_process_pool", self.process_pool_checkbox.isChecked())
            self.config_manager.set("threads", self.thread_spin.value())
            self.config_manager.set("batch_size", self.batch_spin.value())

            # Apply language immediately
            selected_lang = self.lang_combo.currentData()
            self.config_manager.set("language", selected_lang)

            # Apply language to parent window
            self._apply_language_to_parent(selected_lang)

            # Save config to file
            self.config_manager.save_config()

    def accept(self):
        """Save settings when OK is clicked and apply language immediately."""
        if self.config_manager:
            self.config_manager.set("use_pdf_ocr", self.ocr_checkbox.isChecked())
            self.config_manager.set("use_process_pool", self.process_pool_checkbox.isChecked())
            self.config_manager.set("threads", self.thread_spin.value())
            self.config_manager.set("batch_size", self.batch_spin.value())

            # Apply language immediately when OK is clicked
            selected_lang = self.lang_combo.currentData()
            self.config_manager.set("language", selected_lang)
            self.config_manager.save_config()

            # Apply language to parent window if exists
            if self.main_window:
                self._apply_language_to_parent(selected_lang)

        super().accept()

    def _apply_language_to_parent(self, lang: str) -> None:
        """Apply language change to parent window immediately.

        Args:
            lang: Language code ('zh_CN' or 'en')
        """
        if not self.main_window:
            return

        # Update global language variables
        global LANGUAGE, USE_CHINESE
        LANGUAGE = lang
        USE_CHINESE = lang == "zh_CN"

        # Get parent window (DocScanGUI instance)

        # Update all translatable UI elements in parent window
        self.main_window.setWindowTitle(t("window_title"))

        # Update menu bar
        self.main_window.file_menu.setTitle(t("file_menu", default="&File"))
        self.main_window.save_action.setText(t("save_results", default="&Save Results"))
        self.main_window.clear_action.setText(t("clear_results", default="&Clear Results"))

        # Update input section
        for i in range(self.main_window.centralWidget().layout().count()):
            item = self.main_window.centralWidget().layout().itemAt(i)
            if item.widget():
                widget = item.widget()
                if hasattr(widget, "title"):
                    title = widget.title()
                    if "Input" in title or "输入" in title:
                        widget.setTitle(t("input_config_tab"))
                    elif "Results" in title or "结果" in title:
                        widget.setTitle(t("results"))

        # Update labels in input section
        if hasattr(self.main_window, "dir_edit") and self.main_window.dir_edit:
            # Find and update labels (this is a simplified approach)
            # In a real implementation, you'd store references to all translatable widgets
            pass

        # Force UI refresh
        self.main_window.update()

        # Show confirmation message
        lang_name = "中文" if lang == "zh_CN" else "English"
        QMessageBox.information(
            self.main_window,
            t("success"),
            f"Language switched to {lang_name}. Some elements may require restart to fully update.",
        )


class DocScanGUI(QMainWindow):
    """Main GUI window for document scanner application."""

    def __init__(self):
        """Initialize GUI components."""
        super().__init__()
        self.config_manager = ConfigManager()
        self.scan_results = None
        self.scan_worker = None
        self.is_scanning = False
        self.settings_dialog = None
        # Menu actions
        self.file_menu = None
        self.open_action = None
        self.save_action = None
        self.clear_action = None
        # Validation label for rules file
        self.rules_validation_label = None
        self.init_ui()
        self._load_config()
        self._setup_close_handler()

    def init_ui(self):
        """Initialize user interface."""
        self.setWindowTitle(t("window_title"))
        self.setMinimumSize(1000, 700)

        # Create central widget with splitter
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        main_layout = QVBoxLayout(central_widget)

        # Create menu bar
        self._create_menu_bar()

        # Input configuration section
        self._create_input_section(main_layout)

        # Create other sections
        self._create_actions_section(main_layout)
        self._create_results_section(main_layout)

    def _create_menu_bar(self):
        """Create menu bar with File, Settings, and Help menus."""
        menubar = self.menuBar()

        # File menu
        self.file_menu = menubar.addMenu(t("file_menu", default="&File"))

        self.open_action = QAction(t("open_results", default="&Open Results..."), self)
        self.open_action.triggered.connect(self._open_results)  # type: ignore
        self.file_menu.addAction(self.open_action)

        self.file_menu.addSeparator()

        self.save_action = QAction(t("save_results", default="&Save Results"), self)
        self.save_action.triggered.connect(self._save_results)  # type: ignore
        self.save_action.setEnabled(False)
        self.file_menu.addAction(self.save_action)

        self.clear_action = QAction(t("clear_results", default="&Clear Results"), self)
        self.clear_action.triggered.connect(self._clear_results)  # type: ignore
        self.file_menu.addAction(self.clear_action)

        self.file_menu.addSeparator()

        exit_action = QAction(t("exit", default="E&xit"), self)
        exit_action.triggered.connect(self.close)  # type: ignore
        self.file_menu.addAction(exit_action)

        # Settings menu
        settings_menu = menubar.addMenu(t("settings_menu", default="&Settings"))

        preferences_action = QAction(t("preferences", default="&Preferences..."), self)
        preferences_action.triggered.connect(self._show_settings)  # type: ignore
        settings_menu.addAction(preferences_action)

        # Help menu
        help_menu = menubar.addMenu(t("help_menu", default="&Help"))

        about_action = QAction(t("about", default="&About"), self)
        about_action.triggered.connect(self._show_about)  # type: ignore
        help_menu.addAction(about_action)

    def _show_settings(self):
        """Show settings dialog."""
        if self.settings_dialog is None:
            self.settings_dialog = SettingsDialog(self, self.config_manager)
        self.settings_dialog.show()

    def _show_about(self):
        """Show about dialog."""
        QMessageBox.about(
            self,
            t("about_title", default="About Document Scanner"),
            t("about_text", default="Document Scanner GUI\n\nVersion 1.0"),
        )

    def _create_input_section(self, parent_layout: QVBoxLayout) -> None:
        """Create input configuration section.

        Args:
            parent_layout: Parent layout to add this section to
        """
        input_group = QGroupBox(t("input_config_tab"))
        input_layout = QVBoxLayout()
        input_group.setLayout(input_layout)

        # Input directory
        dir_layout = QHBoxLayout()
        dir_label = QLabel(t("input_directory"))
        self.dir_edit = QLineEdit(str(Path.cwd()))
        dir_browse_btn = QPushButton(t("browse"))
        dir_browse_btn.clicked.connect(self._browse_directory)  # type: ignore
        self.dir_edit.textChanged.connect(self._on_directory_changed)  # type: ignore
        dir_layout.addWidget(dir_label)
        dir_layout.addWidget(self.dir_edit)
        dir_layout.addWidget(dir_browse_btn)
        input_layout.addLayout(dir_layout)

        # Rules file
        rules_layout = QHBoxLayout()
        rules_label = QLabel(t("rules_file"))
        self.rules_edit = QLineEdit(t("default_rules_file"))
        self.rules_edit.setMinimumWidth(300)
        rules_browse_btn = QPushButton(t("browse"))
        rules_browse_btn.clicked.connect(self._browse_rules_file)  # type: ignore
        # Validation icon label
        self.rules_validation_label = QLabel("")  # Will show ✓ or ✗
        self.rules_validation_label.setFixedWidth(20)
        rules_layout.addWidget(rules_label)
        rules_layout.addWidget(self.rules_edit)
        rules_layout.addWidget(self.rules_validation_label)
        rules_layout.addWidget(rules_browse_btn)
        input_layout.addLayout(rules_layout)

        # File types - simplified to use default
        types_layout = QHBoxLayout()
        types_label = QLabel(t("file_types"))
        self.types_edit = QLineEdit(t("default_file_types"))
        self.types_edit.setToolTip(t("file_types_tooltip", default="File types to scan (comma separated)"))
        types_layout.addWidget(types_label)
        types_layout.addWidget(self.types_edit)
        input_layout.addLayout(types_layout)

        # Checkbox for including image formats
        self.include_images_checkbox = QCheckBox(t("include_image_formats", default="Include Image Formats"))
        self.include_images_checkbox.setChecked(self.config_manager.get("include_image_formats", False))
        self.include_images_checkbox.setToolTip(
            t("include_image_formats_tooltip", default="Include image formats (jpg, jpeg, png, gif, bmp, tiff) in scan")
        )
        self.include_images_checkbox.stateChanged.connect(self._toggle_image_formats)  # type: ignore
        input_layout.addWidget(self.include_images_checkbox)

        parent_layout.addWidget(input_group)

    def _toggle_image_formats(self, state):
        """Toggle image formats in file types based on checkbox state."""
        base_types = "pdf,docx,xlsx,pptx,txt,odt,rtf,epub,csv,xml,html,md"
        image_types = "jpg,jpeg,png,gif,bmp,tiff"
        self.config_manager.set("include_image_formats", state == 2)

        if state == 2:  # Checked
            # Add image formats if not already present
            current_types = self.types_edit.text()
            if not any(img_type in current_types for img_type in image_types.split(",")):
                all_types = f"{base_types},{image_types}"
                self.types_edit.setText(all_types)
        else:  # Unchecked
            # Remove image formats
            current_types = self.types_edit.text()
            types_list = [t.strip() for t in current_types.split(",")]
            filtered_types = [t for t in types_list if t not in ["jpg", "jpeg", "png", "gif", "bmp", "tiff"]]
            self.types_edit.setText(",".join(filtered_types))

    def _create_actions_section(self, parent_layout: QVBoxLayout) -> None:
        """Create action buttons section.

        Args:
            parent_layout: Parent layout to add this section to
        """
        actions_layout = QHBoxLayout()

        self.scan_btn = QPushButton(t("start_scan"))
        self.scan_btn.clicked.connect(self._start_scan)  # pyright: ignore[reportAttributeAccessIssue]
        self.scan_btn.setMinimumHeight(40)

        self.pause_btn = QPushButton(t("pause"))
        self.pause_btn.clicked.connect(self._pause_scan)  # pyright: ignore[reportAttributeAccessIssue]
        self.pause_btn.setEnabled(False)
        self.pause_btn.setMinimumHeight(40)

        self.stop_btn = QPushButton(t("stop"))
        self.stop_btn.clicked.connect(self._stop_scan)  # pyright: ignore[reportAttributeAccessIssue]
        self.stop_btn.setEnabled(False)
        self.stop_btn.setMinimumHeight(40)

        actions_layout.addWidget(self.scan_btn)
        actions_layout.addWidget(self.pause_btn)
        actions_layout.addWidget(self.stop_btn)

        parent_layout.addLayout(actions_layout)

    def _create_results_section(self, parent_layout: QVBoxLayout) -> None:
        """Create results display section.

        Args:
            parent_layout: Parent layout to add this section to
        """
        results_group = QGroupBox(t("results"))
        results_layout = QVBoxLayout()
        results_group.setLayout(results_layout)

        # Summary labels
        summary_layout = QHBoxLayout()
        self.files_label = QLabel(t("files_scanned_zero"))
        self.matches_label = QLabel(t("files_with_matches_zero"))
        summary_layout.addWidget(self.files_label)
        summary_layout.addWidget(self.matches_label)
        results_layout.addLayout(summary_layout)

        # Progress bar
        self.progress_bar = QProgressBar()
        self.progress_bar.setMinimum(0)
        self.progress_bar.setMaximum(100)
        self.progress_bar.setValue(0)
        results_layout.addWidget(self.progress_bar)

        # Progress/Log text
        self.log_text = QTextEdit()
        self.log_text.setReadOnly(True)
        self.log_text.setMaximumHeight(150)
        results_layout.addWidget(QLabel(t("progress_log")))
        results_layout.addWidget(self.log_text)

        # Results table
        self.results_table = QTableWidget()
        self.results_table.setColumnCount(4)
        self.results_table.setHorizontalHeaderLabels([t("file"), t("type"), t("matches"), t("time")])
        self.results_table.horizontalHeader().setStretchLastSection(True)
        results_layout.addWidget(QLabel(t("match_details")))
        results_layout.addWidget(self.results_table)

        # Match details text
        self.details_text = QTextEdit()
        self.details_text.setReadOnly(True)
        self.details_text.setMaximumHeight(200)
        results_layout.addWidget(QLabel(t("selected_match_context")))
        results_layout.addWidget(self.details_text)

        # Connect table selection
        self.results_table.itemSelectionChanged.connect(self._show_match_details)  # pyright: ignore[reportAttributeAccessIssue]
        self.results_table.cellClicked.connect(self._handle_cell_click)  # pyright: ignore[reportAttributeAccessIssue]

        parent_layout.addWidget(results_group)

    def _handle_cell_click(self, row: int, column: int) -> None:
        """Handle cell click event to show match details regardless of selection change.

        Args:
            row: Row index of clicked cell
            column: Column index of clicked cell
        """
        # Simply call the existing method to show details for the clicked row
        # We temporarily select the row to ensure consistency
        self.results_table.selectRow(row)
        self._show_match_details()

    def _browse_directory(self) -> None:
        """Open directory browser dialog."""
        # Get recent directories for initial path
        recent_dirs = self.config_manager.get("recent_directories", [])
        start_dir = recent_dirs[0] if recent_dirs else str(Path.cwd())

        dir_path = QFileDialog.getExistingDirectory(self, t("select_input_directory"), start_dir)
        if dir_path:
            self.dir_edit.setText(str(Path(dir_path)))

    def _on_directory_changed(self) -> None:
        """Handle directory text change - auto-search for rules.json."""
        dir_text = self.dir_edit.text()
        if not dir_text:
            return

        try:
            input_dir = Path(dir_text)
            if input_dir.exists() and input_dir.is_dir():
                # Search for rules.json or rules*.json files
                rule_files = list(input_dir.glob("rules.json")) + list(input_dir.glob("rules*.json"))

                if rule_files:
                    # Use the first matching file, prefer exact "rules.json"
                    exact_match = next((f for f in rule_files if f.name == "rules.json"), None)
                    rules_file = exact_match if exact_match else rule_files[0]
                    self.rules_edit.setText(str(rules_file.resolve()))
            # Validate rules file after directory change
            self._validate_rules_file()
        except Exception:
            # Ignore errors during directory change handling
            pass

    def _browse_rules_file(self) -> None:
        """Open file browser dialog for rules file."""
        # Get recent rules files for initial path
        recent_files = self.config_manager.get("recent_rules_files", [])
        start_dir = str(Path(recent_files[0]).parent) if recent_files else str(Path.cwd())

        file_path, _ = QFileDialog.getOpenFileName(self, t("select_rules_file"), start_dir, t("json_files"))
        if file_path:
            self.rules_edit.setText(str(Path(file_path)))
            self._validate_rules_file()

    def _validate_rules_file(self) -> None:
        """Validate rules file path and update UI indicator.

        Shows green border and checkmark if file exists, red border and X if not.
        """
        if not hasattr(self, "rules_edit") or self.rules_edit is None:
            return

        rules_path = self.rules_edit.text().strip()
        if not rules_path:
            # Reset style if empty
            self.rules_edit.setStyleSheet("")
            if self.rules_validation_label:
                self.rules_validation_label.setText("")
            return

        try:
            rules_file = Path(rules_path)
            if rules_file.exists() and rules_file.is_file():
                # File exists - show green border and checkmark
                self.rules_edit.setStyleSheet("""
                    QLineEdit {
                        border: 2px solid #4CAF50;
                        border-radius: 3px;
                        padding: 2px;
                    }
                """)
                if self.rules_validation_label:
                    self.rules_validation_label.setText("✓")
                    self.rules_validation_label.setStyleSheet("color: #4CAF50; font-weight: bold;")
            else:
                # File doesn't exist - show red border and X
                self.rules_edit.setStyleSheet("""
                    QLineEdit {
                        border: 2px solid #F44336;
                        border-radius: 3px;
                        padding: 2px;
                    }
                """)
                if self.rules_validation_label:
                    self.rules_validation_label.setText("✗")
                    self.rules_validation_label.setStyleSheet("color: #F44336; font-weight: bold;")
        except Exception:
            # Invalid path - show red border and X
            self.rules_edit.setStyleSheet("""
                QLineEdit {
                    border: 2px solid #F44336;
                    border-radius: 3px;
                    padding: 2px;
                }
            """)
            if self.rules_validation_label:
                self.rules_validation_label.setText("✗")
                self.rules_validation_label.setStyleSheet("color: #F44336; font-weight: bold;")

    def _load_rules(self) -> list[Rule]:
        """Load rules from JSON file.

        Returns:
            List of Rule objects
        """
        rules_file = Path(self.rules_edit.text())
        if not rules_file.exists():
            # Try finding rules in input directory
            input_dir = Path(self.dir_edit.text())
            rule_files = list(input_dir.glob("rules*.json"))
            if rule_files:
                rules_file = rule_files[0]
                self.rules_edit.setText(str(rules_file.resolve()))
                # Validate after updating path
                self._validate_rules_file()
            else:
                raise FileNotFoundError(f"Rules file not found: {rules_file}")

        with open(rules_file, encoding="utf-8") as f:
            rules_data = json.load(f)

        rules = []
        if isinstance(rules_data, list):
            rules = [Rule(rule) for rule in rules_data]
        elif isinstance(rules_data, dict) and "rules" in rules_data:
            rules = [Rule(rule) for rule in rules_data["rules"]]

        return rules

    def _start_scan(self) -> None:
        """Start the document scan."""
        # Validate inputs
        input_dir = Path(self.dir_edit.text())
        if not input_dir.exists() or not input_dir.is_dir():
            QMessageBox.warning(self, t("error"), t("invalid_input_directory"))
            return

        try:
            rules = self._load_rules()
            if not rules:
                QMessageBox.warning(self, t("error"), t("no_valid_rules"))
                return
        except Exception as e:
            QMessageBox.warning(self, t("error"), t("failed_to_load_rules", error=e))
            return

        # Parse file types
        file_types = [ft.strip() for ft in self.types_edit.text().split(",")]

        # Clear previous results
        self._clear_results()

        # Set scanning state
        self.is_scanning = True

        # Disable scan button during scan, enable pause and stop
        self.scan_btn.setEnabled(False)
        self.pause_btn.setEnabled(True)
        self.stop_btn.setEnabled(True)
        self.pause_btn.setText(t("pause"))

        # Create scanner
        scanner = DocumentScanner(
            input_dir=input_dir,
            rules=rules,
            file_types=file_types,
            use_pdf_ocr=self.config_manager.get("use_pdf_ocr", False),
            use_process_pool=self.config_manager.get("use_process_pool", False),
            batch_size=self.config_manager.get("batch_size", 50),
        )

        # Create and start worker thread
        self.scan_worker = ScanWorker(scanner, self.config_manager.get("threads", 4))
        self.scan_worker.signals.progress.connect(self._log_message)
        self.scan_worker.signals.progress_update.connect(self._update_progress)
        self.scan_worker.signals.finished.connect(self._scan_finished)
        self.scan_worker.signals.error.connect(self._scan_error)
        self.scan_worker.start()

    def _scan_finished(self, results: dict[str, Any]) -> None:
        """Handle scan completion.

        Args:
            results: Scan results dictionary
        """
        self.scan_results = results
        self.is_scanning = False
        self.scan_btn.setEnabled(True)
        self.pause_btn.setEnabled(False)
        self.stop_btn.setEnabled(False)

        # Enable menu actions after successful scan
        if self.save_action:
            self.save_action.setEnabled(True)

        # Update summary
        scan_info = results.get("scan_info", {})
        processed = scan_info.get("files_processed", scan_info.get("total_files", 0))
        self.files_label.setText(t("files_scanned").replace("0", f"{processed}/{scan_info.get('total_files', 0)}"))
        self.matches_label.setText(f"{t('files_with_matches')} {scan_info.get('files_with_matches', 0)}")

        # Update progress bar to 100%
        self.progress_bar.setValue(100)

        # Populate results table
        matches = results.get("matches", [])
        self.results_table.setRowCount(len(matches))

        for row, match_data in enumerate(matches):
            file_path = match_data.get("file_path", "")
            file_type = match_data.get("file_type", "")
            match_count = len(match_data.get("matches", []))
            proc_time = match_data.get("metadata", {}).get("processing_time_seconds", 0)

            self.results_table.setItem(row, 0, QTableWidgetItem(Path(file_path).name))
            self.results_table.setItem(row, 1, QTableWidgetItem(file_type))
            self.results_table.setItem(row, 2, QTableWidgetItem(str(match_count)))
            self.results_table.setItem(row, 3, QTableWidgetItem(f"{proc_time:.3f}"))

        # Determine status: completed takes precedence if scan finished normally, otherwise stopped
        # A scan is considered completed if it processed files or was intentionally stopped after starting
        scan_info = results.get("scan_info", {})
        files_processed = scan_info.get("files_processed", scan_info.get("total_files", 0))
        was_stopped = results.get("stopped", False)
        status = t("scan_completed") if files_processed > 0 or not was_stopped else t("scan_stopped")
        self._log_message(status)
        self._log_message(t("found_matches_files", count=len(matches)))

        # Auto-save configuration after successful scan
        self._save_config()

    def _scan_error(self, error_msg: str) -> None:
        """Handle scan error.

        Args:
            error_msg: Error message
        """
        self.is_scanning = False
        self.scan_btn.setEnabled(True)
        self.pause_btn.setEnabled(False)
        self.stop_btn.setEnabled(False)
        self._log_message(f"Error: {error_msg}")
        QMessageBox.critical(self, t("error"), t("scan_failed", error=error_msg))

    def _pause_scan(self) -> None:
        """Pause or resume the document scan."""
        if self.scan_worker and self.scan_worker.scanner:
            scanner = self.scan_worker.scanner
            if scanner.is_paused():
                # Resume
                scanner.resume()
                self.pause_btn.setText(t("pause"))
            else:
                # Pause
                scanner.pause()
                self.pause_btn.setText(t("resume"))
                self._log_message(t("pausing_scan"))

    def _stop_scan(self) -> None:
        """Stop the document scan."""
        if not self.is_scanning:
            return

        if self.scan_worker and self.scan_worker.scanner:
            scanner = self.scan_worker.scanner
            scanner.stop()

            # Disable pause and stop buttons immediately
            self.pause_btn.setEnabled(False)
            self.stop_btn.setEnabled(False)

            # Re-enable the scan button after stopping
            self.scan_btn.setEnabled(True)

            # Log the stop action
            self._log_message(t("stopping_scan"))

            # Force UI update
            QApplication.processEvents()

    def _update_progress(self, current: int, total: int) -> None:
        """Update progress bar and file count.

        Args:
            current: Current number of files processed
            total: Total number of files
        """
        if total > 0:
            percentage = int((current / total) * 100)
            self.progress_bar.setValue(percentage)
            # Use format string to display current/total progress
            self.files_label.setText(f"{t('files_scanned')} {current}/{total}")

    def _show_match_details(self) -> None:
        """Show details of selected match in the results table."""
        selected_rows = self.results_table.selectionModel().selectedRows()
        if not selected_rows or not self.scan_results:
            return

        row = selected_rows[0].row()
        matches = self.scan_results.get("matches", [])

        if row >= len(matches):
            return

        match_data = matches[row]
        details = []

        # File info
        details.append(f"File: {match_data.get('file_path', '')}")
        details.append(f"Type: {match_data.get('file_type', '')}")
        details.append(f"Size: {match_data.get('file_size', 0)} bytes\n")

        # Match info
        for match in match_data.get("matches", []):
            details.append(f"Rule: {match.get('rule_name', '')}")
            details.append(f"Description: {match.get('rule_description', '')}")
            details.append(f"Line {match.get('line_number', 0)}: {match.get('match', '')}")
            details.append("\nContext:")
            for ctx_line in match.get("context", []):
                details.append(f"  {ctx_line}")
            details.append("-" * 50)

        self.details_text.setText("\n".join(details))

    def _save_results(self) -> None:
        """Save scan results to JSON file."""
        if not self.scan_results:
            QMessageBox.warning(self, t("warning"), t("no_results_to_save"))
            return

        default_name = f"scan_results_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
        file_path, _ = QFileDialog.getSaveFileName(self, "Save Results", default_name, "JSON Files (*.json)")

        if file_path:
            try:
                with open(file_path, "w", encoding="utf-8") as f:
                    json.dump(self.scan_results, f, indent=2, ensure_ascii=False)
                self._log_message(t("results_saved_to", path=file_path))
                QMessageBox.information(self, t("success"), t("results_saved_to", path=file_path))
            except Exception as e:
                QMessageBox.critical(self, t("error"), t("failed_to_save_results", error=e))

    def _open_results(self) -> None:
        """Open and load previously saved scan results from JSON file."""
        file_path, _ = QFileDialog.getOpenFileName(
            self,
            t("open_results_file", default="Open Scan Results"),
            str(Path.home()),
            t("json_files", default="JSON Files (*.json)"),
        )

        if not file_path:
            return

        try:
            with open(file_path, encoding="utf-8") as f:
                results = json.load(f)

            # Validate the loaded data structure
            if not isinstance(results, dict) or "matches" not in results:
                raise ValueError("Invalid scan results file format")

            # Clear current results and load the new ones
            self._clear_results()
            self.scan_results = results

            # Update summary
            scan_info = results.get("scan_info", {})
            processed = scan_info.get("files_processed", scan_info.get("total_files", 0))
            self.files_label.setText(t("files_scanned").replace("0", f"{processed}/{scan_info.get('total_files', 0)}"))
            self.matches_label.setText(f"{t('files_with_matches')} {scan_info.get('files_with_matches', 0)}")

            # Update progress bar to 100% since this is completed work
            self.progress_bar.setValue(100)

            # Populate results table
            matches = results.get("matches", [])
            self.results_table.setRowCount(len(matches))

            for row, match_data in enumerate(matches):
                file_path = match_data.get("file_path", "")
                file_type = match_data.get("file_type", "")
                match_count = len(match_data.get("matches", []))
                proc_time = match_data.get("metadata", {}).get("processing_time_seconds", 0)

                self.results_table.setItem(row, 0, QTableWidgetItem(Path(file_path).name))
                self.results_table.setItem(row, 1, QTableWidgetItem(file_type))
                self.results_table.setItem(row, 2, QTableWidgetItem(str(match_count)))
                self.results_table.setItem(row, 3, QTableWidgetItem(f"{proc_time:.3f}"))

            # Enable save menu action since we now have results
            if self.save_action:
                self.save_action.setEnabled(True)

            # Log the action
            self._log_message(t("loaded_results_from", path=file_path))
            QMessageBox.information(self, t("success"), t("results_loaded_successfully", path=file_path))

        except Exception as e:
            QMessageBox.critical(self, t("error"), t("failed_to_load_results", error=str(e)))

    def _clear_results(self) -> None:
        """Clear all results and logs."""
        self.scan_results = None
        self.log_text.clear()
        self.results_table.setRowCount(0)
        self.details_text.clear()
        self.files_label.setText(t("files_scanned_zero"))
        self.matches_label.setText(t("files_with_matches_zero"))
        self.progress_bar.setValue(0)

        # Disable save menu action after clearing results
        if self.save_action:
            self.save_action.setEnabled(False)

    def _log_message(self, message: str) -> None:
        """Add message to log text area.

        Args:
            message: Message to log
        """
        timestamp = datetime.now().strftime("%H:%M:%S")
        self.log_text.append(f"[{timestamp}] {message}")

    def _load_config(self) -> None:
        """Load configuration and restore UI state."""
        # Restore window size and position
        width = self.config_manager.get("window_width", 1000)
        height = self.config_manager.get("window_height", 700)
        x = self.config_manager.get("window_x", 100)
        y = self.config_manager.get("window_y", 100)
        self.resize(width, height)
        self.move(x, y)

        # Restore input directory
        input_dir = self.config_manager.get("input_directory", str(Path.cwd()))
        self.dir_edit.setText(input_dir)

        # Restore rules file
        rules_file = self.config_manager.get("rules_file", "rules.json")
        self.rules_edit.setText(rules_file)

        # Restore file types
        file_types = self.config_manager.get("file_types", "pdf,docx,xlsx,pptx,txt,odt,rtf,epub,csv,xml,html,md")
        self.types_edit.setText(file_types)

        # Validate rules file after loading config
        self._validate_rules_file()

    def _save_config(self) -> None:
        """Save current UI state to configuration."""
        # Save window size and position
        self.config_manager.set("window_width", self.width())
        self.config_manager.set("window_height", self.height())
        self.config_manager.set("window_x", self.x())
        self.config_manager.set("window_y", self.y())

        # Save input directory
        input_dir = self.dir_edit.text()
        self.config_manager.set("input_directory", input_dir)
        self.config_manager.add_recent_directory(input_dir)

        # Save rules file
        rules_file = self.rules_edit.text()
        self.config_manager.set("rules_file", rules_file)
        self.config_manager.add_recent_rules_file(rules_file)

        # Save file types
        self.config_manager.set("file_types", self.types_edit.text())

        # Persist to file
        self.config_manager.save_config()

    def _setup_close_handler(self) -> None:
        """Set up window close event handler."""
        # Override closeEvent to save config before closing
        original_close = self.closeEvent

        def close_event(event):
            """Handle close event by saving config."""
            self._save_config()
            original_close(event)

        self.closeEvent = close_event


def main():
    """Main entry point for GUI application."""
    app = QApplication(sys.argv)
    window = DocScanGUI()
    window.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()
