"""Fleet color language — the generated tokens must be valid, distinguishable, and
greyscale-safe (parsers-over-copy: ontology in models → tokens, tested mechanically).
gen_tokens lives in tools/, so add it to the path like the ts-types generator."""
import re
import sys
from pathlib import Path

import models as M

ROOT = Path(__file__).resolve().parent.parent.parent.parent      # …/Sound/Tidal
sys.path.insert(0, str(ROOT / "tools"))
import gen_tokens as G  # noqa: E402

HEX = re.compile(r"^#[0-9a-f]{6}$")


def _rellum(hexs):
    """WCAG relative luminance, for greyscale-distinctness checks."""
    def lin(c):
        c /= 255
        return c / 12.92 if c <= 0.03928 else ((c + 0.055) / 1.055) ** 2.4
    r, g, b = (int(hexs[i:i + 2], 16) for i in (1, 3, 5))
    return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b)


# ── UT: the OKLCH→hex math + shade ladder ────────────────────────────────────
def test_oklch_hex_is_valid_and_clamped():
    for L in (0.0, 0.5, 1.0):
        for H in (0, 90, 200, 359):
            assert HEX.match(G.oklch_hex(L, 0.15, H))


def test_shades_are_valid_and_monotonically_darker():
    for fam in M.SAMPLE_FAMILIES:
        base, sh = G.shades(fam)
        assert HEX.match(base)
        assert len(sh) == M.SHADE_COUNT
        assert all(HEX.match(s) for s in sh)
        lums = [_rellum(s) for s in sh]
        assert lums == sorted(lums, reverse=True), f"{fam.key} ladder not light→dark"
        # light and dark ends are clearly separated (greyscale-safe)
        assert lums[0] - lums[-1] > 0.15, f"{fam.key} ladder too flat in greyscale"


# ── ontology integrity ───────────────────────────────────────────────────────
def test_every_ontology_term_has_glyph_and_hex():
    for axis in (M.ROLE_FAMILIES, M.LIFECYCLE_TERMS, M.AGREE_TERMS):
        for t in axis:
            assert t.glyph, t.key
            assert HEX.match(t.color), (t.key, t.color)


def test_agree_terms_cover_the_enum():
    keys = {t.key for t in M.AGREE_TERMS}
    assert keys == {lv.value for lv in M.AgreeLevel}


def test_unparsed_is_not_painted_like_conflict():
    """Cardinal rule: a parser miss must never look like a data conflict (red)."""
    by = {t.key: t.color for t in M.AGREE_TERMS}
    assert by["unparsed"] != by["conflict"]
    assert by["empty"] != by["conflict"]


def test_sample_families_have_distinct_hues_and_match_cues():
    hues = [f.hue for f in M.SAMPLE_FAMILIES]
    assert len(set(hues)) == len(hues)          # no two families share a hue
    for f in M.SAMPLE_FAMILIES:
        assert f.match, f"{f.key} has no lexical cues"
        assert f.glyph


# ── DRY contract: emitted tokens validate against the model ──────────────────
def test_tokens_validate_against_model():
    from models import DesignTokens
    tok = G.build()
    dt = DesignTokens.model_validate(tok)
    assert len(dt.sample) == len(M.SAMPLE_FAMILIES)
    # every sample slot is a valid hex
    for f in tok["sample"]:
        assert HEX.match(f["base"]) and all(HEX.match(s) for s in f["shades"])


def test_sample_classifier_examples():
    """The lexical sample-family rules should land obvious cases (coherent model)."""
    fam = {f.key: f for f in M.SAMPLE_FAMILIES}
    def classify(name):
        s = name.lower()
        for f in M.SAMPLE_FAMILIES:
            if any(s == m or s.startswith(m) for m in f.match):
                return f.key
        return None
    assert classify("kick") == "kick"
    assert classify("808bd") == "kick"
    assert classify("hats") == "hat"
    assert classify("meth_bass") == "bass"
    assert classify("jazz") == "break"
    assert classify("superpiano") is None or classify("piano") == "keys"
