"""audio.py — mix da comp-a na Mesa (07-videos/biblioteca/som/mesa.py): voz + Aruarian Dance + vídeos mostrados + efeitos.

  python comp-a/audio.py --parte 1                 # Parte 1 inteira → comp-a/audio/mix-p1.wav (−14 LUFS)
  python comp-a/audio.py --parte 1 --ate 29.6      # só até 29,6 s (prévia de um trecho)
  python comp-a/audio.py --parte 2

1. Exporta os eventos da composição (oraculo_take.py --eventos; < 5 s, sem render): efeitos (`events`) e trechos de
   vídeo mostrado (`videos`: nome, t0, t1, trilha 'para'|'segue', alvo LUFS) que as janelas registram sozinhas.
2. Trilha: Aruarian Dance (Nujabes, instrumental). PLANO = pontos (tempo do Reel, segundo da faixa). Entre os pontos a
   faixa anda junto com o vídeo; nas janelas com trilha 'para' a música sai (fade 0,3 s) e PAUSA, voltando de onde
   parou (fade 0,25 s), a não ser que um ponto do PLANO diga outro lugar (ex.: voltar na entrada da bateria).
3. Vídeos mostrados: audio/videos/<nome>.wav (feito pelo video.py), nivelado no `alvo` (−20 sob a voz, −12,5 sem voz),
   no barramento direto (sem a curva da trilha).
4. Efeitos pelo mapa padrão da Mesa, sutis (v ≈ 0,22). Sem tonais em elemento (Mesa sem tom).
"""
import argparse, json, subprocess, sys
from pathlib import Path
import soundfile as sf, pyloudnorm as pyln

AQUI = Path(__file__).resolve().parent
PROJ = AQUI.parent
VIDEOS = PROJ.parents[1]
sys.path.insert(0, str(VIDEOS / "biblioteca" / "som"))
from mesa import Mesa
sys.stdout.reconfigure(encoding="utf-8")

MUS = PROJ / "audio/musica-v31/aruarian-dance.wav"          # 250 s; bateria de 16,2 a 243,6 s; compasso de 2,4 s
DRE = AQUI / "audio/musica/still-dre-inst.wav"             # Dr. Dre, Still D.R.E. (Instrumental): piano só até 10,36 s; bateria entra em 10,36; 92 BPM
PLANOS = {
    # Parte 1 (17/10 rodada C): o gancho (0 → fim de trailer) é do rap; a Aruarian entra no FLASH, na entrada da bateria (16,2 s)
    1: "flash",
    # Parte 2: começa no groove (a Parte 1 termina perto de 190 s da faixa; aqui volta para o começo da bateria)
    2: [(0.0, 16.2)],
}


def tempo(parte):
    import json as _j
    s = open(AQUI / "dados" / f"tempo-parte{parte}.js", encoding="utf-8").read()
    return _j.loads(s[s.index("{"): s.rindex("}") + 1])


def PF(T, f, txt, fim=False):
    import unicodedata, re
    n = lambda x: re.sub(r"[^a-z0-9]", "", unicodedata.normalize("NFD", x.lower()).encode("ascii", "ignore").decode())
    for w in T["falas"][f]["palavras"]:
        if n(w["w"]) == n(txt):
            return w["e"] if fim else w["s"]
    raise KeyError((f, txt))


