"""
One-time footage prep: cut out menu/settings screens and pre-crop to 9:16.

Menus in this footage are grey, so they show up as low average saturation.
Output: footage/clean/<name>.mp4 (1080x1920, no audio), used by render.py.

    python3 prep_footage.py footage/parkour_cc0.mp4
"""
import re
import subprocess
import sys
import tempfile
from pathlib import Path

from config import FOOTAGE_DIR, VIDEO_HEIGHT, VIDEO_WIDTH

CLEAN_DIR = FOOTAGE_DIR / "clean"
SAT_THRESHOLD = 12.0   # gameplay median ~22, menus ~4
PAD = 1.5              # seconds trimmed around each bad run
MIN_SEGMENT = 4.0      # drop clean pieces shorter than this
CROP_SHIFT = 110       # px right of centre; clears the left-side chat box


def _probe(path: Path, entries: str, stream: str | None = None) -> str:
    cmd = ["ffprobe", "-v", "error"]
    if stream:
        cmd += ["-select_streams", stream]
    cmd += ["-show_entries", entries, "-of", "csv=p=0", str(path)]
    return subprocess.run(cmd, capture_output=True, text=True, check=True).stdout.strip()


def bad_times(path: Path) -> list[float]:
    with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as tmp:
        stats = tmp.name
    subprocess.run(
        ["ffmpeg", "-v", "error", "-i", str(path), "-vf",
         f"fps=2,scale=160:90,signalstats,metadata=print:key=lavfi.signalstats.SATAVG:file={stats}",
         "-f", "null", "-"],
        check=True,
    )
    bad, t = [], 0.0
    for line in open(stats):
        if m := re.search(r"pts_time:([\d.]+)", line):
            t = float(m.group(1))
        if (m := re.search(r"SATAVG=([\d.]+)", line)) and float(m.group(1)) < SAT_THRESHOLD:
            bad.append(t)
    return bad


def clean_segments(duration: float, bad: list[float]) -> list[tuple[float, float]]:
    cuts = sorted((max(0, b - PAD), min(duration, b + 0.5 + PAD)) for b in bad)
    merged: list[list[float]] = []
    for s, e in cuts:
        if merged and s <= merged[-1][1]:
            merged[-1][1] = max(merged[-1][1], e)
        else:
            merged.append([s, e])
    segs, cursor = [], 0.0
    for s, e in merged:
        if s - cursor >= MIN_SEGMENT:
            segs.append((cursor, s))
        cursor = e
    if duration - cursor >= MIN_SEGMENT:
        segs.append((cursor, duration))
    return segs


def prep(src: Path) -> Path:
    CLEAN_DIR.mkdir(exist_ok=True)
    out = CLEAN_DIR / f"{src.stem}.mp4"
    duration = float(_probe(src, "format=duration"))
    w, h = map(int, _probe(src, "stream=width,height", "v:0").split(","))
    crop_w = int(h * 9 / 16)
    crop_x = min(w - crop_w, (w - crop_w) // 2 + CROP_SHIFT)

    segs = clean_segments(duration, bad_times(src))
    kept = sum(e - s for s, e in segs)
    print(f"{src.name}: keeping {len(segs)} segments, {kept:.0f}s of {duration:.0f}s")

    select = "+".join(f"between(t,{s:.2f},{e:.2f})" for s, e in segs)
    vf = (f"select='{select}',setpts=N/FRAME_RATE/TB,"
          f"crop={crop_w}:{h}:{crop_x}:0,scale={VIDEO_WIDTH}:{VIDEO_HEIGHT}:flags=lanczos,fps=30")
    subprocess.run(
        ["ffmpeg", "-v", "error", "-y", "-i", str(src), "-vf", vf, "-an",
         "-c:v", "libx264", "-preset", "medium", "-crf", "18", "-pix_fmt", "yuv420p", str(out)],
        check=True,
    )
    print(f"-> {out} ({float(_probe(out, 'format=duration')):.0f}s)")
    return out


if __name__ == "__main__":
    for arg in sys.argv[1:] or [str(FOOTAGE_DIR / "parkour_cc0.mp4")]:
        prep(Path(arg))
