"""
Глобальные константы и конфигурационные модели приложения DSP Scanner.

Аналог секции "НАСТРОЙКИ ПОИСКА" и переменных `$Script:*` из оригинального
dspscanner.ps1, но оформленный в виде типобезопасных датаклассов и enum'ов.
"""
from __future__ import annotations

import os
import platform
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
from typing import List, Set

APP_NAME = "DSP Scanner"
APP_VERSION = "2.0.0"


# --------------------------------------------------------------------------- #
# Пути по умолчанию (кроссплатформенный аналог логики "если есть диск D:")
# --------------------------------------------------------------------------- #

def _default_log_dir() -> Path:
    if platform.system() == "Windows" and Path("D:\\").exists():
        return Path("D:/PS")
    return Path.home() / "DSPScanner" / "logs"


def _default_copy_destination() -> Path:
    if platform.system() == "Windows" and Path("D:\\").exists():
        return Path("D:/EX")
    return Path.home() / "DSPScanner" / "EX"


LOG_DIR = _default_log_dir()
LOG_FILE = LOG_DIR / "search_errors.log"
DEFAULT_COPY_DESTINATION = _default_copy_destination()
REPORTS_DIR = Path.home() / "DSPScanner" / "reports"
SETTINGS_FILE = Path.home() / "DSPScanner" / "settings.json"
SCAN_CONFIG_FILE = Path.home() / "DSPScanner" / "scan_config.json"

# --------------------------------------------------------------------------- #
# Параметры поиска файлов
# --------------------------------------------------------------------------- #

SUPPORTED_EXTENSIONS = (".docx", ".doc", ".txt", ".pdf")

IGNORED_FOLDER_NAMES = {
    "appdata", "application data", "$recycle.bin", "system volume information",
    "programdata", "windows", "temp", "temporary internet files",
    "node_modules", ".git", ".venv", "venv", "__pycache__",
    "$windows.~ws", "$windows.~bt",
}

TEMP_FILE_PREFIXES = ("~$", "~")
CONTEXT_CHARS_DEFAULT = 150
MAX_FILE_SIZE_MB_DEFAULT = 200
MAX_MATCHES_PER_WORD_PER_FILE = 50  # защита от гигантских отчётов на "мусорных" файлах


class DocReadMethod(str, Enum):
    AUTO = "auto"                 # LibreOffice -> COM (Windows) -> эвристика OLE2
    LIBREOFFICE = "libreoffice"
    COM = "com"
    HEURISTIC = "heuristic"

    @property
    def label(self) -> str:
        return {
            DocReadMethod.AUTO: "Авто (рекомендуется)",
            DocReadMethod.LIBREOFFICE: "LibreOffice (наиболее точный)",
            DocReadMethod.COM: "MS Word COM (только Windows)",
            DocReadMethod.HEURISTIC: "Быстрая эвристика (без внешних программ)",
        }[self]


class ExportFormat(str, Enum):
    EXCEL = "xlsx"
    CSV = "csv"
    MARKDOWN = "md"


# --------------------------------------------------------------------------- #
# Конфигурация запуска сканирования
# --------------------------------------------------------------------------- #

@dataclass
class ScanSettings:
    paths: List[str] = field(default_factory=list)
    words: List[str] = field(default_factory=list)
    file_types: Set[str] = field(default_factory=lambda: set(SUPPORTED_EXTENSIONS))
    doc_read_method: DocReadMethod = DocReadMethod.AUTO
    case_sensitive: bool = False
    whole_word: bool = False
    context_chars: int = CONTEXT_CHARS_DEFAULT
    max_file_size_mb: float = MAX_FILE_SIZE_MB_DEFAULT
    # Если 0 — берётся из AppSettings (диалог "Параметры")
    max_workers: int = 0
    use_ocr_for_pdf: bool = True

    @property
    def max_file_size_bytes(self) -> int:
        return int(self.max_file_size_mb * 1024 * 1024)

    def validate(self) -> list[str]:
        """Возвращает список ошибок валидации (пустой — всё в порядке).

        max_workers == 0 — легальное значение «авто»: реальное число потоков
        будет взято из AppSettings (диалог "Параметры") перед запуском
        сканера. Поэтому валидируем только < 0 и нереалистично большие
        значения, чтобы поймать явные ошибки ввода, а не авто-режим.
        """
        errors: list[str] = []
        if not self.paths:
            errors.append("Не указан ни один путь для поиска.")
        if not self.words:
            errors.append("Не указано ни одного ключевого слова.")
        if not self.file_types:
            errors.append("Не выбран ни один тип файлов.")
        if self.max_workers < 0 or self.max_workers > 256:
            errors.append("Число потоков должно быть от 0 (авто) до 256.")
        if self.context_chars < 20:
            errors.append("Слишком мало символов контекста (минимум 20).")
        return errors


# --------------------------------------------------------------------------- #
# Статистика и результаты (аналог $Script:Stats и объектов результатов ps1)
# --------------------------------------------------------------------------- #

@dataclass
class ScanStats:
    processed: int = 0
    found: int = 0
    skipped: int = 0
    errors: int = 0
    locked: int = 0
    corrupted: int = 0
    temp_files: int = 0
    docx_count: int = 0
    doc_count: int = 0
    txt_count: int = 0
    pdf_count: int = 0
    cache_hits: int = 0
    cache_misses: int = 0

    def as_dict(self) -> dict:
        return dict(self.__dict__)


@dataclass
class FileEntry:
    path: Path
    size: int
    modified: float
    extension: str

    @property
    def name(self) -> str:
        return self.path.name


@dataclass
class SearchResult:
    file_name: str
    full_path: str
    word: str                 # поисковый термин (напр. "охран*")
    context: str
    file_type: str
    modified: str
    occurrence_index: int = 1
    matched_text: str = ""    # реально найденная подстрока — для подсветки в контексте


@dataclass
class ErrorEntry:
    file_path: str
    level: str
    message: str
    timestamp: str


@dataclass
class ScanReport:
    results: List[SearchResult] = field(default_factory=list)
    errors: List[ErrorEntry] = field(default_factory=list)
    stats: ScanStats = field(default_factory=ScanStats)
    elapsed_seconds: float = 0.0
    doc_read_method: str = ""
    cancelled: bool = False
