#!/usr/bin/env python3
"""Render the token-economical, scope-loadable ``llms.txt`` structural summary.

The load-bearing two-layer affordance the incumbent wins on is that an agent can
read **one module's section** instead of slurping a whole graph. So the format is:

* a short header with whole-index counts (so a reader knows the shape before loading),
* one ``## <path> [lang]`` section per file, in stable path order, each independently
  readable — grep ``## <path>`` and read just that block.

Sections are intentionally terse (param names without type annotations, module names
without per-symbol import members) to keep the whole-index token cost near the ~24k
target rather than a heavier graph tool's ~630k.
"""

from __future__ import annotations

from .model import FileInfo, SymbolInfo

_HEADER = (
    "# Structural ontology index — {repo}\n"
    "# Generated by ontology_gen. DO NOT EDIT — regenerate with:\n"
    "#   python3 -m ontology_gen <repo-root> --out ontology/structural/\n"
    "# files={files} nodes={nodes} edges={edges} langs={langs}\n"
    "# Each '## <path>' section is independently loadable — read only what you need.\n"
)


def _dedup(seq: list[str]) -> list[str]:
    seen: set[str] = set()
    out: list[str] = []
    for item in seq:
        if item and item not in seen:
            seen.add(item)
            out.append(item)
    return out


def _render_symbol(sym: SymbolInfo, depth: int, lines: list[str]) -> None:
    indent = "  " * depth
    if sym.kind in ("class", "interface"):
        # class/interface: show base/extends names in parentheses, no params.
        bases = f"({', '.join(sym.bases)})" if sym.bases else ""
        lines.append(f"{indent}- {sym.kind} {sym.name}{bases} @{sym.line}")
    elif sym.kind == "type":
        # type alias: no params or bases, just the name.
        lines.append(f"{indent}- type {sym.name} @{sym.line}")
    else:
        params = ", ".join(sym.params)
        lines.append(f"{indent}- {sym.kind} {sym.name}({params}) @{sym.line}")
    for child in sym.children:
        _render_symbol(child, depth + 1, lines)


def _render_file(file_info: FileInfo) -> list[str]:
    lines: list[str] = [f"## {file_info.path} [{file_info.lang}]"]
    if file_info.summary:
        lines.append(file_info.summary)
    import_mods = _dedup([imp.module for imp in file_info.imports])
    if import_mods:
        lines.append(f"imports: {', '.join(import_mods)}")
    for sym in file_info.symbols:
        _render_symbol(sym, 0, lines)
    # Cypher / language-specific extras.
    for key in ("labels", "relationships", "clauses"):
        values = file_info.extra.get(key)
        if values:
            label = "rels" if key == "relationships" else key
            lines.append(f"{label}: {', '.join(values)}")
    if file_info.reexports:
        lines.append(f"re-exports: {', '.join(_dedup(file_info.reexports))}")
    return lines


def render_llms(files: list[FileInfo], repo_name: str, node_count: int, edge_count: int) -> str:
    ordered = sorted(files, key=lambda f: f.path)
    lang_counts: dict[str, int] = {}
    for f in ordered:
        lang_counts[f.lang] = lang_counts.get(f.lang, 0) + 1
    langs = ",".join(f"{lang}:{lang_counts[lang]}" for lang in sorted(lang_counts))

    out: list[str] = [
        _HEADER.format(
            repo=repo_name,
            files=len(ordered),
            nodes=node_count,
            edges=edge_count,
            langs=langs or "none",
        )
    ]
    for file_info in ordered:
        out.append("")
        out.extend(_render_file(file_info))
    # Single trailing newline (stable, diff-friendly).
    return "\n".join(out).rstrip("\n") + "\n"
