"""从 Markdown / HTML 提取标题层级与行号，供大文档分块阅读。"""

from __future__ import annotations

import json
import re
from pathlib import Path
from typing import Any

# ATX 风格 Markdown 标题
_MD_HEADING = re.compile(r"^(#{1,6})\s+(.+?)\s*$")
# 单行内的 HTML 标题（逐行扫描）
_HTML_HEADING = re.compile(
    r"<h\s*([1-6])[^>]*>(.*?)</h\s*\1\s*>",
    re.IGNORECASE | re.DOTALL,
)


def extract_outline_from_text(
    text: str,
    *,
    source_path: str,
    max_heading_level: int = 6,
    format_hint: str | None = None,
) -> list[dict[str, Any]]:
    lines = text.splitlines()
    fmt = (format_hint or "").strip().lower()
    is_html = fmt == "html" or (not fmt and _looks_like_html(text[:2000]))

    out: list[dict[str, Any]] = []
    if is_html:
        for i, line in enumerate(lines, start=1):
            for m in _HTML_HEADING.finditer(line):
                level = int(m.group(1))
                if level > max_heading_level:
                    continue
                raw = m.group(2)
                title = re.sub(r"<[^>]+>", "", raw).strip()
                if title:
                    byte_off = _byte_offset_at_line_start(text, i)
                    out.append(
                        {
                            "level": level,
                            "line": i,
                            "text": title[:500],
                            "byte_offset": byte_off,
                        }
                    )
        return out

    for i, line in enumerate(lines, start=1):
        m = _MD_HEADING.match(line)
        if not m:
            continue
        level = len(m.group(1))
        if level > max_heading_level:
            continue
        title = m.group(2).strip()
        byte_off = _byte_offset_at_line_start(text, i)
        out.append(
            {
                "level": level,
                "line": i,
                "text": title[:500],
                "byte_offset": byte_off,
            }
        )
    return out


def _looks_like_html(sample: str) -> bool:
    s = sample.lower()
    return "<h1" in s or "<h2" in s or "<html" in s or "<body" in s


def _byte_offset_at_line_start(full_text: str, line_no: int) -> int:
    """1-based 行号 → UTF-8 字节偏移（该行行首）。"""
    if line_no <= 1:
        return 0
    parts = full_text.splitlines(keepends=True)
    if line_no > len(parts):
        return len(full_text.encode("utf-8"))
    acc = 0
    for j in range(line_no - 1):
        acc += len(parts[j].encode("utf-8"))
    return acc


def build_outline_payload(
    path: Path,
    *,
    max_heading_level: int = 6,
) -> dict[str, Any]:
    raw = path.read_text(encoding="utf-8", errors="replace")
    suf = path.suffix.lower()
    hint = "html" if suf in (".html", ".htm") else ("markdown" if suf in (".md", ".markdown") else None)
    headings = extract_outline_from_text(
        raw,
        source_path=str(path),
        max_heading_level=max_heading_level,
        format_hint=hint,
    )
    return {
        "source_path": str(path.resolve()),
        "heading_count": len(headings),
        "headings": headings,
        "format_detected": hint or ("html" if _looks_like_html(raw[:2000]) else "markdown"),
    }


def write_outline_json(payload: dict[str, Any], dest: Path) -> None:
    dest.parent.mkdir(parents=True, exist_ok=True)
    dest.write_text(
        json.dumps(payload, ensure_ascii=False, indent=2),
        encoding="utf-8",
    )
