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

from __future__ import annotations

import contextlib
import json
import logging
import os
import sys
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from typing import Any, ClassVar, Final

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

from .docscan import DocumentScanner, Rule
from .lang.zhcn import TRANSLATIONS

# Embedded QSS styles - Consolidated from styles directory
EMBEDDED_STYLESHEET = """
/* Main window and global styles - Modern unified design */
QMainWindow {
    background-color: #f8fafc;
    font-family: 'Microsoft YaHei UI', 'Segoe UI', 'PingFang SC', sans-serif;
    font-size: 16px;
}

QWidget {
    font-family: 'Microsoft YaHei UI', 'Segoe UI', 'PingFang SC', sans-serif;
    font-size: 16px;
    color: #0f172a;
}

#centralWidget {
    background-color: transparent;
}

/* Menu bar and menu styles - Unified blue theme */
QMenuBar {
    background-color: #0f172a;
    color: white;
    border-bottom: 2px solid #3b82f6;
    padding: 4px;
    font-size: 15px;
    font-family: 'Microsoft YaHei UI', 'Segoe UI', sans-serif;
}

QMenuBar::item {
    background: transparent;
    padding: 10px 16px;
    border-radius: 6px;
}

QMenuBar::item:selected {
    background-color: #3b82f6;
}

QMenuBar::item:pressed {
    background-color: #2563eb;
}

QMenu {
    background-color: white;
    color: #0f172a;
    border: 1px solid #e2e8f0;
    border-radius: 10px;
    padding: 8px 0px;
}

QMenu::item {
    padding: 12px 30px;
    font-size: 16px;
    font-family: 'Microsoft YaHei UI', 'Segoe UI', sans-serif;
}

QMenu::item:selected {
    background-color: #dbeafe;
    color: #1d4ed8;
}

/* Status bar styling */
QStatusBar {
    background-color: #f1f5f9;
    border-top: 1px solid #cbd5e1;
    font-size: 14px;
    color: #334155;
    padding: 4px 12px;
}

/* Input panel styling - Unified blue theme */
#inputPanel {
    background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1,
                                    stop:0 #ffffff, stop:1 #f8fafc);
    border: 2px solid #e2e8f0;
    border-radius: 16px;
    font-weight: 500;
    color: #0f172a;
    margin: 0px;
    padding-top: 32px;
}

#inputPanel::title {
    subcontrol-origin: margin;
    subcontrol-position: top center;
    padding: 14px 22px;
    background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
                              stop:0 #3b82f6, stop:1 #1d4ed8);
    color: white;
    border-top-left-radius: 15px;
    border-top-right-radius: 15px;
    border-bottom: 2px solid #1e40af;
    font-family: 'Microsoft YaHei UI', 'Segoe UI', sans-serif;
    font-size: 18px;
    font-weight: 600;
    letter-spacing: 0.5px;
    text-transform: uppercase;
}

/* Results panel styling - Unified blue theme */
#resultsPanel {
    background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1,
                                    stop:0 #ffffff, stop:1 #f8fafc);
    border: 2px solid #e2e8f0;
    border-radius: 16px;
    font-weight: 500;
    color: #0f172a;
    margin: 0px;
    padding-top: 32px;
}

#resultsPanel::title {
    subcontrol-origin: margin;
    subcontrol-position: top center;
    padding: 14px 22px;
    background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
                              stop:0 #3b82f6, stop:1 #1d4ed8);
    color: white;
    border-top-left-radius: 15px;
    border-top-right-radius: 15px;
    border-bottom: 2px solid #1e40af;
    font-family: 'Microsoft YaHei UI', 'Segoe UI', sans-serif;
    font-size: 18px;
    font-weight: 600;
    letter-spacing: 0.5px;
    text-transform: uppercase;
}

/* Button styles - Unified modern blue theme */
QPushButton {
    background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
                              stop:0 #3b82f6, stop:1 #2563eb);
    color: white;
    border: none;
    border-radius: 8px;
    padding: 12px 20px;
    font-size: 15px;
    font-weight: 500;
    font-family: 'Microsoft YaHei UI', 'Segoe UI', sans-serif;
    min-height: 36px;
}

QPushButton:hover {
    background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
                              stop:0 #2563eb, stop:1 #1d4ed8);
}

QPushButton:pressed {
    background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
                              stop:0 #1d4ed8, stop:1 #1e40af);
}

QPushButton:disabled {
    background: #94a3b8;
    color: #cbd5e1;
}

/* Generic browse button styling - Unified blue theme */
QPushButton[text="Browse"], QPushButton[text="浏览"] {
    background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
                              stop:0 #3b82f6, stop:1 #2563eb);
    color: white;
    border: none;
    border-radius: 8px;
}

QPushButton[text="Browse"]:hover, QPushButton[text="浏览"]:hover {
    background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
                              stop:0 #2563eb, stop:1 #1d4ed8);
}

QPushButton[text="Browse"]:pressed, QPushButton[text="浏览"]:pressed {
    background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
                              stop:0 #1d4ed8, stop:1 #1e40af);
}

#scan_btn {
    background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
                              stop:0 #22c55e, stop:1 #16a34a);
    color: white;
    border: none;
    border-radius: 12px;
    padding: 14px 32px;
    font-size: 17px;
    font-weight: 700;
}

#scan_btn:hover {
    background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
                              stop:0 #16a34a, stop:1 #15803d);
    border: 2px solid #22c55e;
    margin: -2px;
}

#scan_btn:pressed {
    background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
                              stop:0 #15803d, stop:1 #166534);
}

#scan_btn:disabled {
    background: #d1d5db;
    color: #6b7280;
}

#pause_btn {
    background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
                              stop:0 #f59e0b, stop:1 #d97706);
    color: white;
    border: none;
    border-radius: 12px;
    padding: 14px 32px;
    font-size: 17px;
    font-weight: 700;
}

#pause_btn:hover {
    background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
                              stop:0 #d97706, stop:1 #b45309);
    border: 2px solid #f59e0b;
    margin: -1px;
}

#pause_btn:pressed {
    background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
                              stop:0 #b45309, stop:1 #92400e);
}

#pause_btn:disabled {
    background: #d1d5db;
    color: #6b7280;
}

#stop_btn {
    background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
                              stop:0 #ef4444, stop:1 #dc2626);
    color: white;
    border: none;
    border-radius: 12px;
    padding: 14px 32px;
    font-size: 17px;
    font-weight: 700;
}

#stop_btn:hover {
    background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
                              stop:0 #dc2626, stop:1 #b91c1c);
    border: 2px solid #ef4444;
    margin: -1px;
}

#stop_btn:pressed {
    background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
                              stop:0 #b91c1c, stop:1 #991b1b);
}

#stop_btn:disabled {
    background: #d1d5db;
    color: #6b7280;
}

/* Specific browse buttons styling - Unified blue theme */
#browse_dir_btn, #browse_rules_btn {
    background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
                              stop:0 #3b82f6, stop:1 #2563eb);
    color: white;
    border: none;
    border-radius: 8px;
}

#browse_dir_btn:hover, #browse_rules_btn:hover {
    background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
                              stop:0 #2563eb, stop:1 #1d4ed8);
}

#browse_dir_btn:pressed, #browse_rules_btn:pressed {
    background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
                              stop:0 #1d4ed8, stop:1 #1e40af);
}

/* Input controls styles - Unified blue theme */
QLineEdit {
    background-color: #ffffff;
    border: 2px solid #cbd5e1;
    border-radius: 8px;
    padding: 12px 16px;
    font-size: 16px;
    font-family: 'Consolas', 'Microsoft YaHei UI', monospace;
    color: #0f172a;
    selection-background-color: #dbeafe;
    min-height: 28px;
}

QLineEdit:focus {
    border: 2px solid #3b82f6;
    background-color: #f8fafc;
    outline: none;
}

QComboBox {
    background-color: #ffffff;
    border: 2px solid #cbd5e1;
    border-radius: 8px;
    padding: 10px 14px;
    font-size: 15px;
    font-family: 'Consolas', 'Microsoft YaHei UI', monospace;
    min-height: 28px;
}

QComboBox:hover {
    border: 2px solid #94a3b8;
}

QComboBox:focus {
    border: 2px solid #3b82f6;
}

QComboBox::drop-down {
    border: none;
    width: 20px;
}

QSpinBox {
    background-color: #ffffff;
    border: 2px solid #cbd5e1;
    border-radius: 8px;
    padding: 10px 14px;
    font-size: 15px;
    font-family: 'Consolas', 'Microsoft YaHei UI', monospace;
    min-height: 28px;
}

QSpinBox:hover {
    border: 2px solid #94a3b8;
}

QSpinBox:focus {
    border: 2px solid #3b82f6;
}

QCheckBox {
    spacing: 8px;
    font-size: 15px;
    font-family: 'Microsoft YaHei UI', 'Segoe UI', sans-serif;
    color: #334155;
}

QCheckBox::indicator {
    width: 18px;
    height: 18px;
    border-radius: 4px;
    border: 2px solid #cbd5e1;
    background-color: white;
}

QCheckBox::indicator:hover {
    border: 2px solid #3b82f6;
}

QCheckBox::indicator:checked {
    background-color: #3b82f6;
    border: 2px solid #3b82f6;
}

/* File types list styles */
QListWidget {
    background-color: #ffffff;
    border: 2px solid #cbd5e1;
    border-radius: 8px;
    padding: 8px;
    font-size: 15px;
    font-family: 'Consolas', 'Microsoft YaHei UI', monospace;
    alternate-background-color: #f8fafc;
    selection-background-color: #dbeafe;
    selection-color: #1e40af;
}

QListWidget::item {
    padding: 6px 12px;
    border-radius: 4px;
    margin: 2px 0;
}

QListWidget::item:hover {
    background-color: #f1f5f9;
}

QListWidget::item:selected {
    background-color: #3b82f6;
    color: white;
}

/* Scrollbar styles - Unified blue theme */
QScrollBar:vertical {
    border: none;
    background-color: #f1f5f9;
    width: 12px;
    border-radius: 6px;
    margin: 2px;
}

QScrollBar::handle:vertical {
    background-color: #94a3b8;
    border-radius: 6px;
    min-height: 20px;
}

QScrollBar::handle:vertical:hover {
    background-color: #64748b;
}

QScrollBar::handle:vertical:pressed {
    background-color: #475569;
}

QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {
    height: 0px;
}

/* Status label styles - Unified blue theme */
#status_label {
    color: #334155;
    font-size: 16px;
    font-weight: 600;
    padding: 8px 16px;
    background-color: rgba(255, 255, 255, 0.95);
    border: 1px solid #cbd5e1;
    border-radius: 8px;
    font-family: 'Consolas', 'Microsoft YaHei UI', monospace;
}

/* Ready state */
.StatusLabelReady {
    color: #334155;
    background-color: rgba(255, 255, 255, 0.95);
    border: 1px solid #cbd5e1;
}

/* Success state */
.StatusLabelSuccess {
    color: #166534;
    background-color: rgba(240, 253, 244, 0.95);
    border: 1px solid #86efac;
}

/* Error state */
.StatusLabelError {
    color: #b91c1c;
    background-color: rgba(254, 242, 242, 0.95);
    border: 1px solid #fecaca;
}

/* Rules file validation styles */
#rules_validation_label {
    font-size: 18px;
    font-weight: bold;
    min-width: 30px;
    text-align: center;
}

/* Valid rules file */
.RulesValid {
    color: #4CAF50;
}

/* Invalid rules file */
.RulesInvalid {
    color: #F44336;
}

/* Labels in input section */
#inputPanel QLabel {
    font-size: 16px;
    font-weight: 500;
    color: #334155;
}

/* Input section QLineEdit specific styles */
#inputPanel QLineEdit {
    font-size: 15px;
    padding: 10px 14px;
    min-height: 32px;
}

/* Input section QPushButton specific styles */
#inputPanel QPushButton {
    min-height: 32px;
}

/* Checkbox styling in input section */
#inputPanel QCheckBox {
    font-size: 15px;
    padding: 8px;
}

/* Summary labels in results section */
#resultsPanel QLabel {
    font-size: 16px;
    font-weight: 600;
    padding: 8px;
    color: #334155;
}

/* Progress bar styling - Unified blue theme */
QProgressBar {
    border: 2px solid #cbd5e1;
    border-radius: 10px;
    text-align: center;
    font-size: 14px;
    font-weight: 600;
    background-color: #f1f5f9;
    min-height: 28px;
}

QProgressBar::chunk {
    background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
                              stop:0 #3b82f6, stop:1 #2563eb);
    border-radius: 8px;
}

/* Log text area styling - Unified blue theme */
QTextEdit {
    background-color: #f8fafc;
    border: 2px solid #cbd5e1;
    border-radius: 8px;
    padding: 12px 16px;
    font-family: 'Consolas', 'Microsoft YaHei UI', monospace;
    font-size: 14px;
    selection-background-color: #dbeafe;
}

/* Results table styling - Unified blue theme */
QTableWidget {
    background-color: white;
    border: 2px solid #cbd5e1;
    border-radius: 8px;
    font-size: 14px;
    gridline-color: #e2e8f0;
    selection-background-color: #dbeafe;
    selection-color: #1e40af;
}

QTableWidget::item {
    padding: 8px;
}

QTableWidget::item:selected {
    background-color: #dbeafe;
    color: #1d4ed8;
}

QHeaderView::section {
    background-color: #f1f5f9;
    padding: 10px 8px;
    border: 1px solid #cbd5e1;
    font-weight: 600;
    font-size: 14px;
    color: #334155;
}

/* Action buttons specific styles - Enhanced styling */
#scan_btn, #pause_btn, #stop_btn {
    min-height: 48px;
    font-family: 'Microsoft YaHei UI', 'Segoe UI', sans-serif;
}
"""

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

