"""Filescanner module for filescan - 支持简单搜索和规则扫描.

重构说明:
- 简化状态管理逻辑
- 优化提示信息显示
- 统一错误处理
- 提高代码可读性
"""

from __future__ import annotations

import json
import re
import sqlite3
from datetime import datetime, timezone
from pathlib import Path

from PySide2.QtCore import QStringListModel, Qt
from PySide2.QtGui import QCloseEvent, QFont, QMoveEvent, QResizeEvent
from PySide2.QtWidgets import QApplication, QFileDialog, QLabel, QListWidgetItem, QMessageBox, QWidget
from pytola_qtstyles import apply_theme, setup_highdpi_scaling

from .models.config import conf
from .models.database import ScanDatabase
from .models.fileinfo import FileInfo
from .models.logger import logger
from .threads.analyse_thread import AnalyseThread
from .threads.count_thread import CountThread
from .threads.db_thread import DBThread
from .widgets.configdialog import ConfigDialog
from .widgets.filescanner_state import FileScannerState, FileScannerStateManager
from .widgets.filescanner_ui import Ui_FileScanner
from .widgets.filetreeviewer import FileTreeViewer
from .widgets.statuslabel import StatusLabel


class FileScanner(QWidget, Ui_FileScanner):
    """文件扫描器主界面 - 支持简单搜索和规则扫描."""

    def __init__(self) -> None:
        super().__init__()
        self.setupUi(self)

        # 初始化状态栏标签(使用自定义 StatusLabel)
        self.status_label = StatusLabel(self.statusbar)
        self.statusbar.addWidget(self.status_label, 1)

        # 扫描目录
        self.current_directory: Path = Path(conf.CURRENT_DIRECTORY)
        self.directories_model = QStringListModel()
        self.directories_model.setStringList(conf.RECENT_DIRECTORIES)
        self.directory_combobox.setModel(self.directories_model)
        self.directory_combobox.setCurrentText(self.current_directory.as_posix())
        logger.info(f"当前目录:{self.current_directory}")

        self.config_dialog = ConfigDialog(parent=self)
        self.files_view = FileTreeViewer(parent=self)

        # 规则扫描相关
        self.count_thread: CountThread | None = None
        self.db_thread: DBThread | None = None
        self.analyse_thread: AnalyseThread | None = None

        # 分析结果收集
        self.analyse_results: list = []  # 存储 AnalyseResult 对象

        # 分析进度计数
        self._analyse_progress_count: int = 0

        # 数据库路径
        self._db_data: dict[str, list[FileInfo]] = {}

        # 标记是否存在历史分析结果
        self._has_analysis_results: bool = False

        # 预编译正则表达式模式
        self._color_patterns = {
            color: re.compile(f"<{color}>(.*?)</{color}>", re.IGNORECASE)
            for color in ["red", "green", "yellow", "blue", "orange"]
        }
        self._style_patterns = {
            "error": re.compile(r"<error>(.*?)</error>", re.IGNORECASE),
            "warning": re.compile(r"<warning>(.*?)</warning>", re.IGNORECASE),
            "success": re.compile(r"<success>(.*?)</success>", re.IGNORECASE),
        }

        # 连接按钮信号
        self.open_button.clicked.connect(self.on_open_directory)
        self.analyse_button.clicked.connect(self.on_start_analyse)
        self.reanalyse_button.clicked.connect(self.on_start_analyse)
        self.stop_button.clicked.connect(self.on_stop_analyse)
        self.config_button.clicked.connect(self.on_show_config)
        self.view_db_button.clicked.connect(self.on_view_database)
        self.directory_combobox.currentTextChanged.connect(self.on_directory_changed)

        # 初始化状态管理器
        self.state_manager = FileScannerStateManager(parent=self)

        # 设置组件映射, 将组件名称映射到实际的对象
        widget_map = {
            "open_button": self.open_button,
            "analyse_button": self.analyse_button,
            "reanalyse_button": self.reanalyse_button,
            "stop_button": self.stop_button,
            "view_db_button": self.view_db_button,
            "config_button": self.config_button,
        }
        self.state_manager.set_widget_map(widget_map)

        # 处理当前目录, 启动时先加载数据库
        if conf.CURRENT_DIRECTORY:
            self._start_load_db_thread(self.current_directory)
        else:
            logger.info("当前目录未设置")
            self.on_update_status("<yellow>未设置目录</yellow>")

    def on_read_db_finished(self, files_info: list[FileInfo], directory: Path) -> None:
        """数据库读取完成回调."""
        logger.info(f"[GUI] on_read_db_finished 被调用,目录:{directory}, 文件数:{len(files_info)}")

        if files_info:
            self._db_data[directory.as_posix()] = files_info
            logger.info(f"找到文件数据:{directory} - {len(files_info)} 个文件")

            # 检查是否存在分析结果
            analysed_count = sum(1 for f in files_info if getattr(f, "analyse_status", "pending") == "completed")
            total_matches = sum(getattr(f, "match_count", 0) for f in files_info)
            self._has_analysis_results = analysed_count > 0

            logger.info(
                f"数据库包含分析结果:{self._has_analysis_results} "
                f"({analysed_count}/{len(files_info)}, {total_matches} 匹配)",
            )

            self.on_get_directory_files(directory)

            # 根据是否存在分析结果切换到不同状态
            if self._has_analysis_results:
                self.state_manager.transition("has_results")
            else:
                self.state_manager.transition("db_loaded")
        else:
            logger.warning(f"数据库为空:{directory}")
            self._has_analysis_results = False
            self.state_manager.transition("db_not_found")
            # 注意:此时已经在 COUNTING 状态,直接启动计数线程即可,不需要再次状态转换
            self._start_count_file_thread()

    def _stop_db_thread(self) -> bool:
        """停止数据库加载线程.

        Returns
        -------
            如果停止了运行中的线程返回 True,否则返回 False
        """
        if self.db_thread is not None:
            # 如果线程还在运行,先请求停止并等待
            if self.db_thread.isRunning():
                logger.info("检测到旧的 db_thread 对象正在运行,正在停止...")
                self.db_thread.quit_read_db()
                self.db_thread.wait(1000)

            # 断开信号连接, 防止重复触发
            try:
                self.db_thread.signal_db_read_finished.disconnect(self.on_read_db_finished)
                self.db_thread.signal_msg.disconnect(self.on_update_msg)
                self.db_thread.signal_status.disconnect(self.on_update_status)
            except (TypeError, RuntimeError):
                pass  # 忽略断开连接的错误

            # 清空 db_thread 引用
            self.db_thread = None
            logger.info("旧数据库线程引用已清除")
            return True
        return False

    def _start_load_db_thread(self, directory: Path) -> None:
        """启动数据库加载线程."""
        logger.info(f"[GUI] _start_load_db_thread 被调用,目录:{directory}")

        # 停止之前的数据库线程
        self._stop_db_thread()

        # 确保在创建新线程前清除旧引用
        if self.db_thread is not None:
            logger.warning(f"[GUI] db_thread 引用仍然存在,强制清除:{self.db_thread}")
            self.db_thread = None

        try:
            self.state_manager.transition("loading_db")
            self.db_thread = DBThread(parent=self, directory=directory)
            # 确保线程对象在启动前被正确初始化
            if self.db_thread is not None:
                self.db_thread.start()
                logger.info(f"[GUI] 数据库加载线程已启动:id<{id(self.db_thread)}>")
            else:
                logger.error("[GUI] 无法创建 DBThread 实例")
                self.on_update_status("<red>❌ 数据库加载失败</red>")
        except (OSError, ValueError, TypeError, RuntimeError) as e:
            logger.error(f"[GUI] 启动数据库加载线程失败:{e}", exc_info=True)
            self.on_update_status(f"<red>❌ 启动失败:{e}</red>")
            # 重置状态
            self.state_manager.force_set_state(FileScannerState.INIT)

    def _start_count_file_thread(self) -> None:
        """启动计数线程."""
        if self.count_thread is None:
            self.count_thread = CountThread(parent=self, directory=Path(self.directory_combobox.currentText()))
            self.count_thread.start()
            logger.info(f"计数线程已启动,目录:{self.directory_combobox.currentText()}")

    def on_state_changed(self, old_state: str, new_state: str) -> None:
        """状态变更回调(由状态管理器通过信号触发).

        Args:
            old_state: 旧状态
            new_state: 新状态
        """
        logger.debug(f"收到状态变更信号:{old_state} -> {new_state}")
        # UI 组件的启用/禁用由状态管理器自动处理
        # 这里可以添加其他需要在状态变更时执行的逻辑

    def load_theme(self, style_name: str) -> None:
        """加载并应用主题样式.

        Args:
            style_name: 主题样式名称
        """
        apply_theme(self, style_name)

    def on_open_directory(self) -> None:
        """获取待搜索文件夹."""
        self.on_update_status("正在选择目录...")

        directory = QFileDialog.getExistingDirectory(self, "选择文件夹")
        if not directory:
            self.on_update_status("<orange>⚠ 未选择目录</orange>")
            logger.info("用户取消了目录选择")
            return

        if not Path(directory).exists():
            self.on_update_status("<red>❌ 目录不存在</red>")
            logger.error(f"目录不存在:{directory}")
            return

        if directory in set(conf.RECENT_DIRECTORIES):
            self.directory_combobox.setCurrentText(directory)
            self.on_update_status(f"<green>✓ 已切换</green> 到目录:<blue>{Path(directory).name}</blue>")
            logger.info(f"切换到已有目录:{directory}")
            return

        self._add_directory(directory)

    def _add_directory(self, directory: str) -> None:
        """添加目录到列表."""
        conf.RECENT_DIRECTORIES.insert(0, Path(directory).as_posix())
        if len(conf.RECENT_DIRECTORIES) > conf.MAX_RECENT_DIRS:
            conf.RECENT_DIRECTORIES = conf.RECENT_DIRECTORIES[: conf.MAX_RECENT_DIRS]

        self.directories_model.setStringList(conf.RECENT_DIRECTORIES)
        self.directory_combobox.setCurrentText(Path(directory).as_posix())
        self.on_update_status(f"<green>✓ 已添加</green> 新目录:<blue>{Path(directory).name}</blue>")
        logger.info(f"添加新目录:{directory}")

    def on_directory_changed(self, directory: str) -> None:
        """目录切换时的回调."""
        directory_path = Path(directory)
        if not directory_path.exists():
            logger.warning(f"目录不存在:{directory}")
            self.on_update_status("<red>❌ 目录不存在</red>")
            return

        logger.info(f"[GUI] on_directory_changed 被调用,目录:{directory}")
        conf.CURRENT_DIRECTORY = directory_path.as_posix()
        # 重要:更新当前目录属性,确保 db_info 能正确获取数据
        self.current_directory = directory_path

        # 重置状态管理器到初始状态,以便重新加载数据库
        logger.info("重置状态管理器到 INIT 状态")
        self.state_manager.force_set_state(FileScannerState.INIT)

        # 重置分析结果标记
        self._has_analysis_results = False

        # 启动新的数据库加载线程
        logger.info(f"准备加载目录:{directory_path}")
        self._start_load_db_thread(directory_path)
        self.on_update_status(f"正在加载目录数据:<blue>{directory_path.name}</blue>")

    def on_get_directory_files(self, directory: Path) -> None:
        """获取目录文件信息."""
        logger.info(f"加载目录文件数据:{directory}")

        if directory.as_posix() not in self._db_data:
            logger.warning(f"目录未在数据库中:{directory}")
            return

        file_count = len(self.db_info)
        logger.info(f"找到 {file_count} 个文件")
        self.filecount_label.setText(f"文件数:{file_count}")

        # 检查是否有分析结果
        analysed_count = sum(1 for f in self.db_info if getattr(f, "analyse_status", "pending") == "completed")
        match_count = sum(getattr(f, "match_count", 0) for f in self.db_info)

        if file_count > 0:
            if analysed_count > 0:
                self.on_update_status(
                    f"<green>✓ 就绪</green> - 共 <green>{file_count}</green> 个文件,"
                    f"已分析 <blue>{analysed_count}</blue> 个,发现 <blue>{match_count}</blue> 处匹配",
                )
                logger.info(f"数据库包含历史分析结果:{analysed_count}/{file_count} 个文件已分析,{match_count} 处匹配")
            else:
                self.on_update_status(f"<green>✓ 就绪</green> - 共 <green>{file_count}</green> 个文件")
        else:
            self.on_update_status("<orange>⚠ 目录为空</orange>")

    def on_start_analyse(self) -> None:
        """开始搜索 - 支持简单搜索和规则扫描."""
        logger.info("[GUI] 开始启动扫描流程")

        if not self.db_info:
            self.on_update_status("<orange>⚠ 数据库为空,先执行文件统计</orange>")
            QMessageBox.information(self, "提示", "数据库中暂无文件信息,将先执行文件统计和文件名验证")
            logger.info("数据库为空,将先执行文件统计")
            return

        try:
            # 切换到搜索状态
            if not self.state_manager.transition("start_searching"):
                self.on_update_status("<red>❌ 无法启动扫描</red>")
                QMessageBox.critical(self, "错误", "无法切换到搜索状态,请重试")
                logger.error("无法切换到搜索状态")
                return

            # 检查工作线程
            if self.analyse_thread and self.analyse_thread.isRunning():
                self.on_update_status("<orange>⚠ 已有扫描任务在执行</orange>")
                QMessageBox.warning(self, "警告", "已有扫描任务在执行,请先停止或等待完成")
                logger.warning("扫描线程已在运行")
                return

            # 创建工作线程
            self.analyse_thread = AnalyseThread(parent=self, fileinfo_list=self.db_info)

            # 重置结果收集和进度计数
            self.analyse_results = []
            self._analyse_progress_count = 0

            logger.info(f"扫描任务已创建,使用 {conf.MAX_WORKERS} 个线程")
            self.analyse_thread.start()

            self.on_update_status(f"🔍 正在扫描:<blue>{self.current_directory}</blue> (包含文件名验证)")
            logger.info("扫描线程已启动")

        except (ValueError, OSError) as error:
            logger.error(f"创建扫描任务失败:{error}", exc_info=True)
            self.on_update_status("<red>❌ 配置错误</red>")
            QMessageBox.critical(self, "错误", f"配置错误:{error}")
            self._finish_scan_cleanup()

    def on_stop_analyse(self) -> None:
        """停止搜索."""
        stopped_anything = False

        # 停止所有线程
        stopped_anything = self._stop_db_thread() or stopped_anything

        if self.count_thread and self.count_thread.isRunning():
            self.count_thread._flag_quit = True
            self.count_thread.wait(1000)
            self.count_thread = None
            stopped_anything = True
            logger.info("计数线程已停止")

        if self.analyse_thread and self.analyse_thread.isRunning():
            # 调用新的 stop 方法
            stop_method = getattr(self.analyse_thread, "stop", None)
            if callable(stop_method):
                stop_method()
            self.analyse_thread.wait(2000)
            stopped_anything = True
            logger.info("扫描线程已停止")

        if stopped_anything:
            self.on_update_msg("<orange>⚠ 扫描已手动停止</orange>")
            self.on_update_status("<orange>⚠ 已停止</orange>")
            self._finish_scan_cleanup()
        else:
            self.on_update_status("<green>✓ 没有运行中的任务</green>")
            logger.info("没有需要停止的任务")

    def on_show_config(self) -> None:
        """显示配置对话框."""
        self.config_dialog.show()

    def _update_progress(self, current: int, total: int) -> None:
        """更新进度."""
        if total <= 0:
            return

        percentage = int((current / total) * 100)
        self.progress_bar.setValue(percentage)
        self.filecount_label.setText(f"文件数:{current}/{total}")

        # 根据进度显示不同的状态提示
        if percentage == 0:
            self.on_update_status("🚀 开始扫描...")
        elif percentage < 30:
            self.on_update_status(f"📊 扫描初期:<blue>{current}/{total}</blue> ({percentage}%)")
        elif percentage < 70:
            self.on_update_status(f"🔄 扫描中期:<blue>{current}/{total}</blue> ({percentage}%)")
        elif percentage < 100:
            self.on_update_status(f"⏳ 扫描后期:<blue>{current}/{total}</blue> ({percentage}%)")
        else:
            self.on_update_status("✅ 扫描完成")

        # 优化:移除 QCoreApplication.processEvents(),Qt 会自动处理事件循环
        # 频繁调用 processEvents 会导致事件积压和 UI 卡顿

    def _finish_scan_cleanup(self) -> None:
        """扫描完成或停止后的清理工作."""
        # 根据是否存在分析结果切换到对应状态
        if self._has_analysis_results:
            target_state = FileScannerState.HAS_ANALYSIS_RESULTS
        else:
            target_state = FileScannerState.FINISH_COUNTING

        if self.state_manager.current_state != target_state:
            self.state_manager.force_set_state(target_state)

        # 重置扫描线程引用
        if self.analyse_thread and not self.analyse_thread.isRunning():
            self.analyse_thread = None

        logger.debug(f"扫描清理工作已完成,切换到状态:{target_state}")

    def on_file_analysed(self, file_path: str, match_count: int, processing_time: float) -> None:
        """单个文件分析完成回调(已废弃,现在不显示单文件详情).

        该方法保留仅用于信号连接兼容,实际不再被调用.
        所有统计信息应在 on_analyse_finished 中统一显示.
        """
        # 此方法不再被调用,因为 analyse_thread.py 已经移除了 signal_file_analysed 的发射
        pass

    def on_batch_processed(self, processed_count: int, total_files: int, elapsed_time: float) -> None:
        """批次处理完成回调 - 在消息列表中显示汇总信息.

        Args:
            processed_count: 已处理文件数
            total_files: 总文件数
            elapsed_time: 已用时间(秒)
        """
        percentage = (processed_count / total_files * 100) if total_files > 0 else 0
        self.on_update_msg(
            f"<info>📊 已处理 {processed_count}/{total_files} 个文件 ({percentage:.1f}%) - "
            f"用时 {elapsed_time:.1f}s</info>",
        )

    def on_analyse_finished(self, results: list) -> None:
        """扫描完成回调.

        Args:
            results: AnalyseResult 对象列表
        """
        logger.info(f"[GUI] 扫描完成,收到 {len(results)} 个结果")

        # 保存结果用于后续数据库写入
        self.analyse_results = results

        # 统计信息
        total_matches = sum(len(r.matches) for r in results)
        files_with_matches = sum(1 for r in results if r.matches)
        total_time = sum(r.processing_time_seconds for r in results)
        error_count = sum(1 for r in results if r.status == "error")

        # 显示统计信息
        self.on_update_msg(
            f"<success>✅ 扫描完成!</success> - "
            f"总文件:<b>{len(results)}</b>, "
            f"匹配文件:<b>{files_with_matches}</b>, "
            f"总匹配数:<b>{total_matches}</b>, "
            f"错误:<b>{error_count}</b>, "
            f"总耗时:<b>{total_time:.2f}s</b>",
        )

        # 准备写入数据库
        if results:
            self.on_update_status("💾 正在保存结果到数据库...")
            self._save_results_to_database_async(results)
        else:
            self._finish_scan_cleanup()
            QMessageBox.information(self, "完成", "✓ 扫描完成但未处理任何文件\n\n提示:检查文件列表是否正确")

    def _scan_error(self, error_msg: str) -> None:
        """扫描错误回调."""
        # 扫描失败时也切换回计数完成状态
        self._finish_scan_cleanup()

        # 记录详细错误信息
        error_display = f"✗ 扫描错误:{error_msg}"
        self.on_update_msg(f"<error>{error_display}</error>")
        self.on_update_status("<red>❌ 扫描出错</red>")

        # 在控制台也记录错误
        logger.error(f"扫描失败:{error_msg}", exc_info=True)

        QMessageBox.critical(self, "错误", f"扫描失败:\n{error_msg}")

    def _save_results_to_database_async(self, results: list) -> None:
        """异步保存扫描结果到数据库(后台线程).

        Args:
            results: AnalyseResult 对象列表
        """
        from .threads.db_thread import DBThread

        db_file = self.current_directory / conf.DB_FILENAME

        # 创建 DBThread 用于保存操作(不需要指定目录)
        self.db_thread = DBThread(self)
        self.db_thread.save_results_to_db(results, db_file)

    def on_db_save_finished(self, files_updated: int, total_matches_saved: int) -> None:
        """数据库保存完成回调.

        Args:
            files_updated: 更新的文件数
            total_matches_saved: 保存的匹配记录数
        """
        logger.info(f"数据库保存完成:更新了 {files_updated} 个文件,保存了 {total_matches_saved} 条匹配记录")

        self.on_update_msg(
            f"<success>💾 数据库保存完成!</success> - "
            f"更新 <b>{files_updated}</b> 个文件,保存 <b>{total_matches_saved}</b> 条匹配记录",
        )
        self.on_update_status("<green>✓ 保存完成</green>")

        # 优化:不再自动显示扫描结果对话框,引导用户到"查看数据库"界面查看详细结果
        if total_matches_saved > 0:
            self.on_update_msg("<blue>提示:</blue> 请点击 <b>[查看数据库]</b> 按钮查看详细扫描结果")

        # 自动刷新文件树视图,显示最新的分析结果
        db_file = self.current_directory / conf.DB_FILENAME
        if db_file.exists():
            logger.debug("自动刷新文件树视图...")
            self.files_view.load_from_database(db_file)

        # 清理和收尾
        self._finish_scan_cleanup()

    def _save_scan_results(self, results: dict) -> None:
        """保存扫描结果到 JSON 文件."""
        timestamp = datetime.now(tz=timezone.utc).strftime("%Y%m%d_%H%M%S")
        default_filename = f"scan_results_{timestamp}.json"

        file_path, _ = QFileDialog.getSaveFileName(self, "💾 保存扫描结果", default_filename, "JSON Files (*.json)")

        if file_path:
            try:
                with Path(file_path).open("w", encoding="utf-8") as f:
                    json.dump(results, f, indent=2, ensure_ascii=False)
                QMessageBox.information(self, "成功", f"✓ 结果已保存到:\n{file_path}")
                logger.info(f"结果已保存到:{file_path}")
            except OSError as error:
                QMessageBox.critical(self, "错误", f"保存失败:\n{error}")
                logger.error(f"保存结果失败:{error}")

    def on_update_current_directory(self, directory: str) -> None:
        """更新当前处理的目录显示.

        Args:
            directory: 当前处理的目录路径
        """
        self.current_file_label.setText(f"当前处理目录:{directory}")

    def on_update_msg(self, msg: str) -> None:
        """添加消息到列表 - 支持富文本颜色显示.

        支持的颜色标记语法:
        - <red>文本</red>: 红色文字
        - <green>文本</green>: 绿色文字
        - <yellow>文本</yellow>: 黄色文字
        - <blue>文本</blue>: 蓝色文字
        - <orange>文本</orange>: 橙色文字
        - <error>文本</error>: 错误样式 (红色 + 粗体)
        - <warning>文本</warning>: 警告样式 (橙色)
        - <success>文本</success>: 成功样式 (绿色)

        Args:
            msg: 消息文本(可包含颜色标记)
        """
        # 颜色映射
        color_map = {"red": "#ff4444", "green": "#006633", "yellow": "#ffcc00", "blue": "#4488ff", "orange": "#ff9900"}

        # 使用预编译的正则表达式替换颜色标签
        processed_msg = msg
        for color_name, color_value in color_map.items():
            pattern = self._color_patterns[color_name]
            replacement = f'<span style="color:{color_value};">' + r"\1" + "</span>"
            processed_msg = pattern.sub(replacement, processed_msg)

        # 使用预编译的正则表达式替换样式标签
        processed_msg = self._style_patterns["error"].sub(
            r'<span style="color:#ff4444;font-weight:bold;">\1</span>',
            processed_msg,
        )
        processed_msg = self._style_patterns["warning"].sub(r'<span style="color:#ff9900;">\1</span>', processed_msg)
        processed_msg = self._style_patterns["success"].sub(r'<span style="color:#006633;">\1</span>', processed_msg)

        # 创建 QListWidgetItem 并设置富文本
        # QListWidgetItem 默认不支持富文本,需要使用 setToolTip 或者自定义 delegate
        # 这里我们创建一个支持富文本的 QLabel 作为 item 的 widget
        label = QLabel(processed_msg)
        label.setTextFormat(Qt.RichText)
        label.setWordWrap(True)  # 允许文本换行

        font = QFont(conf.DEFAULT_FONT_FAMILY, conf.DEFAULT_SMALL_FONT_SIZE)
        if conf.FONT_BOLD:
            font.setBold(True)
        label.setFont(font)
        label.setStyleSheet("background-color: transparent; padding: 0px;")  # 移除背景,添加内边距

        item = QListWidgetItem()
        self.message_list_widget.addItem(item)
        self.message_list_widget.setItemWidget(item, label)  # 将 label 设置为 item 的 widget
        self.message_list_widget.scrollToBottom()

        # 如果是忽略目录的消息,额外记录到日志中
        if "忽略目录" in msg:
            logger.warning(f"[忽略目录] {msg}")

    def on_update_status(self, status: str) -> None:
        """更新状态栏显示 - 支持简单的颜色标记和加载动画.

        Args:
            status: 状态文本(可包含颜色标记)
        """
        self.status_label.set_status_text(status)

    def on_update_filecount(self, count: int) -> None:
        """更新文件计数信息."""
        self.filecount_label.setText(f"文件数:{count}")

    def on_view_database(self) -> None:
        """查看当前目录的数据库文件树."""
        directory = Path(self.directory_combobox.currentText())
        if not directory.exists():
            QMessageBox.warning(self, "警告", "⚠ 当前目录不存在!")
            return

        db_file = directory / conf.DB_FILENAME
        if not db_file.exists():
            QMessageBox.information(
                self,
                "提示",
                f"提示:该目录尚未创建数据库:\n{db_file}\n\n提示:先执行一次扫描即可创建数据库",
            )
            return

        # 加载并显示数据库文件树
        success = self.files_view.load_from_database(db_file)
        if not success:
            QMessageBox.critical(self, "错误", "❌ 加载数据库失败")
            logger.error(f"加载数据库失败:{db_file}")

    def on_update_progress(self, current: int, total: int) -> None:
        """更新进度条显示.

        Args:
            current: 当前进度百分比 (0-100)
            total: 总进度百分比 (固定为 100)
        """
        if total > 0:
            percentage = min(100, max(0, current))  # current 已经是百分比,直接限制在 0-100 范围
            self.progress_bar.setValue(percentage)

            # 根据进度显示不同的状态提示
            if percentage == 30:
                self.on_update_status("目录收集完成,开始统计文件...")
            elif percentage < 100:
                self.on_update_status(f"正在统计:{percentage}%")
            else:
                self.on_update_status("统计完成")

    def on_finish_count(self, files_info: list[FileInfo], directory: Path) -> None:
        """结束计数."""
        count = len(files_info)
        self.filecount_label.setText(f"文件数:{count}")
        self.count_thread = None
        self.state_manager.transition("finish_counting")
        self._db_data[directory.as_posix()] = files_info

        if count > 0:
            self.on_update_status(f"<green>✓ 就绪</green> - 共 <green>{count}</green> 个文件")
        else:
            self.on_update_status("<orange>⚠ 未找到文件</orange>")
        logger.info(f"计数完成:{count} 个文件")

    def on_save_directory_files_db(self, files_info: list[FileInfo], directory: Path) -> None:
        """文件信息准备好,保存到数据库.

        Args:
            files_info: DirFileInfo 对象列表,包含文件路径,类型,大小等信息
            directory: 目录路径
        """
        db_file = directory / conf.DB_FILENAME

        if not files_info:
            logger.warning("文件信息为空")
            self.on_update_msg("<warning>⚠ 文件信息为空,无法保存</warning>")
            return

        db = None
        try:
            db = ScanDatabase(db_file)
            inserted_count = db.save_directory_files(files_info)
        except sqlite3.Error as e:
            logger.error(f"[数据库] 保存文件信息失败:{e}")
            self.on_update_msg(f"<error>✗ 保存文件信息失败:{e}</error>")
        else:
            self.on_update_msg(f"<success>✓ 已保存</success> {inserted_count} 个文件信息到数据库")
            logger.info(f"保存 {inserted_count} 个文件到数据库:{db_file}")
        finally:
            if db is not None:
                db.close()

    def moveEvent(self, event: QMoveEvent) -> None:
        """窗口移动事件处理."""
        logger.debug(f"窗口移动: {event.pos()}")
        conf.WINDOW_POS = [event.pos().x(), event.pos().y()]
        return super().moveEvent(event)

    def resizeEvent(self, event: QResizeEvent) -> None:
        """窗口调整大小事件处理."""
        logger.debug(f"窗口调整大小:{event.size()}")
        conf.WINDOW_SIZE = [event.size().width(), event.size().height()]
        return super().resizeEvent(event)

    def closeEvent(self, event: QCloseEvent) -> None:
        """关闭窗口时清理线程资源.

        防止因线程未正确退出导致的定时器警告和资源泄漏.
        """
        logger.info("正在关闭窗口,清理线程资源...")

        # 停止并等待所有线程
        threads_to_stop = [("计数线程", self.count_thread), ("扫描线程", self.analyse_thread)]

        for thread_name, thread in threads_to_stop:
            if thread and thread.isRunning():
                logger.info(f"正在停止 {thread_name}...")
                # 设置退出标志
                flag_quit_attr = getattr(thread, "flag_quit", None)
                if flag_quit_attr is not None:
                    thread._flag_quit = True
                # 对于 ScanWorker，需要调用 scanner 的 stop 方法
                scanner_attr = getattr(thread, "scanner", None)
                if thread_name == "扫描线程" and scanner_attr is not None:
                    thread.analyser.stop()  # type: ignore[attr-defined]
                # 请求线程退出
                thread.quit()
                # 等待线程退出(最多等待 3 秒)
                if not thread.wait(3000):
                    logger.warning(f"{thread_name} 未能在 3 秒内正常退出")
                else:
                    logger.info(f"{thread_name} 已正常退出")

        # 断开所有信号连接
        try:
            if self.count_thread:
                self.count_thread.signal_update_file_count.disconnect()
                self.count_thread.signal_msg.disconnect()
                self.count_thread.signal_current_directory.disconnect()
                self.count_thread.signal_progress_update.disconnect()

            if self.db_thread:
                self.db_thread.signal_db_read_finished.disconnect()
                self.db_thread.signal_msg.disconnect()
                self.db_thread.signal_status.disconnect()

            if self.analyse_thread:
                pass
        except (TypeError, RuntimeError):
            # 忽略信号断开时的错误
            pass

        logger.info("线程资源清理完成")
        event.accept()

    @property
    def db_info(self) -> list[FileInfo]:
        """获取当前目录的数据库信息."""
        return self._db_data.get(self.current_directory.as_posix(), [])


def main() -> int:
    """运行文件扫描器."""
    app = QApplication([])

    setup_highdpi_scaling()

    window = FileScanner()
    window.setWindowTitle("文件扫描器 - 支持规则匹配")
    window.show()

    return app.exec_()


if __name__ == "__main__":
    main()
