"""Mascot animation studio — export the Wivexx dot's animations as TRANSPARENT-background
overlay clips (+ optional audio) to drop into social videos.

The studio (web UI) renders the dot frame-by-frame to an alpha canvas and hands this module
a list of RGBA PNG frames (base64) + an optional audio clip. We assemble them into:
  • WebM / VP9+alpha (yuva420p) — for the Wivexx reel maker's overlay track + the web
  • ProRes 4444 .mov (yuva444p10le) — the transparent format CapCut / Premiere / FCP want
Both keep a true alpha channel, so the dot floats over your footage with no background.
"""
from __future__ import annotations
import base64
import os
import subprocess
import tempfile
from typing import Optional

import wivexx_media as mm

# format key -> (file extension, label). NOTE: the bundled ffmpeg's libvpx can't round-trip
# WebM alpha (the encoded alpha plane is dropped — verified), so transparent delivery is
# ProRes 4444 (.mov), which decodes with alpha in BOTH the reel maker's ffmpeg overlay stage
# AND CapCut / Premiere / FCP. A PNG sequence (.zip) is the universal, always-alpha fallback.
OVERLAY_FORMATS = {
    "prores": (".mov", "ProRes 4444 · alpha (reel maker · Premiere · CapCut · FCP)"),
    "pngseq": (".zip", "PNG sequence · alpha (imports anywhere)"),
}


def _run(args: list, exe: str) -> tuple:
    full = [exe, "-hide_banner", "-loglevel", "error", "-y"] + args
    try:
        p = subprocess.run(full, capture_output=True, text=True,
                           creationflags=mm._CREATE_NO_WINDOW, timeout=1800)
        return (p.returncode == 0), (p.stderr or "").strip()[-400:]
    except Exception as e:
        return False, str(e)


def _vargs(fmt: str) -> list:
    # ProRes 4444 carries a real alpha plane; -alpha_bits 16 keeps the matte crisp.
    return ["-c:v", "prores_ks", "-profile:v", "4444", "-pix_fmt", "yuva444p10le",
            "-alpha_bits", "16", "-vendor", "apl0"]


def export_overlay(frames_dir: str, out: str, fmt: str = "prores", fps: int = 30,
                   audio: Optional[str] = None, pattern: str = "f_%05d.png") -> dict:
    """Assemble a folder of RGBA PNG frames (`pattern`) into a transparent clip at `out`."""
    exe = mm.ffmpeg_exe()
    if not exe:
        return {"ok": False, "error": "ffmpeg isn't installed."}
    fmt = (fmt or "prores").lower()
    if fmt == "pngseq":                                # zip the raw RGBA frames (universal)
        import glob as _glob, zipfile
        try:
            with zipfile.ZipFile(out, "w", zipfile.ZIP_DEFLATED) as z:
                for p in sorted(_glob.glob(os.path.join(frames_dir, "*.png"))):
                    z.write(p, os.path.basename(p))
            return {"ok": os.path.isfile(out), "path": out, "error": None, "kb": mm._kb(out)}
        except Exception as e:
            return {"ok": False, "error": str(e)}
    inp = ["-framerate", str(int(fps)), "-i", os.path.join(frames_dir, pattern)]
    has_audio = bool(audio and os.path.isfile(audio))
    aud = (["-i", audio] if has_audio else [])
    vargs = _vargs(fmt)
    aargs = []
    if has_audio:
        # `apad` pads the (often short) SFX with silence so `-shortest` clamps to the VIDEO
        # length — otherwise a 0.4 s pop would truncate the whole clip to 0.4 s.
        aargs = ["-af", "apad"] + (["-c:a", "pcm_s16le"] if fmt == "prores" else ["-c:a", "libopus", "-b:a", "160k"])
    maps = ["-map", "0:v:0"] + (["-map", "1:a:0"] if has_audio else [])
    args = inp + aud + vargs + aargs + maps + (["-shortest"] if has_audio else []) + \
        ["-movflags", "+faststart"] * (1 if fmt == "prores" else 0) + [out]
    ok, err = _run(args, exe)
    return {"ok": ok and os.path.isfile(out), "path": out,
            "error": None if ok else err, "kb": mm._kb(out) if os.path.isfile(out) else 0}


def render_overlay(frames_b64: list, fps: int = 30, formats=None,
                   audio_b64: Optional[str] = None, out_dir: Optional[str] = None,
                   name: str = "wivexx_mascot") -> dict:
    """Full pipeline used by the studio: decode the captured RGBA PNG frames (base64, with or
    without a data: prefix), write them out, and assemble each requested transparent format.
    Returns {ok, outputs:{fmt:path}, frames, error}."""
    formats = formats or ["webm"]
    if not frames_b64:
        return {"ok": False, "error": "no frames captured"}
    out_dir = out_dir or os.path.join(tempfile.gettempdir(), "wvx_mascot_out")
    os.makedirs(out_dir, exist_ok=True)
    tmp = tempfile.mkdtemp(prefix="wvx_mascot_")
    try:
        for i, fr in enumerate(frames_b64):
            data = fr.split(",", 1)[1] if isinstance(fr, str) and fr.startswith("data:") else fr
            with open(os.path.join(tmp, "f_%05d.png" % (i + 1)), "wb") as fh:
                fh.write(base64.b64decode(data))
        audio_path = None
        if audio_b64:
            data = audio_b64.split(",", 1)[1] if audio_b64.startswith("data:") else audio_b64
            audio_path = os.path.join(tmp, "audio.bin")
            with open(audio_path, "wb") as fh:
                fh.write(base64.b64decode(data))
        outputs, last_err = {}, None
        for fmt in formats:
            ext = OVERLAY_FORMATS.get(fmt, OVERLAY_FORMATS["prores"])[0]
            out = os.path.join(out_dir, name + ext)
            r = export_overlay(tmp, out, fmt=fmt, fps=fps, audio=audio_path)
            if r.get("ok"):
                outputs[fmt] = r["path"]
            else:
                last_err = r.get("error")
        return {"ok": bool(outputs), "outputs": outputs, "frames": len(frames_b64),
                "error": None if outputs else (last_err or "export failed")}
    finally:
        import shutil
        shutil.rmtree(tmp, ignore_errors=True)
