#!/usr/bin/env python3
"""scripts/docs-index — regenerate the gitignored doc indexes.

Three standalone generated files, one script, run by the SessionStart
hook (each worktree regenerates its own copies; nothing is committed —
the files are gitignored, so they never churn or conflict):

  docs/backlog/INDEX.md    one line per work item: slug — status — summary
  docs/runbooks/INDEX.md   one line per runbook: slug — summary
  docs/codebase-map.md     package map: import path — docstring first line

The summary for a markdown file is its first non-heading, non-front-matter
prose line; `status:` is read from front-matter (backlog only, defaults to
`idea`). The package map walks src/*/**/__init__.py and takes each module
docstring's first line (PEP 257) — a package without a docstring is listed
as such, which is the nudge to write one.

Stdlib-only on purpose: hooks and inline commands run it with a bare
`python3`, no uv/container. Idempotent; exits non-zero only on I/O
errors, never on content.
"""

from __future__ import annotations

import re
import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent

HEADER = (
    "<!-- GENERATED by scripts/docs-index — do not edit; gitignored, "
    "regenerated at session start. -->"
)


def front_matter(text: str) -> tuple[dict[str, str], str]:
    """Parse a simple `key: value` front-matter block; return (fm, body)."""
    if not text.startswith("---\n"):
        return {}, text
    end = text.find("\n---", 4)
    if end < 0:
        return {}, text
    fm: dict[str, str] = {}
    for line in text[4:end].splitlines():
        if ":" in line:
            k, _, v = line.partition(":")
            fm[k.strip()] = v.strip()
    return fm, text[end + 4 :]


def summary_line(body: str) -> str:
    """First non-heading, non-blockquote prose line, tidied."""
    for line in body.splitlines():
        s = line.strip()
        if s and not s.startswith(("#", ">", "---", "<!--", "|", "```", "-")):
            return re.sub(r"\s+", " ", s)
    return ""


# Backlog priority buckets → sort rank (high first). Unset/unknown = normal.
_PRIO_RANK = {"high": 0, "normal": 1, "low": 2}


def md_index(directory: Path, with_status: bool) -> list[str]:
    # (rank, filename, row): sort high-priority first, then filename for a
    # stable order within a bucket. Runbooks (with_status=False) all get
    # rank 0, so they stay in pure filename order as before.
    entries: list[tuple[int, str, str]] = []
    for f in sorted(directory.glob("*.md")):
        if f.name in ("README.md", "INDEX.md", "TEMPLATE.md"):
            continue
        fm, body = front_matter(f.read_text(encoding="utf-8"))
        summary = fm.get("summary") or summary_line(body)
        if with_status:
            status = fm.get("status", "idea")
            prio = (fm.get("prio") or "normal").lower()
            marker = f" ·{prio}·" if prio in ("high", "low") else ""
            row = f"- [`{f.stem}`](./{f.name}) **{status}**{marker} — {summary}"
            entries.append((_PRIO_RANK.get(prio, 1), f.name, row))
        else:
            entries.append((0, f.name, f"- [`{f.stem}`](./{f.name}) — {summary}"))
    entries.sort(key=lambda e: (e[0], e[1]))
    return [row for _, _, row in entries]


def docstring_first_line(init: Path) -> str | None:
    """First line of the module docstring, or None if absent."""
    text = init.read_text(encoding="utf-8", errors="replace")
    m = re.match(r'\s*(?:#[^\n]*\n\s*)*("""|\'\'\')(.*?)\1', text, re.DOTALL)
    if not m:
        return None
    first = m.group(2).strip().splitlines()
    return re.sub(r"\s+", " ", first[0]).strip() if first else None


def package_map() -> list[str]:
    rows = []
    for init in (ROOT / "src").glob("**/__init__.py"):
        rel = init.relative_to(ROOT / "src").parent
        if any(p.startswith((".", "_")) or p == "__pycache__" for p in rel.parts):
            continue
        if "data" in rel.parts[:-1]:  # resource dirs under a data/ package
            continue
        mod = ".".join(rel.parts)
        line = docstring_first_line(init)
        rows.append(f"- `{mod}` — {line or '*(no package docstring yet)*'}")
    return sorted(rows)


def write_generated(target: Path, title: str, rows: list[str]) -> bool:
    new = "\n".join([f"# {title}", "", HEADER, "", *rows, ""])
    if target.exists() and target.read_text(encoding="utf-8") == new:
        return False
    target.write_text(new, encoding="utf-8")
    return True


def main() -> int:
    changed = []
    backlog = ROOT / "docs" / "backlog"
    if backlog.is_dir() and write_generated(
        backlog / "INDEX.md", "Backlog index", md_index(backlog, with_status=True)
    ):
        changed.append("docs/backlog/INDEX.md")
    runbooks = ROOT / "docs" / "runbooks"
    if runbooks.is_dir() and write_generated(
        runbooks / "INDEX.md", "Runbook index", md_index(runbooks, with_status=False)
    ):
        changed.append("docs/runbooks/INDEX.md")
    if write_generated(
        ROOT / "docs" / "codebase-map.md", "Package map", package_map()
    ):
        changed.append("docs/codebase-map.md")
    for c in changed:
        print(f"→ {c}")
    return 0


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