"""Docscan Window module for widgets."""

from __future__ import annotations

import json
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

from PySide2.QtCore import Qt
from PySide2.QtGui import QResizeEvent
from PySide2.QtWidgets import (
    QAction,
    QApplication,
    QComboBox,
    QFileDialog,
    QGridLayout,
    QGroupBox,
    QHBoxLayout,
    QLabel,
    QLineEdit,
    QMainWindow,
    QMessageBox,
    QProgressBar,
    QPushButton,
    QScrollArea,
    QTableWidget,
    QTableWidgetItem,
    QTextEdit,
    QVBoxLayout,
    QWidget,
)
from pytola_core.config import ConfigMixin
from pytola_qtstyles import SimpleThemeManager

from pytola_office.docscan.cli import DocumentScanner, Rule
from pytola_office.docscan.core.scan_worker import ScanWorker
from pytola_office.docscan.widgets.settings_dialog import SettingsDialog


class DocscanGUIConfig(ConfigMixin):
    """Document scanner GUI configuration with persistence support."""

    input_directory: str = str(Path.cwd())
    rules_file: str = "rules.json"
    file_types: str = "pdf,docx,xlsx,pptx,txt,odt,rtf,epub,csv,xml,html,md,jpg,jpeg,png,gif,bmp,tiff"
    use_pdf_ocr: bool = False
    use_process_pool: bool = False
    THREADS: int = 4
    batch_size: int = 50
    window_width: int = 1000
    window_height: int = 700
    window_x: int = 100
    window_y: int = 100
    recent_directories: list[str] = []  # noqa: RUF012
    recent_rules_files: list[str] = []  # noqa: RUF012
    MAX_RECENT_ITEMS: int = 10
    include_image_formats: bool = False


