"""Ears adapter — emotion read over the tide-table CLAP engines.

Seam: sample_semantics.embed_audios([path]) → a normalized CLAP audio embed,
then emotion_ontology.score(embed) → {valence, arousal, top, dist, confidence}.
Both live in armada/tide-table/ and import each other flat, so we put that dir on
sys.path lazily. CLAP/torch only load inside embed_audios (first call ≈ cold).
"""
import sys
from functools import lru_cache
from pathlib import Path

import config


def _ensure_path():
    p = str(config.TIDE_TABLE)
    if p not in sys.path:
        sys.path.insert(0, p)


def available() -> tuple[bool, str]:
    """(ok, hint) for the SELECTED engine — cheap check, never loads CLAP."""
    if config.EMOTION_ENGINE == "light":
        try:
            import librosa  # noqa: F401
            return True, "light V/A engine (librosa heuristic, no torch)"
        except Exception as e:  # pragma: no cover
            return False, f"light engine unavailable: {e!r}"
    _ensure_path()
    try:
        import emotion_ontology  # noqa: F401  (imports numpy + sample_semantics, light)
        return True, "CLAP engine importable (model loads on first call)"
    except Exception as e:  # pragma: no cover - environment-dependent
        return False, f"CLAP engine unavailable: {e!r}"


def emotion_read(audio_path: str | Path) -> dict:
    """Dispatch to the configured emotion engine (config.EMOTION_ENGINE).
    Both return the same shape (valence/arousal/top/confidence) so the swap is
    invisible to callers; the cache keys on the engine so reads don't collide."""
    if config.EMOTION_ENGINE == "light":
        from engines import ears_light
        return ears_light.emotion_light(audio_path)
    return emotion_of(audio_path)


@lru_cache(maxsize=1)
def _engines():
    """Import + memoize the engine modules (CLAP still lazy within them)."""
    _ensure_path()
    import emotion_ontology as EMO
    import sample_semantics as SEM
    return EMO, SEM


def emotion_of(audio_path: str | Path) -> dict:
    """One audio file → emotion read. Heavy: triggers CLAP on first call.

    Returns the emotion_ontology.score() dict (valence/arousal in [-1,1], top
    emotions, confidence) — exactly the numbers the #90 hexa bundle uses, so the
    API and the batch bundle agree by construction.
    """
    EMO, SEM = _engines()
    ae, _ = SEM.embed_audios([Path(audio_path)])
    if not len(ae):
        raise ValueError("could not decode audio for embedding")
    return EMO.score(ae[0])
