# -*- coding: utf-8 -*-
"""
ripgrep (rg.exe) 封装：日志 stdin 过滤、目录内文件搜索。
"""

from __future__ import annotations

import json
import os
import re
import shutil
import subprocess
import sys
from pathlib import Path
from typing import List, Optional, Tuple

_RG_NAMES = ("rg.exe", "rg")


def find_rg_executable() -> Optional[Path]:
    """解析 rg 可执行文件；找不到返回 None。"""
    env = (os.environ.get("RG_PATH") or "").strip()
    if env:
        p = Path(env)
        if p.is_file():
            return p
    if getattr(sys, "frozen", False):
        base = Path(sys.executable).resolve().parent
        for name in _RG_NAMES:
            cand = base / name
            if cand.is_file():
                return cand
    # resources/bin/rg.exe（开发目录）
    here = Path(__file__).resolve().parent.parent
    project_root = here.parent
    for name in _RG_NAMES:
        cand = project_root / "resources" / "bin" / name
        if cand.is_file():
            return cand
    w = shutil.which("rg")
    if w:
        return Path(w)
    return None


def _build_rg_regex_pattern(pattern: str, whole_word: bool) -> str:
    if whole_word:
        return rf"\b(?:{pattern})\b"
    return pattern


def _utf8_byte_range_to_qt_positions(s: str, start_byte: int, end_byte: int) -> Tuple[int, int]:
    """
    将 UTF-8 字节区间 [start_byte, end_byte) 转为 QTextCursor 使用的 UTF-16 偏移（与 Qt 一致）。
    """
    b = s.encode("utf-8")
    if start_byte < 0 or end_byte > len(b) or start_byte > end_byte:
        raise ValueError("invalid utf-8 byte range for document")
    prefix_start = b[:start_byte].decode("utf-8")
    prefix_end = b[:end_byte].decode("utf-8")
    qt_start = len(prefix_start.encode("utf-16-le")) // 2
    qt_end = len(prefix_end.encode("utf-16-le")) // 2
    return qt_start, qt_end


def rg_match_spans_qt(
    full_text: str,
    pattern: str,
    case_sensitive: bool,
    use_regex: bool,
    whole_word: bool,
    max_spans: int = 3000,
) -> Tuple[Optional[List[Tuple[int, int]]], Optional[str]]:
    """
    使用 ``rg --json`` 得到与 ripgrep 完全一致的匹配区间，并转为 Qt 文档坐标（UTF-16）。

    返回 (spans, error)。error 非空表示未调用 rg 或失败；spans 为 ``(start, end)`` 列表，按从左到右顺序。
    """
    exe = find_rg_executable()
    if not exe:
        return None, "未找到 rg.exe"
    q = (pattern or "").strip()
    if not q:
        return [], None
    args: List[str] = [str(exe), "--json", "--color", "never"]
    if not case_sensitive:
        args.append("-i")
    if not use_regex:
        args.append("--fixed-strings")
        if whole_word:
            args.append("-w")
        pat_arg = q
    else:
        pat_arg = _build_rg_regex_pattern(q, whole_word)
    args.extend(["--", pat_arg, "-"])
    try:
        r = subprocess.run(
            args,
            input=full_text.encode("utf-8"),
            capture_output=True,
            timeout=120,
            creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0)
            if sys.platform == "win32"
            else 0,
        )
    except Exception as e:
        return None, str(e)
    err_txt = (r.stderr or b"").decode("utf-8", errors="replace").strip()
    if r.returncode == 2:
        return None, err_txt or "rg 正则解析失败"
    if r.returncode not in (0, 1):
        return None, err_txt or f"rg 退出码 {r.returncode}"
    out = (r.stdout or b"").decode("utf-8", errors="replace")
    spans: List[Tuple[int, int]] = []
    for line in out.splitlines():
        line = line.strip()
        if not line:
            continue
        try:
            obj = json.loads(line)
        except json.JSONDecodeError:
            continue
        if obj.get("type") != "match":
            continue
        data = obj.get("data") or {}
        abs_off = data.get("absolute_offset")
        if abs_off is None:
            continue
        for sm in data.get("submatches") or []:
            try:
                sb = abs_off + int(sm["start"])
                eb = abs_off + int(sm["end"])
                qt_s, qt_e = _utf8_byte_range_to_qt_positions(full_text, sb, eb)
            except (KeyError, TypeError, ValueError):
                continue
            spans.append((qt_s, qt_e))
            if len(spans) >= max_spans:
                return spans, None
    return spans, None


