#!/usr/bin/env python3
"""Regenerate collapsible fine-grained TOC blocks in docs linear README files."""

from __future__ import annotations

import re
import sys
from pathlib import Path

from lib.taxonomy import doc_readmes_from_taxonomy

ROOT = Path(__file__).resolve().parents[1]
SUMMARY_LINE = "<summary><strong>完整细粒度目录（点击展开/收起）</strong></summary>"
ANCHOR_PATTERN = re.compile(r"<a\s+id=[\"']([^\"']+)[\"']")
HEADING_PATTERN = re.compile(r"^(#{2,6})\s+(.+?)\s*#*\s*$")


def heading_after(lines: list[str], start: int) -> tuple[int, str] | None:
    for line in lines[start + 1 : start + 8]:
        stripped = line.strip()
        if not stripped:
            continue
        match = HEADING_PATTERN.match(stripped)
        if not match:
            return None
        return len(match.group(1)), match.group(2).strip()
    return None


def title_from_anchor(anchor: str, main_anchors: list[str]) -> str:
    for main_anchor in sorted(main_anchors, key=len, reverse=True):
        if anchor == main_anchor:
            return main_anchor
        prefix = f"{main_anchor}-"
        if anchor.startswith(prefix):
            return anchor.removeprefix(prefix).replace("-", " ")
    return anchor.replace("-", " ")


def collect_toc_entries(text: str, main_anchors: list[str]) -> list[tuple[int, str, str]]:
    lines = text.splitlines()
    required_prefixes = tuple(f"{anchor}-" for anchor in main_anchors)
    entries: list[tuple[int, str, str]] = []
    seen: set[str] = set()

    for idx, line in enumerate(lines):
        for anchor in ANCHOR_PATTERN.findall(line):
            if anchor in seen:
                continue
            if anchor not in main_anchors and not anchor.startswith(required_prefixes):
                continue
            seen.add(anchor)

            heading = heading_after(lines, idx)
            if heading:
                level, title = heading
            else:
                level, title = 3, title_from_anchor(anchor, main_anchors)
            entries.append((level, title, anchor))

    return entries


def render_toc(entries: list[tuple[int, str, str]]) -> str:
    rendered = [
        "<details>",
        SUMMARY_LINE,
        "",
        "### 细粒度目录",
        "",
    ]

    for level, title, anchor in entries:
        indent = "  " * max(level - 2, 0)
        rendered.append(f"{indent}- [{title}](#{anchor})")

    rendered.extend(["", "</details>"])
    return "\n".join(rendered)


def active_toc_bounds(text: str) -> tuple[int, int] | None:
    in_fence = False
    fence_marker = ""
    offset = 0
    details_start: int | None = None

    for line in text.splitlines(keepends=True):
        stripped = line.lstrip()
        if stripped.startswith(("```", "~~~")):
            marker = stripped[:3]
            if not in_fence:
                in_fence = True
                fence_marker = marker
            elif marker == fence_marker:
                in_fence = False
                fence_marker = ""

        if not in_fence:
            if "<details>" in line:
                details_start = offset + line.index("<details>")
            if SUMMARY_LINE in line and details_start is not None:
                end = text.find("</details>", offset)
                if end == -1:
                    return None
                return details_start, end + len("</details>")

        offset += len(line)

    return None


def replace_toc_block(text: str, toc: str) -> tuple[str, bool]:
    bounds = active_toc_bounds(text)
    if bounds is None:
        return text, False

    details_start, details_end = bounds
    return f"{text[:details_start]}{toc}{text[details_end:]}", True


def main() -> int:
    changed: list[str] = []
    errors: list[str] = []

    doc_readmes = doc_readmes_from_taxonomy()
    if not doc_readmes:
        print("OK synced docs TOC blocks: 0 changed")
        return 0

    for rel_path, main_anchors in doc_readmes.items():
        path = ROOT / rel_path
        if not path.exists():
            errors.append(f"{rel_path}: missing docs README")
            continue

        text = path.read_text(encoding="utf-8")
        entries = collect_toc_entries(text, main_anchors)
        if not entries:
            errors.append(f"{rel_path}: no TOC entries generated")
            continue

        new_text, replaced = replace_toc_block(text, render_toc(entries))
        if not replaced:
            errors.append(f"{rel_path}: missing standard collapsible TOC block")
            continue

        if new_text != text:
            path.write_text(new_text, encoding="utf-8")
            changed.append(str(rel_path))

    if errors:
        print("SYNC_DOC_TOC_ERRORS")
        for error in errors:
            print(error)
        return 1

    for rel_path in changed:
        print(f"updated {rel_path}")
    print(f"OK synced docs TOC blocks: {len(changed)} changed")
    return 0


if __name__ == "__main__":
    sys.exit(main())
