#!/usr/bin/env python3
from __future__ import annotations

import posixpath
import re
from pathlib import Path, PurePosixPath
from urllib.parse import urlsplit, urlunsplit


HEADER = (
    "# AbstractGateway — llms-full\n\n"
    "This file is a single-document snapshot of the local Markdown files (`*.md`) linked in `llms.txt`, intended for LLM/agent ingestion.\n"
    "It is generated by `scripts/generate-llms-full.py` in the same order the links first appear in `llms.txt` (de-duplicated).\n"
    "Relative links are normalized to repo-root paths.\n\n"
    "---\n\n"
)


_MD_LINK_RE = re.compile(r"\]\(([^)]+)\)")


def _normalize_link_dest(dest: str, *, source_relpath: str) -> str:
    raw = str(dest or "")
    u = urlsplit(raw)
    if u.scheme or u.netloc:
        return raw
    if not u.path:
        # Anchor-only links like (#section)
        return raw
    if u.path.startswith("/"):
        # Absolute web paths (/api/...) are not repo files.
        return raw

    # Resolve path relative to the source file directory.
    base = PurePosixPath(source_relpath).parent
    resolved = posixpath.normpath(str(base / PurePosixPath(u.path)))
    if resolved == ".":
        resolved = str(base)

    return urlunsplit(("", "", resolved, u.query, u.fragment))


def _normalize_links(text: str, *, source_relpath: str) -> str:
    def repl(m: re.Match[str]) -> str:
        dest = m.group(1)
        norm = _normalize_link_dest(dest, source_relpath=source_relpath)
        return f"]({norm})"

    return _MD_LINK_RE.sub(repl, text)


def _strip_trailing_line_spaces(text: str) -> str:
    return "\n".join(line.rstrip(" \t") for line in text.split("\n"))


def _read_utf8(path: Path) -> str:
    return path.read_text(encoding="utf-8").replace("\r\n", "\n").replace("\r", "\n")


def _extract_local_markdown_paths(*, llms_txt: str) -> list[str]:
    """Return repo-relative Markdown file paths linked in llms.txt (deduped, in order)."""

    out: list[str] = []
    seen: set[str] = set()

    for m in _MD_LINK_RE.finditer(llms_txt or ""):
        dest = str(m.group(1) or "").strip()
        if not dest:
            continue

        u = urlsplit(dest)
        if u.scheme or u.netloc:
            continue
        if not u.path:
            continue
        if u.path.startswith("/"):
            # Absolute web paths (/api/...) are not repo files.
            continue

        raw_path = u.path
        if raw_path.startswith("./"):
            raw_path = raw_path[2:]
        rel = posixpath.normpath(raw_path)
        if not rel or rel in {".", ".."}:
            continue
        # Prevent directory traversal: llms-full must only include files under repo root.
        if rel.startswith("../") or "/../" in f"/{rel}/":
            continue
        if not rel.lower().endswith(".md"):
            continue
        if rel in seen:
            continue

        out.append(rel)
        seen.add(rel)

    return out


def main() -> None:
    root = Path(__file__).resolve().parents[1]
    out_path = root / "llms-full.txt"

    chunks: list[str] = [HEADER]

    llms_txt_path = root / "llms.txt"
    llms_txt = _read_utf8(llms_txt_path) if llms_txt_path.exists() else ""
    files_in_order = _extract_local_markdown_paths(llms_txt=llms_txt)

    for i, rel in enumerate(files_in_order):
        p = root / rel
        if not p.exists() or not p.is_file():
            continue
        chunks.append(f"## {rel}\n\n")
        body = _read_utf8(p).rstrip()
        chunks.append(_strip_trailing_line_spaces(_normalize_links(body, source_relpath=rel)))
        chunks.append("\n")
        if i != len(files_in_order) - 1:
            chunks.append("\n---\n\n")

    out_path.write_text("".join(chunks).rstrip() + "\n", encoding="utf-8")


if __name__ == "__main__":
    main()