conf = DocscanGUIConfig()


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

    def __init__(self) -> None:
        """Initialize GUI components."""
        super().__init__()
        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()

    def init_ui(self) -> None:
        """Initialize user interface with modern styling."""
        self.setWindowTitle("文档扫描器")
        self.setMinimumSize(1200, 800)

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

        theme_manager = SimpleThemeManager()
        theme_manager.apply_theme(self, "dark")

    def _create_menu_bar(self) -> None:
        menubar = self.menuBar()

        # File menu
        self.file_menu = menubar.addMenu("文件 (&F)")

        self.open_action = QAction("打开结果...", self)
        self.open_action.triggered.connect(self._open_results)
        self.open_action.setShortcut("Ctrl+O")
        self.file_menu.addAction(self.open_action)

        self.file_menu.addSeparator()

        self.save_action = QAction("保存结果", self)
        self.save_action.triggered.connect(self._save_results)
        self.save_action.setShortcut("Ctrl+S")
        self.save_action.setEnabled(False)
        self.file_menu.addAction(self.save_action)

        self.clear_action = QAction("清除结果", self)
        self.clear_action.triggered.connect(self._clear_results)
        self.clear_action.setShortcut("Ctrl+L")
        self.file_menu.addAction(self.clear_action)

        self.file_menu.addSeparator()

        self.exit_action = QAction("退出 (&X)", self)
        self.exit_action.triggered.connect(self.close)
        self.exit_action.setShortcut("Ctrl+Q")
        self.file_menu.addAction(self.exit_action)

        # Settings menu
        self.settings_menu = menubar.addMenu("设置 (&S)")

        self.preferences_action = QAction("首选项 (&P)...", self)
        self.preferences_action.triggered.connect(self._show_settings)
        self.preferences_action.setShortcut("Ctrl+P")
        self.settings_menu.addAction(self.preferences_action)

        # Help menu
        self.help_menu = menubar.addMenu("帮助 (&H)")

        self.about_action = QAction("关于 (&A)", self)
        self.about_action.triggered.connect(self._show_about)
        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.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("就绪")
        self.status_label.setObjectName("status_label")
        self._set_ready_status_style()
        self.status_bar.addPermanentWidget(self.status_label)
        self.status_bar.showMessage("就绪")

    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 _show_about(self) -> None:
        """Show about dialog."""
        QMessageBox.about(self, "关于文档扫描器", "文档扫描器 GUI\n\n版本 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("输入配置")
        input_group.setObjectName("inputPanel")
        input_layout = QVBoxLayout()
        input_group.setLayout(input_layout)

        # Input directory
        dir_layout = QHBoxLayout()
        self.dir_label = QLabel("输入目录:")
        self.dir_edit = QLineEdit(str(Path.cwd()))
        dir_browse_btn = QPushButton("浏览")
        dir_browse_btn.clicked.connect(self._browse_directory)
        self.dir_edit.textChanged.connect(self._on_directory_changed)
        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()
        self.rules_label = QLabel("规则文件:")
        self.rules_edit = QLineEdit("rules.json")
        rules_browse_btn = QPushButton("浏览")
        rules_browse_btn.clicked.connect(self._browse_rules_file)
        # 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()
        self.types_label = QLabel("文件类型:")

        # 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("+ 添加类型")
        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("- 移除类型")
        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("📂 全部类型")
        self.group_combo.addItem("📄 文档文件")
        self.group_combo.addItem("📊 表格文件")
        self.group_combo.addItem("🖼️ 图像文件")
        self.group_combo.addItem("🌐 网页文件")
        self.group_combo.addItem("📦 压缩文件")
        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: QResizeEvent) -> 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:  # noqa: FBT001
        """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 _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):", echo=QLineEdit.Normal
        )
        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 = conf.include_image_formats
        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: int) -> None:
        """Toggle image formats in file types based on checkbox state."""
        conf.include_image_formats = state == 2  # noqa: PLR2004

        if state == 2:  # Checked  # noqa: PLR2004
            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("开始扫描")
        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("暂停")
        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("停止")
        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("结果")
        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("已扫描文件：0")
        self.matches_label = QLabel("包含匹配项的文件：0")
        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("进度日志:")
        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("匹配详情:")
        results_layout.addWidget(self.table_label)
        self.results_table = QTableWidget()
        self.results_table.setColumnCount(4)
        self.results_table.setHorizontalHeaderLabels(["文件", "类型", "匹配数", "时间 (s)"])
        self.results_table.horizontalHeader().setStretchLastSection(True)
        results_layout.addWidget(self.results_table)

        # Match details text
        self.details_label = QLabel("选中匹配项的上下文:")
        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, _: int) -> None:
        """Handle cell click event to show match details regardless of selection change.

        Args:
            row: Row 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 = conf.recent_directories
        start_dir = recent_dirs[0] if recent_dirs else str(Path.cwd())

        dir_path = QFileDialog.getExistingDirectory(self, "选择输入目录", 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 OSError:
            # 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 = conf.recent_rules_files or []
        start_dir = str(Path(recent_files[0]).parent) if recent_files else str(Path.cwd())

        file_path, _ = QFileDialog.getOpenFileName(self, "选择规则文件", start_dir, "JSON 文件 (*.json)")
        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("")
            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("✓")
                    # 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.style().unpolish(self.rules_validation_label)
                self.rules_validation_label.style().polish(self.rules_validation_label)
        except OSError:
            # Invalid path - show red X
            if self.rules_validation_label:
                self.rules_validation_label.setText("✗")
                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, "错误", "无效的输入目录")
            return

        try:
            rules = self._load_rules()
            if not rules:
                QMessageBox.warning(self, "错误", "没有找到有效的规则")
                return
        except (json.JSONDecodeError, OSError) as error:
            QMessageBox.warning(self, "错误", f"加载规则失败：{error}")
            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("暂停")

        # Create scanner
        scanner = DocumentScanner(
            input_dir=input_dir,
            rules=rules,
            file_types=file_types,
            use_pdf_ocr=conf.use_pdf_ocr,
            use_process_pool=conf.use_process_pool,
            batch_size=conf.batch_size,
        )

        # Create and start worker thread
        self.scan_worker = ScanWorker(scanner, conf.THREADS)
        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("已扫描文件:".replace("0", f"{processed}/{scan_info.get('total_files', 0)}"))
        self.matches_label.setText("包含匹配项的文件:")

        # 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 = "扫描完成" if files_processed > 0 or not was_stopped else "扫描已停止"
        self._log_message(status)
        self._log_message(f"在 {len(matches)} 个文件中找到匹配项")

        # Update status bar
        if hasattr(self, "status_label"):
            status_text = f"{status} | 包含匹配项的文件: {scan_info.get('files_with_matches', 0)}"
            self.status_label.setText(status_text)
            self._set_success_status_style()
            self.status_bar.showMessage(status, 5000)

    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"错误: {error_msg}")
            self._set_error_status_style()
            self.status_bar.showMessage("扫描失败", 5000)

        QMessageBox.critical(self, "错误", "扫描失败：{error}")

    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("暂停")
            else:
                # Pause
                scanner.pause()
                self.pause_btn.setText("恢复")
                self._log_message("暂停扫描...")

    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("停止扫描...")

            # 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"已扫描文件: {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, "警告", "没有可保存的结果")
            return

        default_name = f"scan_results_{datetime.now(tz=timezone.utc).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(f"结果已保存至:\t{file_path}")
                QMessageBox.information(self, "成功", f"结果已保存至:\t{file_path}")
            except OSError as error:
                QMessageBox.critical(self, "错误", f"保存结果失败：{error}")

    def _open_results(self) -> None:
        """Open and load previously saved scan results from JSON file."""
        file_path, _ = QFileDialog.getOpenFileName(self, "打开扫描结果", str(Path.home()), "JSON 文件 (*.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:
                QMessageBox.critical(self, "错误", "无效的扫描结果文件格式")
                return

            # 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("已扫描文件:".replace("0", f"{processed}/{scan_info.get('total_files', 0)}"))
            self.matches_label.setText(f"包含匹配项的文件: {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(f"已从以下位置加载结果：{file_path}")
            QMessageBox.information(self, "成功", f"结果已成功从以下位置加载:\n{file_path}")

        except (json.JSONDecodeError, OSError) as error:
            QMessageBox.critical(self, "错误", f"加载结果失败:{error}")

    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("已扫描文件：0")
        self.matches_label.setText("包含匹配项的文件：0")
        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(tz=timezone.utc).strftime("%H:%M:%S")
        self.log_text.append(f"[{timestamp}] {message}")

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