"""Pure ffmpeg argument builders (no I/O) for the content pipeline.

Each function returns a list[str] ready for ffmpeg, so command construction is
unit-testable without running ffmpeg. tools/pipeline/run.py executes them. The
ffmpeg binary name is injected (default "ffmpeg").
"""

from __future__ import annotations


def _fit(width: int, height: int) -> str:
    """A scale+pad filter chain fitting any input into width×height (letterbox),
    fixing SAR and pixel format so downstream concat/xfade get identical geometry."""
    return (
        f"scale={width}:{height}:force_original_aspect_ratio=decrease,"
        f"pad={width}:{height}:(ow-iw)/2:(oh-ih)/2,"
        "setsar=1,format=yuv420p"
    )


def frame_args(src, dst, *, start: float, duration: float,
               width: int, height: int, ff: str = "ffmpeg") -> list[str]:
    """Stage 2 — trim [start, start+duration], fit to width×height, strip audio.
    Produces a high-quality mezzanine for the loop stage."""
    return [
        ff, "-y", "-ss", str(start), "-t", str(duration),
        "-i", str(src), "-vf", _fit(width, height), "-an",
        "-c:v", "libx264", "-preset", "medium", "-crf", "16",
        "-pix_fmt", "yuv420p", "-movflags", "+faststart", str(dst),
    ]


def crossfade_loop_args(src, dst, *, duration: float, overlap: float,
                        fps: int = 30, ff: str = "ffmpeg") -> list[str]:
    """Stage 3 — make `src` loop seamlessly by crossfading its tail over its head.
    Output length = duration − overlap. Requires overlap < duration/2 so a non-empty
    middle remains.

    fps is applied after each trim so xfade receives a constant-frame-rate stream
    (xfade requires CFR; a raw trim output reports rate 1/0 which xfade rejects)."""
    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:v]trim=0:{o},setpts=PTS-STARTPTS,fps={fps}[head];"
        f"[0:v]trim={o}:{d - o},setpts=PTS-STARTPTS,fps={fps}[mid];"
        f"[0:v]trim={d - o}:{d},setpts=PTS-STARTPTS,fps={fps}[tail];"
        f"[tail][head]xfade=transition=fade:duration={o}:offset=0[xf];"
        f"[xf][mid]concat=n=2:v=1,format=yuv420p[out]"
    )
    return [
        ff, "-y", "-i", str(src), "-filter_complex", fc,
        "-map", "[out]", "-an",
        "-c:v", "libx264", "-preset", "medium", "-crf", "16",
        "-pix_fmt", "yuv420p", "-movflags", "+faststart", str(dst),
    ]


def transcode_args(src, dst, *, width: int, height: int, crf: int,
                   fps: int = 30, ff: str = "ffmpeg") -> list[str]:
    """Stage 4 — encode a final deliverable (master or proxy). Master: source res
    capped ~3840 wide, crf 18. Proxy: 1920×1080, crf 20. Both H.264 High, yuv420p,
    even dims, +faststart, video-only (spec §6)."""
    vf = (
        f"scale={width}:{height}:force_original_aspect_ratio=decrease,"
        f"pad={width}:{height}:(ow-iw)/2:(oh-ih)/2,"
        f"setsar=1,fps={fps},format=yuv420p"
    )
    return [
        ff, "-y", "-i", str(src), "-vf", vf, "-an",
        "-c:v", "libx264", "-profile:v", "high", "-preset", "slow",
        "-crf", str(crf), "-pix_fmt", "yuv420p",
        "-movflags", "+faststart", str(dst),
    ]