def gancho(M, EV, T):
    """Apresentação (falas 01–02): impacto com o rap, opener com o som dele (o rap pausa), riser e fim de trailer.
    Os instantes são os mesmos do p1a.js (T_DROP, T_HIT, T_FLASH)."""
    t_drop, t_hit, t_flash = 0.30, PF(T, "02", "artificial", True) + 0.03, T["falas"]["03"]["t0"] - 0.22
    off = 10.36 - t_drop                                            # a bateria do Still D.R.E. cai no impacto
    paras = sorted((v["t0"], v["t1"]) for v in EV.get("videos", []) if v["t0"] < t_hit and v.get("trilha", "para") == "para")
    a, pausa, trechos = 0.0, 0.0, []
    for p0, p1 in paras:
        trechos.append((a, p0 + 0.05, off + a - pausa)); pausa += (p1 - 0.05) - (p0 + 0.05); a = p1 - 0.05
    trechos.append((a, t_hit, off + a - pausa))
    for i, (a, b, de) in enumerate(trechos):
        ult = i == len(trechos) - 1
        M.musica(str(DRE), entra=a, de=de, ate=de + (b - a) + (0.02 if ult else 0.12), fade_in=0.04 if a == 0 else 0.15, fade_out=0.03 if ult else 0.12)
        print(f"  rap {a:5.2f}–{b:5.2f} s ← faixa {de:5.2f}")
    # impacto de abertura (Shelby ep. 1): golpe Collision do Vol. 3 em Lá + sub, pico 30 ms depois, com a bateria do rap
    M.golpe("collision", t_drop, -17.0, nota="A", off=0.03, fade=2.5)
    # fim de trailer: riser até o golpe; golpe de metais graves + sino/piano com cauda longa; nota grave de piano
    M.riser("sonar-glow", t_hit, -24.0, nota="A", subida=1.8)
    M.golpe("dark-danger", t_hit, -15.5, nota="A", off=0.0, fade=4.5)
    M.golpe("neon-bell", t_hit, -19.0, nota="A", off=0.0, fade=4.0)
    M.nota("piano", "A", 2, t_hit, g=-11.0, vel=80, dur=4.0)
    print(f"  gancho: impacto {t_drop:.2f} s · fim de trailer {t_hit:.2f} s · flash {t_flash:.2f} s")
    return t_drop, t_hit, t_flash


def exporta(parte, ate):
    comp = AQUI / f"parte{parte}.html"
    url = str(comp) + (f"?ate={ate}" if ate else "")
    out = AQUI / "audio" / f"eventos-p{parte}.json"
    subprocess.run([sys.executable, str(VIDEOS / "biblioteca/take-unico/oraculo_take.py"), url, "--eventos", str(out)], check=True)
    return json.load(open(out, encoding="utf-8"))


def trilha(M, plano, videos, dur):
    """Põe a música em trechos: anda 1:1, pausa nas janelas 'para', salta nos pontos do plano."""
    paras = sorted((v["t0"], v["t1"]) for v in videos if v.get("trilha", "para") == "para")
    # junta janelas encostadas
    jun = []
    for a, b in paras:
        if jun and a <= jun[-1][1] + 0.3:
            jun[-1] = (jun[-1][0], max(jun[-1][1], b))
        else:
            jun.append((a, b))
    pontos = sorted(plano)
    # silêncios = janelas 'para', cortadas por um ponto do plano que cai dentro delas
    sil = []
    for a, b in jun:
        dentro = [r for r, _ in pontos if a < r < b]
        sil.append((a, min(dentro) if dentro else b))
    # trechos ativos
    marcas = sorted({0.0, dur, *[r for r, _ in pontos], *[x for s in sil for x in s]})
    ativo = lambda t: not any(a <= t < b for a, b in sil)
    pos, ult_r, ult_p, pausa = None, None, None, 0.0   # antes do 1º ponto do plano: sem trilha
    trechos = []
    for i in range(len(marcas) - 1):
        a, b = marcas[i], marcas[i + 1]
        if b - a < 0.02:
            continue
        p = [pp for r, pp in pontos if abs(r - a) < 1e-6]
        if p:
            ult_r, ult_p, pausa = a, p[0], 0.0
        if ult_r is None:
            continue
        if not ativo(a + 1e-3):
            pausa += b - a
            continue
        de = ult_p + (a - ult_r) - pausa
        if trechos and abs(trechos[-1][1] - a) < 1e-6 and abs(trechos[-1][3] - de) < 1e-3:
            trechos[-1] = (trechos[-1][0], b, trechos[-1][2], de + (b - a))      # emenda contínua
        else:
            trechos.append((a, b, de, de + (b - a)))
    for a, b, de, fim in trechos:
        fi = 0.0 if a == 0 else 0.25
        fo = 0.0 if b >= dur - 1e-3 else 0.35
        lead = 0.0 if a == 0 else 0.0
        M.musica(str(MUS), entra=a, de=de, ate=fim + (0.1 if fo else 0), fade_in=fi, fade_out=fo)
        print(f"  trilha {a:6.2f}–{b:6.2f} s ← faixa {de:6.2f}–{fim:6.2f}")


