#!/usr/bin/env python3
"""gen_tokens — the fleet color language, generated from the ontology (DRY).

armada/DESIGN.md is the design RATIONALE; armada/tide-table/models.py is the
machine-readable ontology (concepts → color + glyph + label, and the hue-anchored
SAMPLE_FAMILIES). This derives the actual paint every L'Armada surface uses:

  • families × variations — each ColorFamily is expanded into SHADE_COUNT shades by
    walking OKLCH lightness DOWN while fanning the hue ±SHADE_FAN° (PLN's scheme),
    so ~50-100 concepts get stable, distinguishable, greyscale-safe colors.
  • two artifacts —
      tokens.css   (:root custom properties) for CSS-var consumers (armada/ui)
      tokens.json  (the structured palette) for runtime consumers (triangle.html
                   fetches it and overrides its :root, so it can never drift)

OKLCH→sRGB is implemented inline (Björn Ottosson's matrices) — no dependencies, so
this runs under the same system python3 as the rest of the pipeline. Validated
against models.DesignTokens on emit.

    python3 tools/gen_tokens.py        # writes tokens.css (ui) + tokens.json (×2)
"""
from __future__ import annotations
import json
import math
import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT / "armada" / "tide-table"))
import models as M  # noqa: E402

AS_OF = "2026-06-06"
CSS_OUT = ROOT / "armada" / "ui" / "src" / "tokens.css"
JSON_OUT = ROOT / "armada" / "ui" / "src" / "tokens.json"
# a served copy so the standalone triangle can fetch it from its own dir
TRIANGLE_OUT = ROOT / "armada" / "tide-table" / "tokens.json"


# ── OKLCH → sRGB hex (Ottosson) ───────────────────────────────────────────────
def _lin_to_srgb(c: float) -> float:
    c = max(0.0, min(1.0, c))
    return 1.055 * (c ** (1 / 2.4)) - 0.055 if c > 0.0031308 else 12.92 * c


def oklch_hex(L: float, C: float, H_deg: float) -> str:
    h = math.radians(H_deg)
    a, b = C * math.cos(h), C * math.sin(h)
    l_ = (L + 0.3963377774 * a + 0.2158037573 * b) ** 3
    m_ = (L - 0.1055613458 * a - 0.0638541728 * b) ** 3
    s_ = (L - 0.0894841775 * a - 1.2914855480 * b) ** 3
    r = 4.0767416621 * l_ - 3.3077115913 * m_ + 0.2309699292 * s_
    g = -1.2684380046 * l_ + 2.6097574011 * m_ - 0.3413193965 * s_
    bb = -0.0041960863 * l_ - 0.7034186147 * m_ + 1.7076147010 * s_
    return "#" + "".join(f"{round(_lin_to_srgb(x) * 255):02x}" for x in (r, g, bb))


def shades(fam) -> tuple[str, list[str]]:
    """Family base + SHADE_COUNT shades: lightness ladder + ±SHADE_FAN° hue fan.
    Chroma eases slightly at the light/dark ends so extremes don't look muddy."""
    n = M.SHADE_COUNT
    lo, hi = M.SHADE_L[1], M.SHADE_L[0]
    out = []
    for i in range(n):
        t = i / (n - 1)                       # 0 (light) … 1 (dark)
        L = hi + (lo - hi) * t
        H = fam.hue + (t - 0.5) * 2 * M.SHADE_FAN
        C = fam.chroma * (1 - 0.35 * (2 * t - 1) ** 2)   # bell: full in the middle
        out.append(oklch_hex(L, C, H))
    base = oklch_hex(0.66, fam.chroma, fam.hue)            # representative swatch
    return base, out


def build() -> dict:
    def terms(ts):
        return [{"key": t.key, "label": t.label, "color": t.color,
                 "glyph": t.glyph, "description": t.description} for t in ts]
    sample = []
    for fam in M.SAMPLE_FAMILIES:
        base, sh = shades(fam)
        sample.append({"key": fam.key, "label": fam.label, "glyph": fam.glyph,
                       "base": base, "shades": sh, "match": fam.match})
    return {
        "schema": "design-tokens v1 (fleet color language: ontology → families × shades)",
        "as_of": AS_OF,
        "neutrals": dict(M.NEUTRALS),
        "brand": dict(M.BRAND),
        "role": terms(M.ROLE_FAMILIES),
        "lifecycle": terms(M.LIFECYCLE_TERMS),
        "agree": terms(M.AGREE_TERMS),
        "sample": sample,
    }


def to_css(tok: dict) -> str:
    out = ["/* AUTO-GENERATED from armada/tide-table/models.py ONTOLOGY by",
           "   tools/gen_tokens.py — DO NOT EDIT. The fleet color language. */",
           ":root{"]
    for k, v in tok["neutrals"].items():
        out.append(f"  --{k}:{v};")
    for k, v in tok["brand"].items():
        out.append(f"  --brand-{k}:{v};")
    for axis in ("role", "lifecycle", "agree"):
        for t in tok[axis]:
            out.append(f"  --{axis}-{t['key'].replace('-', '_')}:{t['color']};")
    for f in tok["sample"]:
        out.append(f"  --sample-{f['key']}:{f['base']};")
        for i, s in enumerate(f["shades"]):
            out.append(f"  --sample-{f['key']}-{i}:{s};")
    out.append("}")
    return "\n".join(out) + "\n"


def main():
    tok = build()
    from models import DesignTokens          # DRY contract: validate before writing
    DesignTokens.model_validate(tok)
    js = json.dumps(tok, ensure_ascii=False, indent=1)
    JSON_OUT.write_text(js)
    TRIANGLE_OUT.write_text(js)
    CSS_OUT.write_text(to_css(tok))
    n_sh = sum(len(f["shades"]) for f in tok["sample"])
    print(f"✓ {CSS_OUT.relative_to(ROOT)}  +  {JSON_OUT.name} (×2: ui + tide-table)")
    print(f"  {len(tok['sample'])} sample families × {M.SHADE_COUNT} shades = {n_sh} sample slots")
    print(f"  + role {len(tok['role'])} · lifecycle {len(tok['lifecycle'])} · agree {len(tok['agree'])}")


OUT = CSS_OUT

if __name__ == "__main__":
    main()
