"""Pattern n-gram registry — UT on the pure transforms + IT-on-real-data with
invariant + coverage guards (parsers-over-copy: the registry is generated, tested
mechanically, validated against the model on emit)."""
import pattern_ngrams as pn


# ── UT: normalization makes surface-different phrases the SAME thing ──────────
def test_normalize_drops_indices_and_collapses_space():
    assert pn.normalize("kick:4 ~ kick:4") == pn.normalize("kick:2  ~  kick:2")
    assert pn.normalize('  BD:4 SN  ') == "bd sn"


def test_pid_is_stable_and_norm_keyed():
    assert pn.pid("bd sn") == pn.pid("bd sn")
    assert pn.pid("bd sn") != pn.pid("sn bd")
    assert pn.pid("x").startswith("p")


def test_is_phrase_rejects_bare_labels_keeps_structure():
    assert not pn.is_phrase("moog")           # a synth name, not a phrase
    assert not pn.is_phrase("")
    assert pn.is_phrase("bd sn")              # ≥2 steps
    assert pn.is_phrase("bd*2")              # operator → structure
    assert pn.is_phrase("<0 1 2 3>")         # note pattern


def test_shingles_are_contiguous_ngrams():
    sh = pn.shingles(["a", "b", "c"])
    assert "a b" in sh and "b c" in sh and "a b c" in sh
    assert "a c" not in sh                    # not contiguous


def test_phrase_tokens_keep_sounds_and_numbers():
    assert pn.phrase_tokens("808bd ~ 0 sn") == ["808bd", "0", "sn"]


# ── IT: structural invariants on the real corpus ─────────────────────────────
def test_registry_validates_against_model(registry):
    from models import PatternRegistry
    r = PatternRegistry.model_validate(registry)
    assert r.n_patterns == len(r.patterns)


def test_scope_is_consistent_with_occurrences(registry):
    for p in registry["patterns"]:
        n_tracks = len({o["track"] for o in p["occurrences"]})
        assert p["n_tracks"] == n_tracks
        if n_tracks >= 2:
            assert p["scope"] == "shared"
        elif any(o["count"] >= 2 for o in p["occurrences"]):
            assert p["scope"] == "repeated"
        else:
            assert p["scope"] == "unique"


def test_every_registered_phrase_has_enough_structure(registry):
    """A 'thing' must carry structure — never a bare single token."""
    for p in registry["patterns"]:
        assert p["n_tokens"] >= pn.MIN_PHRASE_TOKENS or any(
            ch in pn._OP for ch in p["norm"]), p["norm"]


def test_similarity_is_bounded_and_symmetric_floor(registry):
    """Jaccard ∈ (floor,1]; and we never emit a degenerate self-ish 1.0 flood:
    at most a handful of pairs may hit 1.0 (true duplicates), not dozens."""
    ones = 0
    for t in registry["tracks"]:
        for nb in t["neighbors"]:
            assert pn.NEIGHBOR_SIM <= nb["similarity"] <= 1.0
            if nb["similarity"] == 1.0:
                ones += 1
    assert ones <= 6, f"{ones} perfect matches — boilerplate likely dominating clustering"


def test_shared_things_actually_exist(registry):
    """The whole point: the catalog reuses phrases across tracks."""
    assert registry["n_shared"] >= 50      # was 193 — alarm if phrase-reuse craters
    assert registry["n_patterns"] >= 800   # was 1325
