"""Pure ffmpeg argument builders (no I/O) for the audio production pass.

The audio analogue of tools/pipeline/ffmpeg_ops.py: each function returns a
list[str] ready for ffmpeg, so command construction is unit-testable without
running ffmpeg. tools/pipeline/audio_run.py executes them. The ffmpeg binary
name is injected (default "ffmpeg")."""

from __future__ import annotations

_NOISE_COLORS = frozenset({"white", "pink", "brown", "blue", "violet"})


def audio_loop_args(src, dst, *, duration: float, overlap: float,
                    ff: str = "ffmpeg") -> list[str]:
    """Make `src` loop seamlessly by crossfading its tail over its head — the
    audio mirror of ffmpeg_ops.crossfade_loop_args. Output length = duration -
    overlap. Requires overlap < duration/2 so a non-empty middle remains."""
    if overlap <= 0 or overlap >= duration / 2:
        raise ValueError(f"overlap {overlap} must be in (0, duration/2={duration / 2})")
    d, o = duration, overlap
    fc = (
        f"[0:a]atrim=0:{o},asetpts=N/SR/TB[head];"
        f"[0:a]atrim={o}:{d - o},asetpts=N/SR/TB[mid];"
        f"[0:a]atrim={d - o}:{d},asetpts=N/SR/TB[tail];"
        f"[tail][head]acrossfade=d={o}:c1=tri:c2=tri[xf];"
        f"[xf][mid]concat=n=2:v=0:a=1[out]"
    )
    return [
        ff, "-y", "-i", str(src), "-filter_complex", fc,
        "-map", "[out]", "-c:a", "libmp3lame", "-q:a", "4", str(dst),
    ]


def loudnorm_args(src, dst, *, i: float = -18.0, tp: float = -1.5,
                  lra: float = 11.0, ff: str = "ffmpeg") -> list[str]:
    """EBU R128 loudness normalize to the locked installation target so all five
    soundtracks (and the noise bed) sit at one comfortable level."""
    return [
        ff, "-y", "-i", str(src),
        "-af", f"loudnorm=I={i}:TP={tp}:LRA={lra}",
        "-c:a", "libmp3lame", "-q:a", "4", str(dst),
    ]


def white_noise_args(dst, *, duration: float = 60.0, color: str = "pink",
                     ff: str = "ffmpeg") -> list[str]:
    """Synthesize a calm colored-noise bed (default pink) of `duration` seconds —
    deterministic, zero licensing. Loops cleanly (steady-state noise has no seam)."""
    if color not in _NOISE_COLORS:
        raise ValueError(
            f"unknown noise color {color!r}; expected one of {sorted(_NOISE_COLORS)}"
        )
    return [
        ff, "-y", "-f", "lavfi",
        "-i", f"anoisesrc=color={color}:amplitude=0.5:duration={duration}",
        "-t", str(duration), "-c:a", "libmp3lame", "-q:a", "4", str(dst),
    ]
