"""classify_family — role families from the sound name + measured spectrum.

The rail in the Judge / set-judge UIs groups orbits by these families, so a
misfile is directly visible to PLN: OPAL's `vec1_claps` and `drumtraks` were
showing up under *Melodic*, which makes the rail actively misleading.

These tests pin BOTH directions. The widening (suffix + token matching) is only
safe if the collisions the exact-match set was built to avoid still cannot
match — `cpluck` is not a clap, `dropbass` is not a drum, `snippet` is not a
snare. A test that only asserts the new hits would happily green-light a rule
that swallows half the melodic lane.
"""
import sys
from pathlib import Path

import pytest

sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from audio_lens import classify_family  # noqa: E402


# Real sound names from the OPAL-26 scores that were misfiled before.
@pytest.mark.parametrize("name", [
    "vec1_claps", "vec1_snare", "realclaps", "clubkick", "bskick",
    "jbk_kick", "808bd", "drumtraks", "drums_nes", "hardkick_rha",
])
def test_percussion_names_land_in_percs(name):
    fam, _ = classify_family(name)
    assert fam == "percs", f"{name} should be percs, got {fam}"


# The whole reason the classifier used exact-matching. These must NOT be percs.
@pytest.mark.parametrize("name,expected", [
    ("cpluck", "melodic"),      # 'cp' is a clap; cpluck is a pluck
    ("dropbass", "bass"),       # 'dr' is a drum; this is a bass
    ("snippet", "melodic"),     # 'sn' is a snare
    ("subbass", "bass"),
    ("meth_bass", "bass"),      # a noisy wobble, but its REGISTER is bass
])
def test_lookalikes_are_not_percussion(name, expected):
    fam, _ = classify_family(name)
    assert fam == expected, f"{name} should be {expected}, got {fam}"


@pytest.mark.parametrize("name", ["jungle_breaks", "org_jungle_breaks", "breaks165", "amen"])
def test_breaks_go_to_tops(name):
    fam, _ = classify_family(name)
    assert fam == "tops"


def test_measured_centroid_decides_register_not_the_name():
    """A name with no bass hint but a sub-register spectrum is still bass.
    Register comes from the measurement; identity only decides percs/tops."""
    lo = {"centroid": 90.0, "bands": {}}
    hi = {"centroid": 2200.0, "bands": {}}
    assert classify_family("ghost", lo)[0] == "bass"
    assert classify_family("ghost", hi)[0] == "melodic"


def test_percussion_beats_the_spectrum():
    """A kick sits under 150 Hz like a sub does. Identity has to win, or every
    kick in the set collapses into the Bass lane and the rail stops meaning
    anything."""
    sub_like = {"centroid": 70.0, "bands": {}}
    assert classify_family("clubkick", sub_like)[0] == "percs"


def test_empty_name_is_survivable():
    fam, cen = classify_family("", None)
    assert fam in {"melodic", "bass"} and cen is None
