#!/usr/bin/env python3
"""gen_ts_types — pydantic models → generated TypeScript types (DRY, #42).

ONE source of truth: armada/tide-table/models.py. The Judge UI's shapes
(armada/ui/src/types.gen.ts) are GENERATED from the same models the Python build
emits, so the two can never drift. Edit the models, re-run this, never hand-edit
the .ts. Dependency-free (no node/npx) and deterministic.

    python3 tools/gen_ts_types.py        # writes armada/ui/src/types.gen.ts
"""
from __future__ import annotations
import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT / "armada" / "tide-table"))
from models import CatalogView, Note, PatternRegistry, PlayerData, Presence  # noqa: E402

ROOTS = [PlayerData, Note, CatalogView, PatternRegistry, Presence]   # top-level UI-facing models
OUT = ROOT / "armada" / "ui" / "src" / "types.gen.ts"
SCALARS = {"string": "string", "number": "number", "integer": "number",
           "boolean": "boolean", "null": "null"}


def ref_name(node):
    return node["$ref"].split("/")[-1]


def ts_type(node):
    if "$ref" in node:
        return ref_name(node)
    if "anyOf" in node:
        return " | ".join(dict.fromkeys(ts_type(s) for s in node["anyOf"]))
    if "enum" in node:                                  # inline enum
        return " | ".join(f"'{v}'" for v in node["enum"])
    t = node.get("type")
    if isinstance(t, list):                             # e.g. ["number","null"]
        return " | ".join(SCALARS.get(x, "unknown") for x in t)
    if t == "array":
        return f"{ts_type(node['items'])}[]"
    if t == "object":
        ap = node.get("additionalProperties")
        if isinstance(ap, dict):
            key = ref_name(node["propertyNames"]) if "propertyNames" in node else "string"
            return f"Record<{key}, {ts_type(ap)}>"
        return "Record<string, unknown>"
    return SCALARS.get(t, "unknown")


def emit_interface(name, schema, lines):
    desc = schema.get("description")
    if desc:
        lines.append("/** " + desc.replace("\n", " ") + " */")
    lines.append(f"export interface {name} {{")
    req = set(schema.get("required", []))
    for field, node in schema.get("properties", {}).items():
        opt = "" if field in req else "?"
        lines.append(f"  {field}{opt}: {ts_type(node)}")
    lines.append("}")
    lines.append("")


def main():
    defs, root_schemas = {}, []
    for model in ROOTS:
        s = model.model_json_schema()
        defs.update(s.pop("$defs", {}))
        root_schemas.append((model.__name__, s))

    lines = ["// AUTO-GENERATED from armada/tide-table/models.py — DO NOT EDIT.",
             "// Regenerate: python3 tools/gen_ts_types.py  (DRY data layer, #42)",
             ""]
    # enums first
    for name, schema in sorted(defs.items()):
        if "enum" in schema:
            union = " | ".join(f"'{v}'" for v in schema["enum"])
            lines.append(f"export type {name} = {union}")
            lines.append("")
    # object $defs, then roots
    for name, schema in sorted(defs.items()):
        if "enum" not in schema:
            emit_interface(name, schema, lines)
    for name, schema in root_schemas:
        emit_interface(name, schema, lines)

    OUT.write_text("\n".join(lines))
    n_if = sum(1 for _, s in defs.items() if "enum" not in s) + len(root_schemas)
    n_en = sum(1 for _, s in defs.items() if "enum" in s)
    print(f"✓ {OUT.relative_to(ROOT)} — {n_if} interfaces, {n_en} enums")


if __name__ == "__main__":
    main()