def main():
    ap = argparse.ArgumentParser(); ap.add_argument("--parte", type=int, default=1); ap.add_argument("--ate", type=float)
    a = ap.parse_args()
    EV = exporta(a.parte, a.ate)
    dur = EV["dur"]
    M = Mesa(dur=dur, pasta=AQUI)                                    # sem tom: nada de tonal em elemento
    M.voz(str(PROJ / f"audio/parte{a.parte}/voz.wav"))
    plano = PLANOS[a.parte]
    impactos = []
    if plano == "flash":                                             # Parte 1: gancho no rap; a Aruarian entra no flash
        t_drop, t_hit, t_flash = gancho(M, EV, tempo(a.parte))
        plano = [(t_flash, 16.2)]
        impactos = [(t_drop - 0.02, t_drop + 0.35)]
    trilha(M, plano, [v for v in EV.get("videos", []) if v["t0"] > plano[0][0]], dur)
    for v in EV.get("videos", []):
        f = (PROJ / v["arquivo"]) if v.get("arquivo") else AQUI / "audio" / "videos" / f"{v['nome']}.wav"   # arquivo: som de marca etc. (comp-a: somMarca)
        if not f.exists():
            print("  sem som:", v["nome"]); continue
        y, sr = sf.read(f); lufs = pyln.Meter(sr).integrated_loudness(y)
        M.poe(str(f), v["t0"], g=v.get("alvo", -20) - lufs, alinha="ini", fade_in=0.06, fade=0.3, fim=max(0.2, v["t1"] - v["t0"]), bus="direto")
        print(f"  vídeo {v['nome']:22s} {v['t0']:6.2f}–{v['t1']:6.2f} s  alvo {v.get('alvo')} LUFS  trilha {v.get('trilha')}")
    # notificações reais (audio/sfx/): ev(t, 'notif-iphone') etc.
    SFX = PROJ / "audio" / "sfx"
    NOTIF = {"notif-iphone": ("notif-iphone-tritone.wav", -14), "notif-whatsapp": ("notif-whatsapp.wav", -14), "notif-instagram": ("notif-instagram.wav", -15),
             "notif-facebook": ("notif-facebook.wav", -15), "notif-messenger": ("notif-messenger.wav", -15), "imessage": ("imessage-envia.wav", -15), "imessage-recebe": ("imessage-recebe.wav", -15)}
    mapa = {k: (lambda arq, g: (lambda M, e: M.poe(str(SFX / arq), e["t"], g + 20 * __import__("math").log10(max(e.get("v", 0.7), 0.05) / 0.7), e.get("pan", 0) * 0.6, alinha="ini")))(arq, g) for k, (arq, g) in NOTIF.items()}
    if a.parte == 1:   # pedidos do autor (17/10)
        T = tempo(1)
        def nivel(f, alvo):
            y, sr = sf.read(f); return alvo - pyln.Meter(sr).integrated_loudness(y)
        # "da elite": o Casimiro (youtube.com/watch?v=Ad9BO_q-Ms4, 0,44–1,30 s, voz isolada pelo MelBand Big Beta 4) com eco, discreto,
        # o "elite" dele (0,20 s dentro do arquivo) junto do "elite" da narração; a cauda do eco soa depois
        f = SFX / "casimiro-elite-eco.wav"
        M.poe(str(f), PF(T, "05", "elite") - 0.20, g=nivel(f, -20.0), alinha="ini", bus="direto")
        # "rápida": som de flash (flash-pulse + whoosh curto), junto do clarão leve do p1a.js
        t_r = PF(T, "03", "rapida") + 0.06
        P = "<BIBLIOTECA>/sfx-biblioteca/premium/avulsos/flash-pulse.wav"
        M.poe(P, t_r, g=nivel(P, -24.0), alinha="pico", fade=0.5, fim=1.2, bus="direto")
        Wf = "<BIBLIOTECA>/public/biblioteca/sfx/nivelados/whoosh-curto-2.wav"
        M.poe(Wf, t_r, g=-16, alinha="pico", bus="direto")
        print(f"  elite (Casimiro) {PF(T, '05', 'elite'):.2f} s · flash 'rápida' {t_r:.2f} s")
    M.eventos(EV, sobrepor=mapa)
    if M.sem_som:
        print("  eventos sem som padrão:", M.sem_som)
    M.mixar(saida=str(AQUI / "audio" / f"mix-p{a.parte}.wav"), lufs=-14.0, sob_voz=-15.0, impactos=impactos, stems=False)


if __name__ == "__main__":
    main()