def compile_python_span_pattern(
    pattern: str,
    case_sensitive: bool,
    use_regex: bool,
    whole_word: bool,
) -> Optional[re.Pattern]:
    """与界面选项一致；在 rg 不可用时用于高亮与跳转（Python re）。"""
    if not (pattern or "").strip():
        return None
    q = pattern.strip()
    flags = 0 if case_sensitive else re.IGNORECASE
    try:
        if use_regex:
            src = _build_rg_regex_pattern(q, whole_word)
            return re.compile(src, flags)
        inner = re.escape(q)
        if whole_word:
            inner = rf"\b{inner}\b"
        return re.compile(inner, flags)
    except re.error:
        return None


def rg_filter_lines(
    full_text: str,
    pattern: str,
    case_sensitive: bool,
    use_regex: bool,
    whole_word: bool,
) -> Tuple[Optional[str], Optional[str]]:
    """
    将全文经 stdin 交给 rg，返回仅匹配行（与 rg 默认输出一致）。

    - use_regex=False：``--fixed-strings``，可选 ``-w`` 全词。
    - use_regex=True：Rust 正则语法（与 Python 略有差异）；全词时包 ``\\b(?:... )\\b``。
    返回 (result, error)；error 非空表示未调用 rg 或失败。
    """
    exe = find_rg_executable()
    if not exe:
        return None, "未找到 rg.exe，请将 rg.exe 放在 Server Manager 目录或设置环境变量 RG_PATH"
    q = (pattern or "").strip()
    if not q:
        return full_text, None
    args: List[str] = [str(exe), "--color", "never", "--no-heading"]
    if not case_sensitive:
        args.append("-i")
    if not use_regex:
        args.append("--fixed-strings")
        if whole_word:
            args.append("-w")
        pat_arg = q
    else:
        pat_arg = _build_rg_regex_pattern(q, whole_word)
    args.extend(["--", pat_arg, "-"])
    try:
        r = subprocess.run(
            args,
            input=full_text.encode("utf-8"),
            capture_output=True,
            timeout=120,
            creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0)
            if sys.platform == "win32"
            else 0,
        )
    except Exception as e:
        return None, str(e)
    if r.returncode not in (0, 1):
        err = (r.stderr or b"").decode("utf-8", errors="replace").strip()
        return None, err or f"rg 退出码 {r.returncode}"
    out = (r.stdout or b"").decode("utf-8", errors="replace")
    if r.returncode == 1:
        return "", None
    return out.rstrip("\n"), None


def rg_search_directory(
    search_root: Path,
    pattern: str,
    case_sensitive: bool,
    use_regex: bool,
    whole_word: bool,
    glob_globs: Optional[List[str]] = None,
) -> Tuple[str, Optional[str]]:
    """在目录下递归搜索，返回 rg 标准输出（含文件名与行号）。"""
    exe = find_rg_executable()
    if not exe:
        return "", "未找到 rg.exe"
    q = (pattern or "").strip()
    if not q:
        return "", "请输入搜索内容"
    if not search_root.is_dir():
        return "", f"目录不存在: {search_root}"
    args: List[str] = [str(exe), "-n", "-S", "--color", "never", "--heading"]
    if not case_sensitive:
        args.append("-i")
    if not use_regex:
        args.append("--fixed-strings")
        if whole_word:
            args.append("-w")
        pat_arg = q
    else:
        pat_arg = _build_rg_regex_pattern(q, whole_word)
    if glob_globs:
        for g in glob_globs:
            args.extend(["--glob", g])
    args.extend(["--", pat_arg, str(search_root.resolve())])
    try:
        r = subprocess.run(
            args,
            capture_output=True,
            text=True,
            timeout=180,
            creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0)
            if sys.platform == "win32"
            else 0,
        )
    except Exception as e:
        return "", str(e)
    out = (r.stdout or "").strip()
    err = (r.stderr or "").strip()
    if r.returncode not in (0, 1):
        return "", err or f"rg 退出码 {r.returncode}"
    if r.returncode == 1 and not out:
        return "(无匹配)", None
    return out, None