# Constants
DEFAULT_WINDOW_WIDTH: Final[int] = 1000
DEFAULT_WINDOW_HEIGHT: Final[int] = 700
DEFAULT_WINDOW_X: Final[int] = 100
DEFAULT_WINDOW_Y: Final[int] = 100
DEFAULT_THREADS: Final[int] = 4
DEFAULT_BATCH_SIZE: Final[int] = 50
MAX_RECENT_ITEMS: Final[int] = 10
DEFAULT_FILE_TYPES: Final[str] = "pdf,docx,xlsx,pptx,txt,odt,rtf,epub,csv,xml,html,md,jpg,jpeg,png,gif,bmp,tiff"

# UI Styling Constants
UI_LABEL_MIN_WIDTH: Final[int] = 130
UI_INPUT_MIN_HEIGHT: Final[int] = 36
UI_BUTTON_MIN_HEIGHT: Final[int] = 32
UI_LAYOUT_SPACING_SMALL: Final[int] = 8
UI_LAYOUT_SPACING_MEDIUM: Final[int] = 12
UI_LAYOUT_SPACING_LARGE: Final[int] = 16
UI_LAYOUT_MARGIN_TOP: Final[int] = 25
UI_LAYOUT_MARGIN_SIDES: Final[int] = 20
UI_LAYOUT_MARGIN_BOTTOM: Final[int] = 20
UI_BORDER_RADIUS: Final[int] = 10

# 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 pytola.docscan.lang.eng import ENGLISH_DEFAULTS as EN_TRANSLATIONS
        except ImportError:
            try:
                from docscan.lang.eng import ENGLISH_DEFAULTS as EN_TRANSLATIONS
            except ImportError:
                EN_TRANSLATIONS = {}

        text = EN_TRANSLATIONS.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


