"""Limpa o ruído da voz do corte (cut.mp4) e põe a voz limpa no lugar, sem mexer na imagem nem no tempo.

Escrito pelo PrimoCode em cima do EDIT IA PRO (SHK Soluções Digitais, com autorização por escrito, 19/10/2026).
No caminho do HyperFrames a voz vem do cut.mp4, e a limpeza do denoise.py só rodava dentro do compose_final (motor
antigo). Aqui ela roda depois do render_cut: a voz é refeita das fontes, trecho a trecho (limpeza antes do ganho, do
limitador e dos fades, igual ao render_cut), normalizada em -14 LUFS e trocada no cut.mp4. O original fica em
<edit>/cut_sem_limpeza.mp4 e o relatório em <edit>/verify/voz_limpa.json (com o modo do loudnorm).

Uso:
    python helpers/voz_limpa.py <edit> [--modo leve|forte]
"""
from __future__ import annotations

import argparse
import json
import shutil
import subprocess
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent))
import denoise  # noqa: E402
from common import load_json  # noqa: E402


def main() -> int:
    ap = argparse.ArgumentParser(description=__doc__.split("\n")[0])
    ap.add_argument("edit", type=Path)
    ap.add_argument("--modo", default="leve", choices=["leve", "forte"])
    a = ap.parse_args()
    edit = a.edit.resolve()
    cut, tl_path = edit / "cut.mp4", edit / "cut_timeline.json"
    if not cut.exists() or not tl_path.exists():
        print("erro: falta o cut.mp4 ou o cut_timeline.json; rode o render_cut antes", file=sys.stderr)
        return 2
    tl = load_json(tl_path)
    fps = int(round(tl.get("fps") or 30))
    frames = int(tl["frames"])
    total = frames * 48000 // fps
    original = edit / "cut_sem_limpeza.mp4"
    marca = edit / "verify" / "voz_limpa.json"
    assinatura = lambda p: [p.stat().st_size, int(p.stat().st_mtime)]  # noqa: E731
    # rodar de novo parte do corte SEM limpeza (senão limparia duas vezes): o cut.mp4 que esta ferramenta gravou
    # tem a assinatura anotada; um render_cut novo troca a assinatura e vira o novo original
    ja_limpo = False
    try:
        ja_limpo = original.exists() and load_json(marca).get("cut_assinatura") == assinatura(cut)
    except Exception:  # noqa: BLE001
        ja_limpo = False
    if not ja_limpo:
        shutil.copy2(cut, original)
    base = original
    work = edit / "clips" / "voz_limpa"
    work.mkdir(parents=True, exist_ok=True)
    res = denoise.build_voice(edit, base, tl, fps, total, a.modo, work, keep_temp=False)
    rel = {"modo": a.modo, "voz": str(res.path) if res.path else None, "relatorio": res.report}
    if not res.path or res.report.get("applied") in (None, "off"):
        rel["aviso"] = "a limpeza não se aplicou (sem ruído medível ou sem fonte); o corte ficou como estava"
        (edit / "verify").mkdir(exist_ok=True)
        (edit / "verify" / "voz_limpa.json").write_text(json.dumps(rel, ensure_ascii=False, indent=1, default=str))
        print(rel["aviso"])
        return 0
    dur = float(tl["duration"])
    tmp = edit / ".cut_limpo.part.mp4"
    r = subprocess.run(["ffmpeg", "-v", "error", "-y", "-i", str(base), "-i", str(res.path), "-map", "0:v:0", "-map",
                        "1:a:0", "-c:v", "copy", "-c:a", "aac", "-b:a", "192k", "-ar", "48000", "-ac", "2", "-t",
                        f"{dur:.6f}", "-movflags", "+faststart", str(tmp)], capture_output=True, text=True)
    if r.returncode:
        print("erro: o ffmpeg falhou ao trocar a voz:\n" + r.stderr[-800:], file=sys.stderr)
        return 1
    tmp.replace(cut)
    rel["cut_assinatura"] = assinatura(cut)
    (edit / "verify").mkdir(exist_ok=True)
    marca.write_text(json.dumps(rel, ensure_ascii=False, indent=1, default=str))
    modo_ln = res.report.get("loudnorm_modo")
    print(f"voz limpa ({a.modo}) no cut.mp4; loudnorm {modo_ln}; original em {original.name}")
    return 0


if __name__ == "__main__":
    sys.exit(main())
