"""Loudness engine — BS.1770 integrated LUFS + peak/true-peak. CPU, NO torch.

The mastering numbers PLN + hexa actually gate on (reference_postprod_master:
-14 LUFS streaming, -9 LUFS club). pyloudnorm does the K-weighted BS.1770
integrated loudness; true-peak is a cheap 4× oversample (catches inter-sample
peaks a raw sample-peak misses). Returns gain-to-target so a caller knows exactly
how much to push for each delivery target.
"""
import numpy as np

_EPS = 1e-12
TARGETS = {"streaming": -14.0, "club": -9.0}   # LUFS, reference_postprod_master


def _db(x):
    return float(20.0 * np.log10(max(abs(float(x)), _EPS)))


def _true_peak_dbfs(mono, sr):
    """4× oversample → max |sample| in dBFS (approximates inter-sample true peak)."""
    try:
        from scipy.signal import resample_poly
        up = resample_poly(mono, 4, 1)
        return _db(np.max(np.abs(up)))
    except Exception:
        return _db(np.max(np.abs(mono)))


def loudness(audio_path, max_seconds=180.0) -> dict:
    """Integrated LUFS, sample/true peak (dBFS), crest, + gain-to-target."""
    import soundfile as sf
    data, sr = sf.read(str(audio_path), dtype="float64")
    if data.shape[0] < sr // 2:          # pyloudnorm needs ≥ ~0.4 s for a gated block
        raise ValueError("clip too short for integrated loudness (need ≥ 0.5 s)")
    if max_seconds:
        data = data[: int(sr * max_seconds)]
    mono = data if data.ndim == 1 else data.mean(axis=1)

    rms_db = _db(np.sqrt(np.mean(mono ** 2) + _EPS))
    peak_db = _db(np.max(np.abs(mono)))
    tp_db = _true_peak_dbfs(mono, sr)

    lufs = None
    try:
        import pyloudnorm as pyln
        lufs = float(pyln.Meter(sr).integrated_loudness(data))
    except Exception:
        pass

    gain = ({k: round(t - lufs, 2) for k, t in TARGETS.items()} if lufs is not None
            and np.isfinite(lufs) else None)
    return {
        "lufs_integrated": round(lufs, 2) if lufs is not None and np.isfinite(lufs) else None,
        "peak_dbfs": round(peak_db, 2),
        "true_peak_dbfs": round(tp_db, 2),
        "rms_dbfs": round(rms_db, 2),
        "crest_db": round(peak_db - rms_db, 2),
        "gain_to_target_db": gain,         # add this many dB to hit each LUFS target
        "channels": 1 if data.ndim == 1 else data.shape[1],
        "sr": int(sr),
        "engine": "light",
    }