@dataclass
class DocScanConfig:
    """Document scanner GUI configuration with persistence support."""

    input_directory: str = str(Path.cwd())
    rules_file: str = "rules.json"
    file_types: str = DEFAULT_FILE_TYPES
    use_pdf_ocr: bool = False
    use_process_pool: bool = False
    threads: int = DEFAULT_THREADS
    batch_size: int = DEFAULT_BATCH_SIZE
    window_width: int = DEFAULT_WINDOW_WIDTH
    window_height: int = DEFAULT_WINDOW_HEIGHT
    window_x: int = DEFAULT_WINDOW_X
    window_y: int = DEFAULT_WINDOW_Y
    recent_directories: list[str] = field(default_factory=list)
    recent_rules_files: list[str] = field(default_factory=list)
    include_image_formats: bool = False
    language: str = "zh_CN"  # Add language setting with default to Chinese

    def __post_init__(self) -> None:
        """Initialize configuration after creation."""
        if self.recent_directories is None:
            self.recent_directories = []
        if self.recent_rules_files is None:
            self.recent_rules_files = []


class ConfigManager:
    """Manage GUI configuration persistence using dataclass pattern."""

    MAX_RECENT_ITEMS: ClassVar[int] = MAX_RECENT_ITEMS
    DEFAULT_CONFIG: ClassVar[DocScanConfig] = DocScanConfig()

    def __init__(self, config_file: Path | None = 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() / ".pytola"
            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) -> DocScanConfig:
        """Load configuration from file.

        Returns
        -------
            Configuration dataclass
        """
        if self.config_file.exists():
            try:
                with Path(self.config_file).open(encoding="utf-8") as f:
                    config_data = json.load(f)

                # Helper function to get config value with fallback
                def get_config_value(key: str, default_attr: str):
                    return config_data.get(
                        key,
                        getattr(self.DEFAULT_CONFIG, default_attr),
                    )

                # Create config from loaded data, falling back to defaults
                return DocScanConfig(
                    input_directory=get_config_value(
                        "input_directory",
                        "input_directory",
                    ),
                    rules_file=get_config_value("rules_file", "rules_file"),
                    file_types=get_config_value("file_types", "file_types"),
                    use_pdf_ocr=get_config_value("use_pdf_ocr", "use_pdf_ocr"),
                    use_process_pool=get_config_value(
                        "use_process_pool",
                        "use_process_pool",
                    ),
                    threads=get_config_value("threads", "threads"),
                    batch_size=get_config_value("batch_size", "batch_size"),
                    window_width=get_config_value("window_width", "window_width"),
                    window_height=get_config_value("window_height", "window_height"),
                    window_x=get_config_value("window_x", "window_x"),
                    window_y=get_config_value("window_y", "window_y"),
                    recent_directories=get_config_value(
                        "recent_directories",
                        "recent_directories",
                    ),
                    recent_rules_files=get_config_value(
                        "recent_rules_files",
                        "recent_rules_files",
                    ),
                    include_image_formats=get_config_value(
                        "include_image_formats",
                        "include_image_formats",
                    ),
                    language=get_config_value("language", "language"),
                )
            except (OSError, json.JSONDecodeError) as e:
                logger.warning(f"Failed to load config: {e}. Using defaults.")
        return DocScanConfig()

    def save_config(self) -> None:
        """Save configuration to file."""
        try:
            config_dict = {
                "input_directory": self.config.input_directory,
                "rules_file": self.config.rules_file,
                "file_types": self.config.file_types,
                "use_pdf_ocr": self.config.use_pdf_ocr,
                "use_process_pool": self.config.use_process_pool,
                "threads": self.config.threads,
                "batch_size": self.config.batch_size,
                "window_width": self.config.window_width,
                "window_height": self.config.window_height,
                "window_x": self.config.window_x,
                "window_y": self.config.window_y,
                "recent_directories": self.config.recent_directories,
                "recent_rules_files": self.config.recent_rules_files,
                "include_image_formats": self.config.include_image_formats,
                "language": self.config.language,
            }
            with Path(self.config_file).open("w", encoding="utf-8") as f:
                json.dump(config_dict, 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 getattr(self.config, key, default)

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

        Args:
            key: Configuration key
            value: Value to set
        """
        if hasattr(self.config, key):
            setattr(self.config, key, value)

    def _add_recent_item(self, items: list[str], new_item: str) -> list[str]:
        """Add item to recent items list with deduplication and size limiting.

        Args:
            items: Current list of recent items
            new_item: Item to add

        Returns
        -------
            Updated list of recent items
        """
        # Remove if already exists
        filtered_items = [item for item in items if item != new_item]
        # Add to front
        filtered_items.insert(0, new_item)
        # Keep only MAX_RECENT_ITEMS
        return filtered_items[: self.MAX_RECENT_ITEMS]

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

        Args:
            directory: Directory path to add
        """
        self.config.recent_directories = self._add_recent_item(
            self.config.recent_directories,
            directory,
        )

    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
        """
        self.config.recent_rules_files = self._add_recent_item(
            self.config.recent_rules_files,
            rules_file,
        )


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

    # Progress reporting signals
    progress_message = Signal(str)
    progress_update = Signal(int, int)

    # Completion signals
    scan_finished = Signal(dict)
    scan_error = Signal(str)


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

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

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

                def emit(self, record) -> None:
                    # Format the message and send it via signal
                    try:
                        message = self.format(record)
                        # Only emit non-empty messages
                        if message and message.strip():
                            self.signal.emit(message)
                    except Exception:
                        # Silently ignore formatting errors
                        pass

            # Create handler for capturing logs
            handler = ProgressHandler(self.signals.progress_message)
            handler.setFormatter(logging.Formatter("%(message)s"))

            # Add handler to multiple loggers to catch all messages
            loggers_to_monitor = [
                logging.getLogger(__name__),  # Main GUI logger
                logging.getLogger("docscan"),  # DocScan module logger
                logging.getLogger("pytola.docscan"),  # Full module path logger
                logging.getLogger(),  # Root logger
                logging.getLogger("docscan.docscan"),  # Direct module logger
            ]

            # Store original handlers for cleanup
            original_handlers = {}
            for logger in loggers_to_monitor:
                original_handlers[logger] = list(logger.handlers)
                logger.addHandler(handler)
                logger.setLevel(logging.INFO)

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

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

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

            # Clean up handlers
            for logger in loggers_to_monitor:
                if logger in original_handlers:
                    # Remove our handler while preserving original ones
                    logger.removeHandler(handler)
                    # Restore original handlers
                    logger.handlers = original_handlers[logger]

            self.signals.progress_message.emit(t("scan_complete"))
            self.signals.scan_finished.emit(results)
        except Exception as e:
            # Ensure handler is removed even if error occurs
            try:
                loggers_to_monitor = [
                    logging.getLogger(__name__),
                    logging.getLogger("docscan"),
                    logging.getLogger("pytola.docscan"),
                    logging.getLogger(),
                    logging.getLogger("docscan.docscan"),
                ]
                for logger in loggers_to_monitor:
                    logger.removeHandler(handler)
            except Exception:
                pass
            self.signals.scan_error.emit(str(e))


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

    def __init__(self, parent=None, config_manager=None) -> 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) -> None:
        """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)",
            ),
        )

        self.include_images_checkbox = QCheckBox(
            t("include_image_formats", default="Include Image Formats"),
        )
        self.include_images_checkbox.setToolTip(
            t(
                "include_image_formats_tooltip",
                default="Include image formats (jpg, jpeg, png, gif, bmp, tiff) in scan",
            ),
        )

        processing_layout.addWidget(self.ocr_checkbox)
        processing_layout.addWidget(self.process_pool_checkbox)
        processing_layout.addWidget(self.include_images_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) -> None:
        """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.setWindowTitle(
                        t("language_settings", default="Language Settings"),
                    )  # type: ignore
                elif "Processing" in group_box.title():  # type: ignore
                    group_box.setWindowTitle(
                        t("processing_options", default="Processing Options"),
                    )  # type: ignore
                elif "Performance" in group_box.title():  # type: ignore
                    group_box.setWindowTitle(
                        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) -> None:
        """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.include_images_checkbox.setChecked(
                self.config_manager.get("include_image_formats", 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) -> None:
        """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(
                "include_image_formats",
                self.include_images_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) -> None:
        """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(
                "include_image_formats",
                self.include_images_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 with full hot update.

        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"

        # Update all translatable UI elements in parent window
        self._update_main_window_elements()
        self._update_menu_elements()
        self._update_toolbar_elements()
        self._update_status_elements()
        self._update_input_section_elements()
        self._update_results_section_elements()
        self._update_action_buttons()
        self._update_file_type_elements()

        # Force complete UI refresh
        self.main_window.update()
        QApplication.processEvents()

        # Language switch completed silently - no modal dialog
        # The UI update itself serves as confirmation

    def _update_main_window_elements(self) -> None:
        """Update main window title and core elements."""
        self.main_window.setWindowTitle(t("window_title"))

        # Update window properties if needed
        if hasattr(self.main_window, "statusBar"):
            status_bar = self.main_window.statusBar()
            if status_bar:
                status_bar.setToolTip(
                    t("status_bar_tooltip", default="Current application status"),
                )

    def _update_menu_elements(self) -> None:
        """Update all menu elements."""
        # Update menu bar
        if hasattr(self.main_window, "file_menu") and self.main_window.file_menu:
            self.main_window.file_menu.setTitle(t("file_menu", default="&File"))

        if hasattr(self.main_window, "settings_menu") and self.main_window.settings_menu:
            self.main_window.settings_menu.setTitle(
                t("settings_menu", default="&Settings"),
            )

        if hasattr(self.main_window, "help_menu") and self.main_window.help_menu:
            self.main_window.help_menu.setTitle(t("help_menu", default="&Help"))

        # Update menu actions
        if hasattr(self.main_window, "save_action") and self.main_window.save_action:
            self.main_window.save_action.setText(
                t("save_results", default="&Save Results"),
            )
            self.main_window.save_action.setToolTip(
                t("save_results_tooltip", default="Save scan results to file"),
            )

        if hasattr(self.main_window, "clear_action") and self.main_window.clear_action:
            self.main_window.clear_action.setText(
                t("clear_results", default="&Clear Results"),
            )
            self.main_window.clear_action.setToolTip(
                t("clear_results_tooltip", default="Clear current results"),
            )

        if hasattr(self.main_window, "open_action") and self.main_window.open_action:
            self.main_window.open_action.setText(
                t("open_results", default="&Open Results"),
            )
            self.main_window.open_action.setToolTip(
                t("open_results_tooltip", default="Open previously saved results"),
            )

        if hasattr(self.main_window, "preferences_action") and self.main_window.preferences_action:
            self.main_window.preferences_action.setText(
                t("preferences", default="&Preferences"),
            )

        if hasattr(self.main_window, "exit_action") and self.main_window.exit_action:
            self.main_window.exit_action.setText(t("exit", default="E&xit"))

        if hasattr(self.main_window, "about_action") and self.main_window.about_action:
            self.main_window.about_action.setText(t("about", default="&About"))

    def _update_toolbar_elements(self) -> None:
        """Update toolbar elements if they exist."""
        # Toolbar updates would go here if toolbar is implemented

    def _update_status_elements(self) -> None:
        """Update status bar and other status elements."""
        if hasattr(self.main_window, "status_label") and self.main_window.status_label:
            # Update status label text based on current state
            current_text = self.main_window.status_label.text()
            if "Ready" in current_text or "准备" in current_text:
                self.main_window.status_label.setText(
                    t("ready_status", default="Ready"),
                )
            elif "Scanning" in current_text or "扫描" in current_text:
                self.main_window.status_label.setText(
                    t("scanning_status", default="Scanning..."),
                )
            elif "Completed" in current_text or "完成" in current_text:
                self.main_window.status_label.setText(
                    t("completed_status", default="Completed"),
                )

    def _update_input_section_elements(self) -> None:
        """Update all elements in the input configuration section."""
        # Update group box titles
        self._update_group_box_titles()

        # Update input labels
        self._update_input_labels()

        # Update button texts
        self._update_input_buttons()

        # Update validation messages
        self._update_validation_elements()

    def _update_group_box_titles(self) -> None:
        """Update group box titles in the main layout."""
        central_widget = self.main_window.centralWidget()
        if not central_widget:
            return

        layout = central_widget.layout()
        if not layout:
            return

        for i in range(layout.count()):
            item = layout.itemAt(i)
            if item and item.widget() and isinstance(item.widget(), QGroupBox):
                group_box = item.widget()
                current_title = group_box.title()

                # Map current titles to new translations
                title_mapping = {
                    "Input Configuration": t("input_config_tab"),
                    "输入配置": t("input_config_tab"),
                    "Results": t("results"),
                    "结果": t("results"),
                }

                if current_title in title_mapping:
                    group_box.setTitle(title_mapping[current_title])

    def _update_input_labels(self) -> None:
        """Update input field labels."""
        # Update directory input label
        if hasattr(self.main_window, "dir_label") and self.main_window.dir_label:
            self.main_window.dir_label.setText(t("input_directory"))

        # Update rules file label
        if hasattr(self.main_window, "rules_label") and self.main_window.rules_label:
            self.main_window.rules_label.setText(t("rules_file"))

        # Update file types label
        if hasattr(self.main_window, "types_label") and self.main_window.types_label:
            self.main_window.types_label.setText(t("file_types"))

    def _update_input_buttons(self) -> None:
        """Update input section buttons."""
        # Update browse buttons
        if hasattr(self.main_window, "browse_btn") and self.main_window.browse_btn:
            self.main_window.browse_btn.setText(t("browse"))
            self.main_window.browse_btn.setToolTip(
                t("browse_directory_tooltip", default="Browse for input directory"),
            )

        if hasattr(self.main_window, "browse_rules_btn") and self.main_window.browse_rules_btn:
            self.main_window.browse_rules_btn.setText(t("browse"))
            self.main_window.browse_rules_btn.setToolTip(
                t("browse_rules_tooltip", default="Browse for rules file"),
            )

    def _update_validation_elements(self) -> None:
        """Update validation-related UI elements."""
        if hasattr(self.main_window, "rules_validation_label") and self.main_window.rules_validation_label:
            # The validation label content stays the same (✓ or ✗)
            # But we ensure the styling is reapplied
            self.main_window.rules_validation_label.style().unpolish(
                self.main_window.rules_validation_label,
            )
            self.main_window.rules_validation_label.style().polish(
                self.main_window.rules_validation_label,
            )

    def _update_file_type_elements(self) -> None:
        """Update file type related UI elements including group combo and buttons."""
        # Update file type group combo box items
        if hasattr(self.main_window, "group_combo") and self.main_window.group_combo:
            # Store current selection
            current_text = self.main_window.group_combo.currentText()

            # Clear and repopulate with translated items
            self.main_window.group_combo.clear()
            group_items = [
                ("all_types_group", "📂 全部类型", "📂 All Types"),
                ("document_files_group", "📄 文档文件", "📄 Document Files"),
                ("spreadsheet_files_group", "📊 表格文件", "📊 Spreadsheet Files"),
                ("image_files_group", "🖼️ 图像文件", "🖼️ Image Files"),
                ("web_files_group", "🌐 网页文件", "🌐 Web Files"),
                ("archive_files_group", "📦 压缩文件", "📦 Archive Files"),
            ]

            for _key, zh_text, en_text in group_items:
                display_text = zh_text if USE_CHINESE else en_text
                self.main_window.group_combo.addItem(display_text)

            # Restore selection if possible
            for i in range(self.main_window.group_combo.count()):
                if self.main_window.group_combo.itemText(i) == current_text:
                    self.main_window.group_combo.setCurrentIndex(i)
                    break

        # Update file type buttons text
        if hasattr(self.main_window, "add_type_btn") and self.main_window.add_type_btn:
            self.main_window.add_type_btn.setText(t("add_type_button"))

        if hasattr(self.main_window, "remove_type_btn") and self.main_window.remove_type_btn:
            self.main_window.remove_type_btn.setText(t("remove_type_button"))

    def _update_results_section_elements(self) -> None:
        """Update all elements in the results section."""
        # Update summary labels
        if hasattr(self.main_window, "files_label") and self.main_window.files_label:
            current_text = self.main_window.files_label.text()
            if "Files Scanned:" in current_text or "已扫描文件:" in current_text:
                # Preserve the numeric part but update the label
                parts = current_text.split()
                if len(parts) >= 2:
                    count_part = parts[-1]  # Get the "current/total" part
                    self.main_window.files_label.setText(
                        f"{t('files_scanned')} {count_part}",
                    )
                else:
                    self.main_window.files_label.setText(
                        t("files_scanned_zero", default="Files Scanned: 0/0"),
                    )

        if hasattr(self.main_window, "matches_label") and self.main_window.matches_label:
            current_text = self.main_window.matches_label.text()
            if "Files with Matches:" in current_text or "包含匹配项的文件:" in current_text:
                # Extract and preserve the number
                import re

                match = re.search(r"(\d+)$", current_text)
                if match:
                    count = match.group(1)
                    self.main_window.matches_label.setText(
                        f"{t('files_with_matches')} {count}",
                    )
                else:
                    self.main_window.matches_label.setText(
                        t("files_with_matches_zero", default="Files with Matches: 0"),
                    )

        # Update section labels
        if hasattr(self.main_window, "log_label") and self.main_window.log_label:
            self.main_window.log_label.setText(t("progress_log"))

        if hasattr(self.main_window, "table_label") and self.main_window.table_label:
            self.main_window.table_label.setText(t("match_details"))

        if hasattr(self.main_window, "details_label") and self.main_window.details_label:
            self.main_window.details_label.setText(t("selected_match_context"))

        # Update table headers
        if hasattr(self.main_window, "results_table") and self.main_window.results_table:
            self.main_window.results_table.setHorizontalHeaderLabels(
                [
                    t("file"),
                    t("type"),
                    t("matches"),
                    t("time"),
                ]
            )

    def _update_action_buttons(self) -> None:
        """Update action buttons text and tooltips."""
        # Update main action buttons
        if hasattr(self.main_window, "scan_btn") and self.main_window.scan_btn:
            self.main_window.scan_btn.setText(t("start_scan"))
            self.main_window.scan_btn.setToolTip(
                t("start_scan_tooltip", default="Start document scanning"),
            )

        if hasattr(self.main_window, "pause_btn") and self.main_window.pause_btn:
            # Preserve current state (Pause/Resume)
            current_text = self.main_window.pause_btn.text()
            if "Pause" in current_text or "暂停" in current_text:
                self.main_window.pause_btn.setText(t("pause"))
            else:
                self.main_window.pause_btn.setText(t("resume"))
            self.main_window.pause_btn.setToolTip(
                t("pause_resume_tooltip", default="Pause or resume scanning"),
            )

        if hasattr(self.main_window, "stop_btn") and self.main_window.stop_btn:
            self.main_window.stop_btn.setText(t("stop"))
            self.main_window.stop_btn.setToolTip(
                t("stop_tooltip", default="Stop scanning"),
            )


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

    def __init__(self) -> None:
        """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._apply_configured_language()  # Apply language from config
        self._setup_close_handler()

    def init_ui(self) -> None:
        """Initialize user interface with modern styling."""
        self.setWindowTitle(t("window_title"))
        self.setMinimumSize(1200, 800)

        # Apply modern window styling from external QSS files
        self._load_stylesheet()

        # Create central widget with splitter
        central_widget = QWidget()
        central_widget.setObjectName("centralWidget")
        self.setCentralWidget(central_widget)
        main_layout = QVBoxLayout(central_widget)
        main_layout.setSpacing(20)  # Increase spacing between sections
        main_layout.setContentsMargins(25, 25, 25, 25)  # Increase outer margins

        # Create menu bar
        self._create_menu_bar()

        # Create status bar
        self._create_status_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) -> None:
        """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.open_action.setShortcut("Ctrl+O")
        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.setShortcut("Ctrl+S")
        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.clear_action.setShortcut("Ctrl+L")
        self.file_menu.addAction(self.clear_action)

        self.file_menu.addSeparator()

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

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

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

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

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

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

    def _create_status_bar(self) -> None:
        """Create status bar for displaying application status."""
        self.status_bar = self.statusBar()
        self.status_label = QLabel(t("ready_status", default="Ready"))
        self.status_label.setObjectName("status_label")
        self._set_ready_status_style()
        self.status_bar.addPermanentWidget(self.status_label)
        self.status_bar.showMessage(t("ready_status", default="Ready"))

    def _set_ready_status_style(self) -> None:
        """Set status label style for ready state."""
        # Styles are now managed by external QSS files
        # This method is kept for API compatibility

    def _set_success_status_style(self) -> None:
        """Set status label style for success state."""
        # Styles are now managed by external QSS files
        # This method is kept for API compatibility

    def _set_error_status_style(self) -> None:
        """Set status label style for error state."""
        # Styles are now managed by external QSS files
        # This method is kept for API compatibility

    def _get_modern_stylesheet(self) -> str:
        """Get the embedded modern stylesheet.

        Returns
        -------
            str: Complete CSS stylesheet string
        """
        return EMBEDDED_STYLESHEET

    def _load_stylesheet(self) -> None:
        """Load embedded stylesheet for the application.

        Uses consolidated embedded stylesheet for all UI styling.
        """
        try:
            self.setStyleSheet(EMBEDDED_STYLESHEET)
        except Exception as e:
            logger.debug(f"Failed to apply embedded stylesheet: {e}")
            # Minimal fallback styling
            self.setStyleSheet("QMainWindow { background-color: #f0f0f0; }")

    def _show_about(self) -> None:
        """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_group.setObjectName("inputPanel")
        input_layout = QVBoxLayout()
        input_layout.setSpacing(
            UI_LAYOUT_SPACING_SMALL,
        )  # Use smaller spacing between input rows
        input_layout.setContentsMargins(
            UI_LAYOUT_MARGIN_SIDES,
            UI_LAYOUT_MARGIN_TOP - 5,
            UI_LAYOUT_MARGIN_SIDES,
            UI_LAYOUT_MARGIN_BOTTOM - 5,
        )  # Reduce padding for more compact layout
        input_group.setLayout(input_layout)

        # Input directory
        dir_layout = QHBoxLayout()
        dir_layout.setSpacing(UI_LAYOUT_SPACING_SMALL)
        self.dir_label = self._create_input_label("input_directory")
        self.dir_edit = self._create_input_field(str(Path.cwd()))
        dir_browse_btn = self._create_styled_button("browse", "browse_dir_btn")
        dir_browse_btn.clicked.connect(self._browse_directory)  # type: ignore
        self.dir_edit.textChanged.connect(self._on_directory_changed)  # type: ignore
        dir_layout.addWidget(self.dir_label)
        dir_layout.addWidget(self.dir_edit, 1)
        dir_layout.addWidget(dir_browse_btn)
        input_layout.addLayout(dir_layout)

        # Rules file
        rules_layout = QHBoxLayout()
        rules_layout.setSpacing(UI_LAYOUT_SPACING_SMALL)
        self.rules_label = self._create_input_label("rules_file")
        self.rules_edit = self._create_input_field(t("default_rules_file"), 300)
        rules_browse_btn = self._create_styled_button("browse", "browse_rules_btn")
        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.setObjectName("rules_validation_label")
        self.rules_validation_label.setFixedWidth(30)
        rules_layout.addWidget(self.rules_label)
        rules_layout.addWidget(self.rules_edit, 1)
        rules_layout.addWidget(self.rules_validation_label)
        rules_layout.addWidget(rules_browse_btn)
        input_layout.addLayout(rules_layout)

        # File types - improved display using QListWidget
        types_layout = QHBoxLayout()
        types_layout.setSpacing(UI_LAYOUT_SPACING_SMALL)
        self.types_label = self._create_input_label("file_types")

        # Create container widget for file types grid
        types_container = QWidget()
        types_container.setFixedHeight(140)  # Increased height for grid layout
        types_container_layout = QVBoxLayout(types_container)
        types_container_layout.setContentsMargins(0, 0, 0, 0)
        types_container_layout.setSpacing(8)

        # Create scroll area for file types grid
        self.types_scroll_area = QScrollArea()
        self.types_scroll_area.setWidgetResizable(True)
        self.types_scroll_area.setHorizontalScrollBarPolicy(
            Qt.ScrollBarPolicy.ScrollBarAsNeeded,
        )
        self.types_scroll_area.setVerticalScrollBarPolicy(
            Qt.ScrollBarPolicy.ScrollBarAlwaysOff,
        )

        # Create grid widget for file types
        self.types_grid_widget = QWidget()
        self.types_grid_layout = QGridLayout(self.types_grid_widget)
        self.types_grid_layout.setSpacing(4)
        self.types_grid_layout.setContentsMargins(6, 6, 6, 6)

        # Add grid widget to scroll area
        self.types_scroll_area.setWidget(self.types_grid_widget)

        # Add button row for file type management
        button_layout = QHBoxLayout()
        button_layout.setSpacing(5)

        self.add_type_btn = QPushButton(t("add_type_button"))
        self.add_type_btn.setObjectName("add_type_btn")
        self.add_type_btn.setCursor(Qt.PointingHandCursor)
        self.add_type_btn.setStyleSheet(
            "border-radius: 6px !important; min-height: 28px;",
        )
        self.add_type_btn.clicked.connect(self._add_file_type)

        self.remove_type_btn = QPushButton(t("remove_type_button"))
        self.remove_type_btn.setObjectName("remove_type_btn")
        self.remove_type_btn.setCursor(Qt.PointingHandCursor)
        self.remove_type_btn.setStyleSheet(
            "border-radius: 6px !important; min-height: 28px;",
        )
        self.remove_type_btn.clicked.connect(self._remove_file_type)
        self.remove_type_btn.setEnabled(False)

        # Add group selection combo box
        self.group_combo = QComboBox()
        self.group_combo.addItem(t("all_types_group"))
        self.group_combo.addItem(t("document_files_group"))
        self.group_combo.addItem(t("spreadsheet_files_group"))
        self.group_combo.addItem(t("image_files_group"))
        self.group_combo.addItem(t("web_files_group"))
        self.group_combo.addItem(t("archive_files_group"))
        self.group_combo.currentTextChanged.connect(self._filter_file_types)
        self.group_combo.setStyleSheet("min-height: 28px;")

        button_layout.addWidget(self.group_combo)
        button_layout.addSpacing(10)
        button_layout.addWidget(self.add_type_btn)
        button_layout.addWidget(self.remove_type_btn)
        button_layout.addStretch()

        types_container_layout.addWidget(self.types_scroll_area, 1)
        types_container_layout.addLayout(button_layout)

        types_layout.addWidget(self.types_label)
        types_layout.addWidget(types_container, 1)
        input_layout.addLayout(types_layout)

        # Initialize with default file types
        self._initialize_file_types_grid()

        # Handle image formats setting changes
        self._sync_image_formats_setting()

        parent_layout.addWidget(input_group)

    def _initialize_file_types_grid(self) -> None:
        """Initialize file types grid with default values and grouping."""
        # Define file type groups
        self.file_type_groups = {
            "全部类型": [
                "pdf",
                "docx",
                "xlsx",
                "pptx",
                "txt",
                "odt",
                "rtf",
                "epub",
                "csv",
                "xml",
                "html",
                "md",
                "jpg",
                "jpeg",
                "png",
                "gif",
                "bmp",
                "tiff",
                "svg",
                "psd",
                "ai",
                "dwg",
                "dxf",
                "zip",
                "rar",
                "7z",
                "tar",
                "gz",
                "bz2",
                "xz",
                "iso",
                "exe",
            ],
            "文档文件": ["pdf", "docx", "odt", "rtf", "epub", "txt"],
            "表格文件": ["xlsx", "csv", "xml"],
            "图像文件": [
                "jpg",
                "jpeg",
                "png",
                "gif",
                "bmp",
                "tiff",
                "svg",
                "psd",
                "ai",
            ],
            "网页文件": ["html", "md"],
            "压缩文件": ["zip", "rar", "7z", "tar", "gz", "bz2", "xz", "iso"],
        }

        # Create file type buttons in grid layout
        self.file_type_buttons = {}
        self._display_file_types("全部类型")

    def _display_file_types(self, group_name: str) -> None:
        """Display file types for the specified group in adaptive waterfall layout."""
        # Clear existing buttons
        for button in self.file_type_buttons.values():
            button.setParent(None)
        self.file_type_buttons.clear()

        # Get file types for this group
        file_types = self.file_type_groups.get(group_name, [])

        # Calculate adaptive grid dimensions based on available width
        cols = self._calculate_adaptive_columns()

        # Use waterfall layout for sparse content (≤ cols items)
        use_waterfall = len(file_types) <= cols

        if use_waterfall:
            # Waterfall layout: center items in a single row
            start_col = (cols - len(file_types)) // 2  # Center the items
        else:
            # Regular grid layout
            (len(file_types) + cols - 1) // cols
            start_col = 0

        # Create buttons in layout
        for i, file_type in enumerate(file_types):
            if use_waterfall:
                row = 0
                col = start_col + i
            else:
                row = i // cols
                col = i % cols

            button = QPushButton(file_type.upper())
            button.setCheckable(True)
            button.setChecked(True)  # Default to checked
            button.setCursor(Qt.PointingHandCursor)
            button.setStyleSheet("""
                QPushButton {
                    background-color: #e2e8f0;
                    border: 1px solid #cbd5e1;
                    border-radius: 4px;
                    padding: 4px 8px;
                    font-size: 12px;
                    font-weight: 600;
                    min-width: 45px;
                    min-height: 24px;
                    max-width: 60px;
                }
                QPushButton:checked {
                    background-color: #4a6fa5;
                    color: white;
                    border: 1px solid #4a6fa5;
                }
                QPushButton:hover {
                    border: 1px solid #4a6fa5;
                }
                QPushButton:checked:hover {
                    background-color: #3a5a80;
                    border: 1px solid #3a5a80;
                }
            """)
            button.clicked.connect(
                lambda checked, ft=file_type: self._toggle_file_type(ft, checked),
            )

            self.types_grid_layout.addWidget(button, row, col)
            self.file_type_buttons[file_type] = button

    def _calculate_adaptive_columns(self) -> int:
        """Calculate optimal number of columns based on available width."""
        # Get available width (accounting for scroll bar and margins)
        available_width = self.types_scroll_area.viewport().width() - 20  # 20px buffer

        # Ensure minimum width to avoid division by zero or negative values
        available_width = max(100, available_width)  # Minimum 100px width

        # Estimate button width (min + average padding)
        estimated_button_width = 60  # min-width 45px + padding 15px avg

        # Calculate maximum columns that fit
        max_cols = max(1, available_width // estimated_button_width)

        # Limit to reasonable range with better defaults
        return min(max(3, max_cols), 8)  # Between 3-8 columns (increased minimum)

    def resizeEvent(self, event) -> None:
        """Handle window resize events to update grid layout."""
        super().resizeEvent(event)
        # Refresh the current file type display to adapt to new size
        current_group = self.group_combo.currentText()
        if current_group:
            self._filter_file_types(current_group)

    def _filter_file_types(self, group_name: str) -> None:
        """Filter and display file types based on selected group."""
        # Remove emoji prefixes
        clean_group_name = (
            group_name.replace("📂 ", "")
            .replace("📄 ", "")
            .replace("📊 ", "")
            .replace("🖼️ ", "")
            .replace("🌐 ", "")
            .replace("📦 ", "")
        )

        # Map English group names to Chinese keys for file_type_groups
        group_mapping = {
            "All Types": "全部类型",
            "Document Files": "文档文件",
            "Spreadsheet Files": "表格文件",
            "Image Files": "图像文件",
            "Web Files": "网页文件",
            "Archive Files": "压缩文件",
        }

        # Use mapped key if available, otherwise use clean name
        actual_group_key = group_mapping.get(clean_group_name, clean_group_name)

        self._display_file_types(actual_group_key)

    def _toggle_file_type(self, file_type: str, checked: bool) -> None:
        """Handle file type toggle (this is mainly for visual feedback)."""
        # This method can be extended for more complex logic if needed

    def _get_selected_file_types(self) -> list[str]:
        """Get list of currently selected file types."""
        selected = []
        for file_type, button in self.file_type_buttons.items():
            if button.isChecked():
                selected.append(file_type)
        return selected

    def _initialize_file_types(self) -> None:
        """Initialize file types list with default values."""
        default_types = t("default_file_types").split(",")
        self.types_list.clear()
        for file_type in default_types:
            self.types_list.addItem(file_type.strip())

    def _get_file_types_string(self) -> str:
        """Get current file types as comma-separated string."""
        if hasattr(self, "file_type_buttons"):
            # Use grid layout approach
            selected_types = self._get_selected_file_types()
            return ",".join(selected_types)
        # Fallback to list approach
        items = [self.types_list.item(i).text() for i in range(self.types_list.count())]
        return ",".join(items)

    def _set_file_types_from_string(self, types_string: str) -> None:
        """Set file types from comma-separated string."""
        if hasattr(self, "file_type_buttons"):
            # Use grid layout approach
            types = [t.strip().lower() for t in types_string.split(",") if t.strip()]
            for file_type, button in self.file_type_buttons.items():
                button.setChecked(file_type in types)
        else:
            # Fallback to list approach
            self.types_list.clear()
            types = [t.strip() for t in types_string.split(",") if t.strip()]
            for file_type in types:
                self.types_list.addItem(file_type)

    def _add_file_type(self) -> None:
        """Add a new file type to the list."""
        from PySide2.QtWidgets import QInputDialog

        file_type, ok = QInputDialog.getText(
            self,
            "添加文件类型",
            "请输入文件扩展名 (例如: pdf, docx):",
        )
        if ok and file_type.strip():
            clean_file_type = file_type.strip().lower()

            if hasattr(self, "file_type_buttons"):
                # Grid layout approach
                if clean_file_type in self.file_type_buttons:
                    QMessageBox.information(
                        self,
                        "提示",
                        f"文件类型 '{file_type}' 已存在!",
                    )
                    return

                # Add to current group
                current_group = (
                    self.group_combo.currentText()
                    .replace("📂 ", "")
                    .replace("📄 ", "")
                    .replace("📊 ", "")
                    .replace("🖼️ ", "")
                    .replace("🌐 ", "")
                    .replace("📦 ", "")
                )
                if current_group in self.file_type_groups:
                    self.file_type_groups[current_group].append(clean_file_type)

                # Add to all types group
                if "全部类型" in self.file_type_groups and clean_file_type not in self.file_type_groups["全部类型"]:
                    self.file_type_groups["全部类型"].append(clean_file_type)

                # Refresh display
                self._display_file_types(current_group)
            else:
                # List approach (fallback)
                for i in range(self.types_list.count()):
                    if self.types_list.item(i).text().lower() == clean_file_type:
                        QMessageBox.information(
                            self,
                            "提示",
                            f"文件类型 '{file_type}' 已存在!",
                        )
                        return
                self.types_list.addItem(clean_file_type)
                self.remove_type_btn.setEnabled(True)

    def _remove_file_type(self) -> None:
        """Remove selected file type from the list."""
        if hasattr(self, "file_type_buttons"):
            # Grid layout approach
            selected_types = [ft for ft, btn in self.file_type_buttons.items() if btn.isChecked()]
            if selected_types:
                # Remove from current group
                current_group = (
                    self.group_combo.currentText()
                    .replace("📂 ", "")
                    .replace("📄 ", "")
                    .replace("📊 ", "")
                    .replace("🖼️ ", "")
                    .replace("🌐 ", "")
                    .replace("📦 ", "")
                )
                if current_group in self.file_type_groups:
                    self.file_type_groups[current_group] = [
                        ft for ft in self.file_type_groups[current_group] if ft not in selected_types
                    ]

                # Remove from all types group
                if "全部类型" in self.file_type_groups:
                    self.file_type_groups["全部类型"] = [
                        ft for ft in self.file_type_groups["全部类型"] if ft not in selected_types
                    ]

                # Refresh display
                self._display_file_types(current_group)
            else:
                QMessageBox.information(self, "提示", "请选择要删除的文件类型!")
        else:
            # List approach (fallback)
            current_item = self.types_list.currentItem()
            if current_item:
                self.types_list.takeItem(self.types_list.row(current_item))
                self.remove_type_btn.setEnabled(self.types_list.count() > 0)
            else:
                QMessageBox.information(self, "提示", "请先选择要删除的文件类型!")

    def _sync_image_formats_setting(self) -> None:
        """Sync image formats setting with current configuration."""
        include_images = self.config_manager.get("include_image_formats", False)
        if include_images:
            self._add_image_formats()
        else:
            self._remove_image_formats()

    def _add_image_formats(self) -> None:
        """Add image formats to file type groups."""
        image_types = "jpg,jpeg,png,gif,bmp,tiff"
        current_group = (
            self.group_combo.currentText()
            .replace("📂 ", "")
            .replace("📄 ", "")
            .replace("📊 ", "")
            .replace("🖼️ ", "")
            .replace("🌐 ", "")
            .replace("📦 ", "")
        )
        for img_type in image_types.split(","):
            if img_type not in self.file_type_groups[current_group]:
                self.file_type_groups[current_group].append(img_type)
            if img_type not in self.file_type_groups["全部类型"]:
                self.file_type_groups["全部类型"].append(img_type)
        self._display_file_types(current_group)

    def _remove_image_formats(self) -> None:
        """Remove image formats from file type groups."""
        image_types = "jpg,jpeg,png,gif,bmp,tiff"
        current_group = (
            self.group_combo.currentText()
            .replace("📂 ", "")
            .replace("📄 ", "")
            .replace("📊 ", "")
            .replace("🖼️ ", "")
            .replace("🌐 ", "")
            .replace("📦 ", "")
        )
        self.file_type_groups[current_group] = [
            ft for ft in self.file_type_groups[current_group] if ft not in image_types.split(",")
        ]
        self.file_type_groups["全部类型"] = [
            ft for ft in self.file_type_groups["全部类型"] if ft not in image_types.split(",")
        ]
        self._display_file_types(current_group)

    def _toggle_image_formats(self, state) -> None:
        """Toggle image formats in file types based on checkbox state."""
        self.config_manager.set("include_image_formats", state == 2)

        if state == 2:  # Checked
            self._add_image_formats()
        else:  # Unchecked
            self._remove_image_formats()

    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()
        actions_layout.setSpacing(12)

        self.scan_btn = QPushButton(t("start_scan"))
        self.scan_btn.setObjectName("scan_btn")
        self.scan_btn.clicked.connect(self._start_scan)  # pyright: ignore[reportAttributeAccessIssue]
        self.scan_btn.setMinimumHeight(40)  # Reduced height for better proportion
        self.scan_btn.setCursor(Qt.PointingHandCursor)
        # Force consistent border radius
        self.scan_btn.setStyleSheet("border-radius: 10px !important;")

        self.pause_btn = QPushButton(t("pause"))
        self.pause_btn.setObjectName("pause_btn")
        self.pause_btn.clicked.connect(self._pause_scan)  # pyright: ignore[reportAttributeAccessIssue]
        self.pause_btn.setEnabled(False)
        self.pause_btn.setMinimumHeight(40)  # Reduced height for better proportion
        self.pause_btn.setCursor(Qt.PointingHandCursor)
        # Force consistent border radius
        self.pause_btn.setStyleSheet("border-radius: 10px !important;")

        self.stop_btn = QPushButton(t("stop"))
        self.stop_btn.setObjectName("stop_btn")
        self.stop_btn.clicked.connect(self._stop_scan)  # pyright: ignore[reportAttributeAccessIssue]
        self.stop_btn.setEnabled(False)
        self.stop_btn.setMinimumHeight(40)  # Reduced height for better proportion
        self.stop_btn.setCursor(Qt.PointingHandCursor)
        # Force consistent border radius
        self.stop_btn.setStyleSheet("border-radius: 10px !important;")

        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_group.setObjectName("resultsPanel")
        results_layout = QVBoxLayout()
        results_layout.setSpacing(10)
        results_layout.setContentsMargins(16, 16, 16, 16)
        results_group.setLayout(results_layout)

        # Summary labels
        summary_layout = QHBoxLayout()
        summary_layout.setSpacing(20)
        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)
        summary_layout.addStretch()
        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)
        self.progress_bar.setMinimumHeight(28)
        results_layout.addWidget(self.progress_bar)

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

        # Results table
        self.table_label = QLabel(t("match_details"))
        results_layout.addWidget(self.table_label)
        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(self.results_table)

        # Match details text
        self.details_label = QLabel(t("selected_match_context"))
        results_layout.addWidget(self.details_label)
        self.details_text = QTextEdit()
        self.details_text.setReadOnly(True)
        self.details_text.setMaximumHeight(200)
        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 or 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 checkmark if file exists, red 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
            if self.rules_validation_label:
                self.rules_validation_label.setText("")
                self.rules_validation_label.setProperty("class", "")
            return

        try:
            rules_file = Path(rules_path)
            if rules_file.exists() and rules_file.is_file():
                # File exists - show green checkmark
                if self.rules_validation_label:
                    self.rules_validation_label.setText("✓")
                    self.rules_validation_label.setProperty("class", "RulesValid")
                    # Force style update
                    self.rules_validation_label.style().unpolish(
                        self.rules_validation_label,
                    )
                    self.rules_validation_label.style().polish(
                        self.rules_validation_label,
                    )
            # File doesn't exist - show red X
            elif self.rules_validation_label:
                self.rules_validation_label.setText("✗")
                self.rules_validation_label.setProperty("class", "RulesInvalid")
                # Force style update
                self.rules_validation_label.style().unpolish(
                    self.rules_validation_label,
                )
                self.rules_validation_label.style().polish(
                    self.rules_validation_label,
                )
        except Exception:
            # Invalid path - show red X
            if self.rules_validation_label:
                self.rules_validation_label.setText("✗")
                self.rules_validation_label.setProperty("class", "RulesInvalid")
                # Force style update
                self.rules_validation_label.style().unpolish(
                    self.rules_validation_label,
                )
                self.rules_validation_label.style().polish(self.rules_validation_label)

    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:
                msg = f"Rules file not found: {rules_file}"
                raise FileNotFoundError(msg)

        with Path(rules_file).open(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 = self._get_selected_file_types()

        # 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_message.connect(self._log_message)
        self.scan_worker.signals.progress_update.connect(self._update_progress)
        self.scan_worker.signals.scan_finished.connect(self._scan_finished)
        self.scan_worker.signals.scan_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}"))

        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)))

        # Update status bar
        if hasattr(self, "status_label"):
            status_text = f"{status} | {t('files_with_matches')}: {scan_info.get('files_with_matches', 0)}"
            self.status_label.setText(status_text)
            self._set_success_status_style()
            self.status_bar.showMessage(status, 5000)

        # 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}")

        # Update status bar
        if hasattr(self, "status_label"):
            self.status_label.setText(f"{t('error')}: {error_msg}")
            self._set_error_status_style()
            self.status_bar.showMessage(t("scan_failed", error=error_msg), 5000)

        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}")

            # Force UI update to ensure immediate display
            QApplication.processEvents()

    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.extend(
            (
                f"File: {match_data.get('file_path', '')}",
                f"Type: {match_data.get('file_type', '')}",
                f"Size: {match_data.get('file_size', 0)} bytes\n",
            )
        )

        # Match info
        for match in match_data.get("matches", []):
            details.extend(
                (
                    f"Rule: {match.get('rule_name', '')}",
                    f"Description: {match.get('rule_description', '')}",
                    f"Line {match.get('line_number', 0)}: {match.get('match', '')}",
                    "\nContext:",
                )
            )
            details.extend(f"  {ctx_line}" for ctx_line in match.get("context", []))
            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 Path(file_path).open("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 Path(file_path).open(encoding="utf-8") as f:
                results = json.load(f)

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

            # 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}")

        # Force UI update to ensure immediate display
        QApplication.processEvents()

    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,jpg,jpeg,png,gif,bmp,tiff",
        )
        self._set_file_types_from_string(file_types)

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

    def _create_styled_button(
        self,
        text_key: str,
        object_name: str = "",
    ) -> QPushButton:
        """Create a consistently styled button.

        Args:
            text_key: Translation key for button text
            object_name: Optional object name for styling

        Returns
        -------
            Styled QPushButton instance
        """
        button = QPushButton(t(text_key))
        if object_name:
            button.setObjectName(object_name)
        button.setMinimumHeight(UI_BUTTON_MIN_HEIGHT)
        button.setCursor(Qt.PointingHandCursor)
        button.setStyleSheet(f"border-radius: {UI_BORDER_RADIUS}px !important;")
        return button

    def _create_input_label(self, text_key: str) -> QLabel:
        """Create a consistently styled input label.

        Args:
            text_key: Translation key for label text

        Returns
        -------
            Styled QLabel instance
        """
        label = QLabel(t(text_key))
        label.setMinimumWidth(UI_LABEL_MIN_WIDTH)
        return label

    def _create_input_field(
        self,
        default_text: str = "",
        min_width: int = 300,
    ) -> QLineEdit:
        """Create a consistently styled input field.

        Args:
            default_text: Default text for the input field
            min_width: Minimum width for the field

        Returns
        -------
            Styled QLineEdit instance
        """
        field = QLineEdit(default_text)
        field.setMinimumWidth(min_width)
        field.setMinimumHeight(UI_INPUT_MIN_HEIGHT)
        return field

    def _apply_configured_language(self) -> None:
        """Apply language setting from configuration to UI."""
        # Get configured language
        configured_lang = self.config_manager.get("language", "zh_CN")

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

        # Apply language to all UI elements
        if hasattr(self, "settings_dialog") and self.settings_dialog:
            # If settings dialog exists, use its language update method
            self.settings_dialog._apply_language_to_parent(configured_lang)
        else:
            # Create temporary settings dialog to use its update methods
            from pytola.office.docscan.docscan_gui import SettingsDialog

            temp_dialog = SettingsDialog(self, self.config_manager)
            try:
                temp_dialog._apply_language_to_parent(configured_lang)
            finally:
                temp_dialog.close()
                temp_dialog.deleteLater()

            # Force UI refresh
            self.update()
            QApplication.processEvents()

    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._get_file_types_string())

        # 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) -> None:
            """Handle close event by saving config."""
            self._save_config()
            original_close(event)

        self.closeEvent = close_event


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


if __name__ == "__main__":
    main()
