#!/usr/bin/env python3
"""Generate, drift-check, and build the reproducible omp-spt documentation site."""

from __future__ import annotations

import argparse
from pathlib import Path, PurePosixPath
import re
import shutil
import subprocess
import sys
import tempfile

MDBOOK_VERSION = "0.5.3"
SUMMARY_LINK = re.compile(r"^\s*-\s+\[[^]]+\]\(([^)]+\.md)\)\s*$")
GENERATED_HEADER = """# omp-spt — full documentation

> Generated from `docs-site/src/SUMMARY.md` by `ci/docs/build-docs.py`.
> Do not edit this file; edit the canonical Markdown pages and regenerate it.
"""


class DocsError(RuntimeError):
    pass


def normalize(text: str) -> str:
    return text.replace("\r\n", "\n").replace("\r", "\n").rstrip() + "\n"


def chapter_paths(root: Path) -> list[Path]:
    source = root / "docs-site" / "src"
    summary = source / "SUMMARY.md"
    try:
        lines = summary.read_text(encoding="utf-8").splitlines()
    except OSError as exc:
        raise DocsError(f"cannot read {summary}: {exc}") from exc

    chapters: list[Path] = []
    seen: set[PurePosixPath] = set()
    for line in lines:
        match = SUMMARY_LINK.match(line)
        if not match:
            continue
        relative = PurePosixPath(match.group(1))
        if relative.is_absolute() or ".." in relative.parts:
            raise DocsError(f"SUMMARY.md chapter escapes docs source: {relative}")
        if relative in seen:
            raise DocsError(f"SUMMARY.md contains duplicate chapter: {relative}")
        seen.add(relative)
        page = source.joinpath(*relative.parts)
        if not page.is_file():
            raise DocsError(f"SUMMARY.md chapter does not exist: {page}")
        chapters.append(page)
    if not chapters:
        raise DocsError("SUMMARY.md contains no Markdown chapters")
    return chapters


def render_llms_full(root: Path) -> str:
    source = root / "docs-site" / "src"
    parts = [GENERATED_HEADER.rstrip()]
    for page in chapter_paths(root):
        relative = page.relative_to(source).as_posix()
        body = normalize(page.read_text(encoding="utf-8")).rstrip()
        parts.append(f"<!-- source: {relative} -->\n\n{body}")
    return "\n\n---\n\n".join(parts) + "\n"


def write_generated_export(root: Path) -> Path:
    destination = root / "docs-site" / "llms-full.txt"
    destination.write_text(render_llms_full(root), encoding="utf-8", newline="\n")
    return destination


# [impl->REQ-DOCS-PUBLISH-GATE]
def check_generated_export(root: Path) -> None:
    destination = root / "docs-site" / "llms-full.txt"
    expected = render_llms_full(root)
    try:
        actual = normalize(destination.read_text(encoding="utf-8"))
    except OSError as exc:
        raise DocsError(
            f"generated export missing: {destination}; run ci/docs/build-docs.py --write"
        ) from exc
    if actual != expected:
        raise DocsError(
            "generated documentation drift: docs-site/llms-full.txt; "
            "run ci/docs/build-docs.py --write and commit the result"
        )


def mdbook_command() -> str:
    executable = shutil.which("mdbook")
    if executable is None:
        raise DocsError(f"mdbook {MDBOOK_VERSION} is required on PATH")
    result = subprocess.run(
        [executable, "--version"], text=True, capture_output=True, check=False
    )
    observed = result.stdout.strip()
    if result.returncode != 0 or observed != f"mdbook v{MDBOOK_VERSION}":
        raise DocsError(
            f"mdbook v{MDBOOK_VERSION} is required for reproducible output; observed {observed!r}"
        )
    return executable


def copy_publish_assets(root: Path, output: Path) -> None:
    docs = root / "docs-site"
    shutil.copyfile(docs / "llms.txt", output / "llms.txt")
    shutil.copyfile(docs / "llms-full.txt", output / "llms-full.txt")

    raw = output / "raw"
    source = docs / "src"
    for page in [source / "SUMMARY.md", *chapter_paths(root)]:
        destination = raw / page.relative_to(source)
        destination.parent.mkdir(parents=True, exist_ok=True)
        shutil.copyfile(page, destination)

    contracts = output / "contracts"
    contracts.mkdir(parents=True, exist_ok=True)
    shutil.copyfile(root / "adapter" / "omp-spt.toml", contracts / "adapter-manifest.toml")
    shutil.copyfile(root / "adapter" / "manifest.schema.json", contracts / "manifest.schema.json")
    shutil.copyfile(
        root / "release" / "evidence-v1.schema.json",
        contracts / "release-evidence-v1.schema.json",
    )


def build_site(root: Path, output: Path) -> None:
    check_generated_export(root)
    mdbook = mdbook_command()
    output = output.resolve()
    repository = root.resolve()
    docs_source = (root / "docs-site").resolve()
    if output in (repository, docs_source):
        raise DocsError(f"unsafe documentation output path: {output}")
    output.parent.mkdir(parents=True, exist_ok=True)
    result = subprocess.run(
        [mdbook, "build", str(root / "docs-site"), "--dest-dir", str(output)],
        text=True,
        check=False,
    )
    if result.returncode != 0:
        raise DocsError(f"mdbook build failed with exit {result.returncode}")
    copy_publish_assets(root, output)


def main(argv: list[str] | None = None) -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument(
        "--root",
        type=Path,
        default=Path(__file__).resolve().parents[2],
        help="repository root (defaults to the script's repository)",
    )
    modes = parser.add_mutually_exclusive_group(required=True)
    modes.add_argument("--write", action="store_true", help="regenerate checked-in exports")
    modes.add_argument("--check", action="store_true", help="check drift and perform a clean build")
    modes.add_argument("--output", type=Path, help="check drift and build publishable Pages output")
    args = parser.parse_args(argv)
    root = args.root.resolve()
    try:
        if args.write:
            destination = write_generated_export(root)
            print(f"wrote {destination.relative_to(root)}")
        elif args.check:
            with tempfile.TemporaryDirectory(prefix="omp-spt-docs-") as work:
                build_site(root, Path(work) / "site")
            print("DOCS-PUBLISH-GATE OK")
        else:
            build_site(root, args.output)
            print(f"DOCS-PUBLISH-GATE OK: {args.output}")
    except (DocsError, OSError) as exc:
        print(f"FAIL: {exc}", file=sys.stderr)
        return 1
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
