"""Rhubarb Lip-Sync – Mundformen aus Dialog-Audio."""

from __future__ import annotations

import json
import subprocess
from dataclasses import dataclass
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
RHUBARB_CANDIDATES = [
    ROOT / "tools" / "3d-video" / "rhubarb" / "rhubarb.exe",
    ROOT / "tools" / "3d-video" / "rhubarb" / "Rhubarb-Lip-Sync-1.14.0-Windows" / "rhubarb.exe",
]


@dataclass
class MouthCue:
    start: float
    end: float
    shape: str


def rhubarb_exe() -> Path | None:
    for p in RHUBARB_CANDIDATES:
        if p.is_file():
            return p
    for p in (ROOT / "tools" / "3d-video" / "rhubarb").rglob("rhubarb.exe"):
        return p
    return None


def available() -> bool:
    return rhubarb_exe() is not None


def analyze(audio: Path, *, dialog: bool = True) -> list[MouthCue]:
    exe = rhubarb_exe()
    if not exe or not audio.is_file():
        return []
    out = audio.with_suffix(".rhubarb.json")
    cmd = [str(exe), "-f", "json", "-o", str(out), "--quiet", str(audio)]
    try:
        subprocess.run(cmd, capture_output=True, timeout=120, check=True)
    except (subprocess.CalledProcessError, subprocess.TimeoutExpired, OSError):
        return []
    if not out.is_file():
        return []
    try:
        data = json.loads(out.read_text(encoding="utf-8"))
    except (json.JSONDecodeError, OSError):
        return []
    cues: list[MouthCue] = []
    for row in data.get("mouthCues") or []:
        cues.append(
            MouthCue(
                start=float(row.get("start", 0)),
                end=float(row.get("end", 0)),
                shape=str(row.get("value") or "X").upper(),
            )
        )
    return cues


def mouth_open_at(cues: list[MouthCue], t: float) -> float:
    """0=zu, 1=weit offen – aus Rhubarb-Shape."""
    shape_map = {
        "X": 0.05,
        "A": 0.95,
        "B": 0.45,
        "C": 0.55,
        "D": 0.35,
        "E": 0.75,
        "F": 0.25,
        "G": 0.65,
        "H": 0.40,
    }
    for c in cues:
        if c.start <= t < c.end:
            return shape_map.get(c.shape, 0.3)
    return 0.05


def shape_at(cues: list[MouthCue], t: float) -> str:
    for c in cues:
        if c.start <= t < c.end:
            return c.shape
    return "X"
