#!/usr/bin/env python3
"""
Editor de vídeo do Renato - Duas fases de layout.

Pipeline:
  A) Transcreve vídeo 1 com Whisper para detectar o ponto de corte do hook
  B) FASE 1 - Hook (tela cheia 1080x1920): trecho inicial do vídeo 1 até fim do hook
  C) FASE 2 - Split screen: [hook_end → fim do vídeo 1] + [vídeo 2 inteiro]
     - Topo (1080x960): Renato
     - Base (1080x960): B-roll / landing page / Pexels
  D) Grava landing page px3lab/quiz_funnel com Playwright (~15s)
  E) Baixa B-roll relevante do Pexels por keyword
  F) Queima legenda automática (Whisper -> SRT) em todo o vídeo final
  G) Concatena: tela_cheia + split_screen = vídeo final

Uso: python3 editar_renato_split.py
"""

import json
import os
import re
import shutil
import subprocess
import sys
import time
import unicodedata
from datetime import datetime
from pathlib import Path

# --- Setup path pra importar utils existentes ---
BACKEND_DIR = "/opt/mia/workspace/video_editor/backend"
sys.path.insert(0, BACKEND_DIR)

from whisper_utils import transcrever_audio  # noqa: E402
from pexels_utils import buscar_videos, baixar_video  # noqa: E402
from ffmpeg_utils import (  # noqa: E402
    run,
    get_duracao,
    gerar_legenda_srt,
)


# --- Constantes do pipeline ---
VIDEO_1 = "/opt/mia-bot/docs/1785205045035198.mp4"
VIDEO_2 = "/opt/mia-bot/docs/1785205065248461.mp4"
LANDING_HTML = "/opt/mia/workspace/clientes/px3lab/quiz_funnel/index.html"
OUTPUT_ROOT = "/opt/mia/workspace/video_editor/output"

TARGET_W = 1080
TARGET_H = 1920
HALF_H = 960  # 1920 / 2
TARGET_FPS = 30
AUDIO_BITRATE = "128k"
AUDIO_SR = "48000"

# Margens de trim ao redor da fala (segundos)
MARGEM_ANTES = 0.3
MARGEM_DEPOIS = 0.5

# Palavras-chave para identificar quando exibir a landing page
LANDING_KEYWORDS = [
    "landing", "pagina", "page", "site", "quiz",
    "funil", "formulario", "captura",
]

# Palavras-chave que indicam o FIM do hook (início da apresentação da landing)
HOOK_END_KEYWORDS = [
    "landing", "pagina", "page", "site", "quiz", "funil",
    "mostrar", "veja", "olha", "criei", "fiz", "montei",
    "ficou", "pronto", "resultado", "aqui",
]

# Keyword mapping para B-roll do Pexels
BROLL_QUERIES = {
    "driving car pov": ["dirigindo", "carro", "estrada", "volante", "transito"],
    "smartphone technology ai": ["agente", "ia", "inteligencia", "telegram",
                                  "celular", "whatsapp", "chat", "mensagem"],
    "business entrepreneur office": ["negocio", "empresa", "resultado", "cliente",
                                       "vendas", "faturamento"],
    "modern technology laptop": ["tecnologia", "computador", "codigo", "programacao",
                                  "sistema", "app"],
}
DEFAULT_QUERY = "technology entrepreneur"


def log(msg: str, level: str = "INFO"):
    ts = datetime.now().strftime("%H:%M:%S")
    print(f"[{ts}] [{level}] {msg}", flush=True)


def normalizar_texto(s: str) -> str:
    """Remove acentos e minusculiza pra matching de palavras."""
    s = unicodedata.normalize("NFD", s or "")
    s = "".join(c for c in s if unicodedata.category(c) != "Mn")
    return s.lower()


# =========================================================================
# DETECCAO DO HOOK END
# =========================================================================

def detectar_hook_end(transcricao: dict, dur_video: float) -> float:
    """
    Analisa a transcrição do vídeo 1 para encontrar o ponto onde termina o hook.

    Estratégia:
    1. Procura o primeiro segmento onde aparecem palavras de "mostrar landing/resultado"
    2. O hook_end é o INICIO desse segmento (não o fim - pra não cortar no meio da frase)
    3. Fallback: entre 3s e 8s se não encontrar

    Retorna o timestamp (float) onde termina o hook.
    """
    segs = transcricao.get("segmentos", [])
    if not segs:
        log("Sem segmentos na transcrição - usando hook_end padrão de 4s", "WARN")
        return min(4.0, dur_video * 0.15)

    # Janela de hook: entre 2s e min(10s, 30% do vídeo)
    janela_max = min(10.0, dur_video * 0.3)

    log(f"Detectando hook_end. Total de segmentos: {len(segs)}, janela_max: {janela_max:.2f}s")
    log(f"Transcrição completa: {transcricao.get('texto', '')[:300]}")

    # Tenta detectar por palavras-chave de "transição"
    for i, seg in enumerate(segs):
        t_norm = normalizar_texto(seg["texto"])
        inicio = seg["inicio"]

        # Só considera dentro da janela de hook
        if inicio > janela_max:
            break

        # Verifica se este segmento menciona a landing/resultado
        for kw in HOOK_END_KEYWORDS:
            if kw in t_norm:
                # Usa o inicio deste segmento como ponto de corte
                # (hook termina antes de mencionar a landing)
                hook_end = max(2.0, inicio)
                log(f"Hook end detectado por keyword '{kw}' no segmento {i}: {hook_end:.2f}s")
                log(f"  Texto do segmento: '{seg['texto']}'")
                return hook_end

    # Fallback: usa o fim do primeiro segmento ou 4s
    if segs:
        candidato = segs[0]["fim"]
        hook_end = max(2.0, min(candidato, 8.0))
        log(f"Hook end por fallback (fim do 1o segmento): {hook_end:.2f}s")
    else:
        hook_end = 4.0
        log(f"Hook end por fallback fixo: {hook_end:.2f}s")

    return hook_end


# =========================================================================
# NORMALIZACAO DE CLIPS
# =========================================================================

def normalizar_fullscreen(input_path: str, output_path: str,
                           ss: float = None, to: float = None):
    """
    Normaliza para 1080x1920 (tela cheia 9:16) com áudio AAC 48kHz.
    Usado na FASE 1 (hook em tela cheia).
    """
    cmd = ["ffmpeg", "-y"]
    if ss is not None:
        cmd += ["-ss", f"{ss:.3f}"]
    if to is not None:
        cmd += ["-to", f"{to:.3f}"]
    cmd += ["-i", input_path]
    cmd += [
        "-vf", (
            f"scale={TARGET_W}:{TARGET_H}:force_original_aspect_ratio=decrease,"
            f"pad={TARGET_W}:{TARGET_H}:(ow-iw)/2:(oh-ih)/2:black,"
            f"setsar=1,fps={TARGET_FPS}"
        ),
        "-c:v", "libx264", "-preset", "fast", "-crf", "20",
        "-pix_fmt", "yuv420p", "-g", str(TARGET_FPS * 2),
        "-c:a", "aac", "-b:a", AUDIO_BITRATE, "-ar", AUDIO_SR,
        "-movflags", "+faststart",
        output_path,
    ]
    r = run(cmd)
    if r.returncode != 0 or not Path(output_path).exists():
        raise RuntimeError(f"Falha ao normalizar fullscreen {input_path}: {r.stderr[-400:]}")


def normalizar_para_topo(input_path: str, output_path: str,
                          ss: float = None, to: float = None):
    """
    Normaliza para 1080x960 (metade superior do split) com áudio AAC 48kHz.
    Usado na FASE 2 (split screen).
    """
    cmd = ["ffmpeg", "-y"]
    if ss is not None:
        cmd += ["-ss", f"{ss:.3f}"]
    if to is not None:
        cmd += ["-to", f"{to:.3f}"]
    cmd += ["-i", input_path]
    cmd += [
        "-vf", (
            f"scale={TARGET_W}:{HALF_H}:force_original_aspect_ratio=increase,"
            f"crop={TARGET_W}:{HALF_H},setsar=1,fps={TARGET_FPS}"
        ),
        "-c:v", "libx264", "-preset", "fast", "-crf", "20",
        "-pix_fmt", "yuv420p", "-g", str(TARGET_FPS * 2),
        "-c:a", "aac", "-b:a", AUDIO_BITRATE, "-ar", AUDIO_SR,
        "-movflags", "+faststart",
        output_path,
    ]
    r = run(cmd)
    if r.returncode != 0 or not Path(output_path).exists():
        raise RuntimeError(f"Falha ao normalizar para topo {input_path}: {r.stderr[-400:]}")


# =========================================================================
# ETAPA B: Concatenar clips de split (topo)
# =========================================================================

def concatenar_topo(clip_a: str, clip_b: str, out_path: str, work_dir: Path):
    """Concatena dois clips já normalizados pra 1080x960."""
    lista = work_dir / "concat_topo.txt"
    with open(lista, "w") as f:
        f.write(f"file '{clip_a}'\n")
        f.write(f"file '{clip_b}'\n")

    r = run([
        "ffmpeg", "-y", "-f", "concat", "-safe", "0",
        "-i", str(lista), "-c", "copy", out_path,
    ])
    if r.returncode != 0 or not Path(out_path).exists():
        log("concat copy falhou, refazendo com filter concat...", "WARN")
        r = run([
            "ffmpeg", "-y",
            "-i", clip_a, "-i", clip_b,
            "-filter_complex",
            "[0:v][0:a][1:v][1:a]concat=n=2:v=1:a=1[v][a]",
            "-map", "[v]", "-map", "[a]",
            "-c:v", "libx264", "-preset", "fast", "-crf", "20",
            "-pix_fmt", "yuv420p",
            "-c:a", "aac", "-b:a", AUDIO_BITRATE, "-ar", AUDIO_SR,
            out_path,
        ])
        if r.returncode != 0:
            raise RuntimeError(f"Concat falhou: {r.stderr[-400:]}")


def mesclar_transcricoes(t1: dict, t2: dict, offset_seg2: float) -> dict:
    """Une duas transcrições em uma só, com offset aplicado à segunda."""
    segs = list(t1.get("segmentos", []))
    for s in t2.get("segmentos", []):
        ns = {
            "inicio": s["inicio"] + offset_seg2,
            "fim": s["fim"] + offset_seg2,
            "texto": s["texto"],
            "words": [
                {"word": w["word"], "start": w["start"] + offset_seg2, "end": w["end"] + offset_seg2}
                for w in s.get("words", [])
            ],
        }
        segs.append(ns)
    texto = (t1.get("texto", "") + " " + t2.get("texto", "")).strip()
    return {"texto": texto, "segmentos": segs}


def reshift_transcricao(trans: dict, offset: float) -> dict:
    """Aplica offset de tempo negativo (para remover o início do vídeo do timestamp)."""
    segs = []
    for s in trans.get("segmentos", []):
        ini = s["inicio"] - offset
        fim = s["fim"] - offset
        if fim < 0:
            continue
        ini = max(0.0, ini)
        fim = max(0.0, fim)
        words = []
        for w in s.get("words", []):
            ws = max(0.0, w["start"] - offset)
            we = max(0.0, w["end"] - offset)
            words.append({"word": w["word"], "start": ws, "end": we})
        segs.append({
            "inicio": ini,
            "fim": fim,
            "texto": s["texto"],
            "words": words,
        })
    return {"texto": trans.get("texto", ""), "segmentos": segs}


# =========================================================================
# ETAPA C: Gravar landing page com Playwright
# =========================================================================

def gravar_landing(html_path: str, work_dir: Path) -> str:
    """
    Abre o HTML no Playwright, faz scroll e grava vídeo.
    Retorna path do MP4 normalizado 1080x960.
    """
    from playwright.sync_api import sync_playwright

    log("Gravando landing page com Playwright...")

    rec_dir = work_dir / "playwright_rec"
    rec_dir.mkdir(exist_ok=True)

    file_url = f"file://{html_path}"

    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True)
        context = browser.new_context(
            viewport={"width": 1080, "height": 1920},
            record_video_dir=str(rec_dir),
            record_video_size={"width": 1080, "height": 1920},
        )
        page = context.new_page()
        page.set_default_timeout(30000)
        page.goto(file_url, wait_until="networkidle", timeout=30000)

        page.wait_for_timeout(2000)

        try:
            altura = page.evaluate("document.body.scrollHeight")
        except Exception:
            altura = 2000
        altura = max(1200, min(altura, 8000))

        # Scroll suave ate o fim (~8s)
        passos = 40
        alvo = max(0, altura - 1920)
        for i in range(1, passos + 1):
            y = int(alvo * i / passos)
            page.evaluate(f"window.scrollTo({{top: {y}, behavior: 'auto'}})")
            page.wait_for_timeout(200)

        # Volta ao topo (~2s)
        for i in range(20, -1, -1):
            y = int(alvo * i / 20)
            page.evaluate(f"window.scrollTo({{top: {y}, behavior: 'auto'}})")
            page.wait_for_timeout(100)

        page.wait_for_timeout(3000)

        page.close()
        context.close()
        browser.close()

    webms = list(rec_dir.glob("*.webm"))
    if not webms:
        raise RuntimeError("Playwright nao gerou video webm")
    webm = str(webms[0])
    log(f"  webm gerado: {Path(webm).name} ({get_duracao(webm):.2f}s)")

    landing_mp4 = str(work_dir / "landing.mp4")
    r = run([
        "ffmpeg", "-y", "-i", webm,
        "-vf", (
            f"scale={TARGET_W}:{HALF_H}:force_original_aspect_ratio=increase,"
            f"crop={TARGET_W}:{HALF_H},setsar=1,fps={TARGET_FPS}"
        ),
        "-c:v", "libx264", "-preset", "fast", "-crf", "22",
        "-pix_fmt", "yuv420p", "-an",
        landing_mp4,
    ])
    if r.returncode != 0 or not Path(landing_mp4).exists():
        raise RuntimeError(f"Falha ao normalizar landing: {r.stderr[-400:]}")

    log(f"  landing MP4 pronto ({get_duracao(landing_mp4):.2f}s)")
    return landing_mp4


# =========================================================================
# ETAPA D: B-roll do Pexels
# =========================================================================

def escolher_query(texto: str) -> str:
    """Retorna a query Pexels mais adequada pro texto do segmento."""
    t = normalizar_texto(texto)
    for query, keywords in BROLL_QUERIES.items():
        for kw in keywords:
            if normalizar_texto(kw) in t:
                return query
    return DEFAULT_QUERY


def eh_segmento_landing(texto: str) -> bool:
    """Verifica se o segmento fala da landing/pagina."""
    t = normalizar_texto(texto)
    for kw in LANDING_KEYWORDS:
        if normalizar_texto(kw) in t:
            return True
    return False


def baixar_broll_pexels(queries: list, work_dir: Path) -> dict:
    """Baixa 2-3 clips por query. Retorna dict {query: [paths]}."""
    broll_dir = work_dir / "broll"
    broll_dir.mkdir(exist_ok=True)

    resultado = {}
    for query in queries:
        log(f"Buscando Pexels: '{query}'")
        videos = buscar_videos(query, quantidade=3)
        clips = []
        for i, v in enumerate(videos):
            destino = str(broll_dir / f"{query.replace(' ', '_')}_{i}.mp4")
            if baixar_video(v["url"], destino):
                clips.append(destino)
                log(f"  baixado: {Path(destino).name}")
        if clips:
            resultado[query] = clips
        else:
            log(f"  sem resultado pra '{query}'", "WARN")

    return resultado


def clip_fallback_preto(work_dir: Path, duracao: float = 10.0) -> str:
    """Gera um clip preto de fallback."""
    dest = str(work_dir / "fallback_preto.mp4")
    if Path(dest).exists():
        return dest
    log(f"Gerando fallback preto ({duracao}s)")
    r = run([
        "ffmpeg", "-y",
        "-f", "lavfi", "-i", f"color=c=black:s={TARGET_W}x{HALF_H}:d={duracao}:r={TARGET_FPS}",
        "-c:v", "libx264", "-preset", "fast", "-crf", "22",
        "-pix_fmt", "yuv420p", "-an",
        dest,
    ])
    if r.returncode != 0:
        raise RuntimeError(f"Falha fallback preto: {r.stderr[-300:]}")
    return dest


# =========================================================================
# ETAPA E: Preparar clips inferiores (por segmento)
# =========================================================================

def preparar_clip_inferior(source_path: str, duracao: float, out_path: str,
                             usar_kenburns: bool = True):
    """
    Recorta/escala um clip pra 1080x960@30fps com Ken Burns zoom (se ativo).
    Loops se o source for menor que a duracao requerida.
    """
    dur_src = get_duracao(source_path)
    if dur_src <= 0:
        raise RuntimeError(f"Source invalido: {source_path}")

    frames = max(1, int(round(duracao * TARGET_FPS)))

    if usar_kenburns:
        vf = (
            f"scale={TARGET_W * 2}:{HALF_H * 2}:force_original_aspect_ratio=increase,"
            f"crop={TARGET_W * 2}:{HALF_H * 2},"
            f"zoompan=z='min(zoom+0.0012,1.15)':"
            f"x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':"
            f"d={frames}:s={TARGET_W}x{HALF_H}:fps={TARGET_FPS},"
            f"setsar=1"
        )
    else:
        vf = (
            f"scale={TARGET_W}:{HALF_H}:force_original_aspect_ratio=increase,"
            f"crop={TARGET_W}:{HALF_H},setsar=1,fps={TARGET_FPS}"
        )

    cmd = [
        "ffmpeg", "-y",
        "-stream_loop", "-1",
        "-i", source_path,
        "-t", f"{duracao:.3f}",
        "-vf", vf,
        "-c:v", "libx264", "-preset", "fast", "-crf", "22",
        "-pix_fmt", "yuv420p", "-an",
        out_path,
    ]
    r = run(cmd)
    if r.returncode != 0 or not Path(out_path).exists():
        raise RuntimeError(f"Falha ao preparar clip inferior: {r.stderr[-400:]}")


def montar_faixa_inferior(transcricao: dict, duracao_total: float,
                            broll_map: dict, landing_mp4: str,
                            work_dir: Path, offset_audio: float = 0.0) -> str:
    """
    Constroi a faixa INFERIOR (1080x960) alinhada com os segmentos da fala.
    offset_audio: offset em segundos dos timestamps da transcricao relativos ao clip de topo.
    Retorna path do video silencioso 1080x960 com duracao = duracao_total.
    """
    segs = transcricao.get("segmentos", [])
    if not segs:
        out = str(work_dir / "inferior.mp4")
        preparar_clip_inferior(landing_mp4, duracao_total, out, usar_kenburns=True)
        return out

    blocos = []
    cursor = 0.0
    query_cursor = {q: 0 for q in broll_map}

    for s in segs:
        ini = max(cursor, s["inicio"])
        fim = min(duracao_total, s["fim"])
        if fim - ini < 0.4:
            continue

        bloco_inicio = cursor
        bloco_fim = fim

        if eh_segmento_landing(s["texto"]):
            source = landing_mp4
        else:
            query = escolher_query(s["texto"])
            clips = broll_map.get(query) or broll_map.get(DEFAULT_QUERY) or []
            if clips:
                idx = query_cursor.get(query, 0) % len(clips)
                query_cursor[query] = query_cursor.get(query, 0) + 1
                source = clips[idx]
            else:
                source = landing_mp4

        blocos.append((bloco_inicio, bloco_fim, source))
        cursor = fim

    if cursor < duracao_total - 0.2:
        source_fim = blocos[-1][2] if blocos else landing_mp4
        blocos.append((cursor, duracao_total, source_fim))

    # Merge blocos consecutivos com mesma source
    merged = []
    for b in blocos:
        if merged and merged[-1][2] == b[2]:
            merged[-1] = (merged[-1][0], b[1], b[2])
        else:
            merged.append(list(b))
    merged = [tuple(b) for b in merged]

    log(f"Faixa inferior: {len(merged)} blocos")
    for i, (a, b, src) in enumerate(merged):
        log(f"  bloco {i}: {a:.2f}-{b:.2f}s ({b-a:.2f}s) -> {Path(src).name}")

    partes = []
    inf_dir = work_dir / "inferior_parts"
    inf_dir.mkdir(exist_ok=True)
    for i, (a, b, src) in enumerate(merged):
        dur = b - a
        if dur < 0.05:
            continue
        out = str(inf_dir / f"parte_{i:03d}.mp4")
        usar_kb = (src != landing_mp4)
        try:
            preparar_clip_inferior(src, dur, out, usar_kenburns=usar_kb)
        except Exception as e:
            log(f"  falha bloco {i} ({src}): {e}. Usando fallback preto.", "WARN")
            fb = clip_fallback_preto(work_dir, duracao=dur)
            preparar_clip_inferior(fb, dur, out, usar_kenburns=False)
        partes.append(out)

    lista = inf_dir / "concat.txt"
    with open(lista, "w") as f:
        for p in partes:
            f.write(f"file '{p}'\n")

    out_final = str(work_dir / "inferior.mp4")
    r = run([
        "ffmpeg", "-y", "-f", "concat", "-safe", "0",
        "-i", str(lista), "-c", "copy", out_final,
    ])
    if r.returncode != 0 or not Path(out_final).exists():
        log("concat copy da faixa inferior falhou, refazendo...", "WARN")
        inputs = []
        for p in partes:
            inputs += ["-i", p]
        n = len(partes)
        fc = "".join(f"[{i}:v]" for i in range(n)) + f"concat=n={n}:v=1:a=0[v]"
        r = run([
            "ffmpeg", "-y", *inputs,
            "-filter_complex", fc,
            "-map", "[v]",
            "-c:v", "libx264", "-preset", "fast", "-crf", "22",
            "-pix_fmt", "yuv420p", "-an",
            out_final,
        ])
        if r.returncode != 0:
            raise RuntimeError(f"Falha concat inferior: {r.stderr[-400:]}")

    log(f"Faixa inferior pronta: {get_duracao(out_final):.2f}s")
    return out_final


# =========================================================================
# ETAPA E.2: Vstack topo + inferior
# =========================================================================

def montar_split(topo_mp4: str, inferior_mp4: str, out_path: str):
    """Empilha topo (com audio) e inferior (silencioso) em 1080x1920."""
    log("Montando split screen (vstack)...")
    r = run([
        "ffmpeg", "-y",
        "-i", topo_mp4, "-i", inferior_mp4,
        "-filter_complex",
        f"[0:v]scale={TARGET_W}:{HALF_H},setsar=1[t];"
        f"[1:v]scale={TARGET_W}:{HALF_H},setsar=1[b];"
        f"[t][b]vstack=inputs=2[v]",
        "-map", "[v]", "-map", "0:a",
        "-c:v", "libx264", "-preset", "fast", "-crf", "20",
        "-pix_fmt", "yuv420p", "-r", str(TARGET_FPS),
        "-c:a", "aac", "-b:a", AUDIO_BITRATE, "-ar", AUDIO_SR,
        "-shortest",
        out_path,
    ])
    if r.returncode != 0 or not Path(out_path).exists():
        raise RuntimeError(f"Falha vstack: {r.stderr[-500:]}")


# =========================================================================
# ETAPA F: Legendas
# =========================================================================

def queimar_legenda(video_in: str, transcricao: dict, out_path: str, work_dir: Path):
    """Queima legenda branca com outline preto."""
    segs = transcricao.get("segmentos", [])
    if not segs:
        log("Sem segmentos pra legenda, copiando input.", "WARN")
        shutil.copy(video_in, out_path)
        return

    srt_path = str(work_dir / "legenda.srt")
    gerar_legenda_srt(segs, srt_path)
    srt_escaped = srt_path.replace("\\", "\\\\").replace(":", "\\:")

    style = (
        "FontName=Manrope,FontSize=17,"
        "PrimaryColour=&H00FFFFFF,OutlineColour=&H00000000,BackColour=&H80000000,"
        "BorderStyle=1,Outline=2,Shadow=1,Bold=1,"
        "Alignment=2,MarginV=980"
    )
    vf = f"subtitles={srt_escaped}:original_size={TARGET_W}x{TARGET_H}:force_style='{style}'"

    log("Queimando legenda...")
    r = run([
        "ffmpeg", "-y", "-i", video_in,
        "-vf", vf,
        "-c:v", "libx264", "-preset", "fast", "-crf", "20",
        "-pix_fmt", "yuv420p", "-r", str(TARGET_FPS),
        "-c:a", "copy",
        out_path,
    ])
    if r.returncode != 0 or not Path(out_path).exists():
        log(f"Falha ao queimar legenda: {r.stderr[-400:]}. Copiando sem legenda.", "WARN")
        shutil.copy(video_in, out_path)


# =========================================================================
# ETAPA G: Concatenar tela cheia + split
# =========================================================================

def concatenar_final(clip_fullscreen: str, clip_split: str, out_path: str, work_dir: Path):
    """
    Concatena o clip de tela cheia (FASE 1) com o clip split (FASE 2).
    Ambos devem ser 1080x1920.
    """
    log("Concatenando FASE 1 (tela cheia) + FASE 2 (split)...")
    lista = work_dir / "concat_final.txt"
    with open(lista, "w") as f:
        f.write(f"file '{clip_fullscreen}'\n")
        f.write(f"file '{clip_split}'\n")

    r = run([
        "ffmpeg", "-y", "-f", "concat", "-safe", "0",
        "-i", str(lista), "-c", "copy", out_path,
    ])
    if r.returncode != 0 or not Path(out_path).exists():
        log("concat copy final falhou, refazendo com filter...", "WARN")
        r = run([
            "ffmpeg", "-y",
            "-i", clip_fullscreen, "-i", clip_split,
            "-filter_complex",
            "[0:v][0:a][1:v][1:a]concat=n=2:v=1:a=1[v][a]",
            "-map", "[v]", "-map", "[a]",
            "-c:v", "libx264", "-preset", "fast", "-crf", "20",
            "-pix_fmt", "yuv420p", "-r", str(TARGET_FPS),
            "-c:a", "aac", "-b:a", AUDIO_BITRATE, "-ar", AUDIO_SR,
            out_path,
        ])
        if r.returncode != 0:
            raise RuntimeError(f"Concat final falhou: {r.stderr[-400:]}")


# =========================================================================
# MAIN
# =========================================================================

def main():
    ts = datetime.now().strftime("%Y%m%d_%H%M%S")
    work_dir = Path(OUTPUT_ROOT) / f"renato_split_{ts}"
    work_dir.mkdir(parents=True, exist_ok=True)
    log(f"Diretorio de trabalho: {work_dir}")

    # ================================================================
    # ETAPA A: Transcrever vídeo 1 para detectar o hook_end
    # ================================================================
    log("=== ETAPA A: Transcricao do video 1 (deteccao do hook) ===")
    log(f"Transcrevendo {VIDEO_1}...")
    trans1_raw = transcrever_audio(VIDEO_1)
    dur_v1 = get_duracao(VIDEO_1)
    log(f"Video 1: {dur_v1:.2f}s | Segmentos: {len(trans1_raw.get('segmentos', []))}")

    hook_end = detectar_hook_end(trans1_raw, dur_v1)
    log(f"*** Hook end definido em: {hook_end:.2f}s ***")

    # ================================================================
    # ETAPA B: Preparar FASE 1 - Tela cheia (0 -> hook_end)
    # ================================================================
    log("=== ETAPA B: Preparar FASE 1 - Tela cheia (hook) ===")
    fase1_mp4 = str(work_dir / "fase1_fullscreen.mp4")
    normalizar_fullscreen(VIDEO_1, fase1_mp4, ss=0.0, to=hook_end)
    dur_fase1 = get_duracao(fase1_mp4)
    log(f"FASE 1 (tela cheia): {dur_fase1:.2f}s -> {fase1_mp4}")

    # Preparar transcrição da FASE 1 (para legendas - sem reshift, começa em 0)
    # Filtra apenas segmentos que estão dentro do hook
    segs_fase1 = [
        s for s in trans1_raw.get("segmentos", [])
        if s["inicio"] < hook_end
    ]
    # Clipa o ultimo segmento no hook_end
    segs_fase1_clipados = []
    for s in segs_fase1:
        fim = min(s["fim"], hook_end)
        segs_fase1_clipados.append({**s, "fim": fim})
    trans_fase1 = {"texto": " ".join(s["texto"] for s in segs_fase1_clipados), "segmentos": segs_fase1_clipados}
    log(f"Transcricao FASE 1: {len(segs_fase1_clipados)} segmentos")

    # ================================================================
    # ETAPA C: Preparar topo da FASE 2 (split screen)
    # hook_end -> fim do v1, depois v2 inteiro
    # ================================================================
    log("=== ETAPA C: Preparar topo da FASE 2 (split) ===")

    # Parte do video 1 após o hook (hook_end até o fim)
    dur_v1_resto = dur_v1 - hook_end
    log(f"Resto do video 1 apos hook: {hook_end:.2f}s -> {dur_v1:.2f}s ({dur_v1_resto:.2f}s)")

    topo_v1_resto = str(work_dir / "topo_v1_resto.mp4")
    normalizar_para_topo(VIDEO_1, topo_v1_resto, ss=hook_end, to=dur_v1)
    log(f"Topo v1 resto: {get_duracao(topo_v1_resto):.2f}s")

    # Transcrição do resto do v1 (com timestamps reshifted para 0)
    segs_v1_resto = [
        s for s in trans1_raw.get("segmentos", [])
        if s["fim"] > hook_end
    ]
    trans_v1_resto_raw = {"texto": " ".join(s["texto"] for s in segs_v1_resto), "segmentos": segs_v1_resto}
    trans_v1_resto = reshift_transcricao(trans_v1_resto_raw, hook_end)
    dur_topo_v1_resto = get_duracao(topo_v1_resto)
    log(f"Transcricao resto v1: {len(trans_v1_resto['segmentos'])} segmentos")

    # Video 2 inteiro
    log(f"Transcrevendo {VIDEO_2}...")
    trans2_raw = transcrever_audio(VIDEO_2)
    dur_v2 = get_duracao(VIDEO_2)
    log(f"Video 2: {dur_v2:.2f}s | Segmentos: {len(trans2_raw.get('segmentos', []))}")

    # Trim de silêncio no v2
    segs2 = trans2_raw.get("segmentos", [])
    if segs2:
        inicio_fala_v2 = max(0.0, segs2[0]["inicio"] - MARGEM_ANTES)
        fim_fala_v2 = min(dur_v2, segs2[-1]["fim"] + MARGEM_DEPOIS)
        log(f"V2 fala: {inicio_fala_v2:.2f}s -> {fim_fala_v2:.2f}s")
    else:
        inicio_fala_v2 = 0.0
        fim_fala_v2 = dur_v2

    topo_v2 = str(work_dir / "topo_v2.mp4")
    normalizar_para_topo(VIDEO_2, topo_v2, ss=inicio_fala_v2, to=fim_fala_v2)
    dur_topo_v2 = get_duracao(topo_v2)
    log(f"Topo v2 trimado: {dur_topo_v2:.2f}s")

    trans_v2 = reshift_transcricao(trans2_raw, inicio_fala_v2)

    # Concatenar topo da FASE 2: [v1_resto] + [v2]
    topo_fase2 = str(work_dir / "topo_fase2.mp4")
    concatenar_topo(topo_v1_resto, topo_v2, topo_fase2, work_dir)
    dur_topo_fase2 = get_duracao(topo_fase2)
    log(f"Topo FASE 2 concatenado: {dur_topo_fase2:.2f}s")

    # Transcrição completa da FASE 2 (para SRT e faixa inferior)
    trans_fase2 = mesclar_transcricoes(trans_v1_resto, trans_v2, dur_topo_v1_resto)
    log(f"Transcricao FASE 2: {len(trans_fase2.get('segmentos', []))} segmentos")

    # ================================================================
    # ETAPA D: Gravar landing page
    # ================================================================
    log("=== ETAPA D: Gravar landing page ===")
    try:
        landing_mp4 = gravar_landing(LANDING_HTML, work_dir)
    except Exception as e:
        log(f"Playwright falhou: {e}. Usando fallback preto.", "ERROR")
        landing_mp4 = clip_fallback_preto(work_dir, duracao=15.0)

    # ================================================================
    # ETAPA E: Baixar B-roll
    # ================================================================
    log("=== ETAPA E: Baixar B-roll Pexels ===")
    queries_necessarias = set([DEFAULT_QUERY])
    for s in trans_fase2.get("segmentos", []):
        if not eh_segmento_landing(s["texto"]):
            queries_necessarias.add(escolher_query(s["texto"]))
    log(f"Queries: {list(queries_necessarias)}")
    broll_map = baixar_broll_pexels(list(queries_necessarias), work_dir)

    if not broll_map:
        log("Nenhum B-roll baixado. Todos os slots vao pra landing.", "WARN")

    # ================================================================
    # ETAPA F: Montar faixa inferior + split (FASE 2)
    # ================================================================
    log("=== ETAPA F: Montar faixa inferior + split FASE 2 ===")
    inferior_mp4 = montar_faixa_inferior(
        trans_fase2, dur_topo_fase2, broll_map, landing_mp4, work_dir
    )
    split_mp4 = str(work_dir / "split_sem_legenda.mp4")
    montar_split(topo_fase2, inferior_mp4, split_mp4)
    dur_split = get_duracao(split_mp4)
    log(f"Split FASE 2 montado: {dur_split:.2f}s")

    # ================================================================
    # ETAPA G: Queimar legendas em ambas as fases
    # ================================================================
    log("=== ETAPA G: Queimar legendas ===")

    # Legenda FASE 1 (timestamps 0 -> hook_end, posicao central na tela cheia)
    fase1_legendado = str(work_dir / "fase1_legendado.mp4")
    segs_fase1_para_srt = trans_fase1.get("segmentos", [])
    if segs_fase1_para_srt:
        srt_fase1 = str(work_dir / "legenda_fase1.srt")
        gerar_legenda_srt(segs_fase1_para_srt, srt_fase1)
        srt_fase1_esc = srt_fase1.replace("\\", "\\\\").replace(":", "\\:")
        # Na tela cheia, legenda fica mais para baixo (area segura do IG)
        style_fase1 = (
            "FontName=Manrope,FontSize=20,"
            "PrimaryColour=&H00FFFFFF,OutlineColour=&H00000000,BackColour=&H80000000,"
            "BorderStyle=1,Outline=2,Shadow=1,Bold=1,"
            "Alignment=2,MarginV=300"
        )
        vf_fase1 = f"subtitles={srt_fase1_esc}:original_size={TARGET_W}x{TARGET_H}:force_style='{style_fase1}'"
        log("Queimando legenda FASE 1...")
        r = run([
            "ffmpeg", "-y", "-i", fase1_mp4,
            "-vf", vf_fase1,
            "-c:v", "libx264", "-preset", "fast", "-crf", "20",
            "-pix_fmt", "yuv420p", "-r", str(TARGET_FPS),
            "-c:a", "copy",
            fase1_legendado,
        ])
        if r.returncode != 0 or not Path(fase1_legendado).exists():
            log("Falha legenda FASE 1, usando sem legenda.", "WARN")
            shutil.copy(fase1_mp4, fase1_legendado)
    else:
        shutil.copy(fase1_mp4, fase1_legendado)

    # Legenda FASE 2 (timestamps relativos ao split)
    split_legendado = str(work_dir / "split_legendado.mp4")
    queimar_legenda(split_mp4, trans_fase2, split_legendado, work_dir)

    # ================================================================
    # ETAPA H: Concatenar FASE 1 + FASE 2 = video final
    # ================================================================
    log("=== ETAPA H: Concatenar FASE 1 + FASE 2 ===")
    final_mp4 = str(work_dir / "renato_split_final.mp4")
    concatenar_final(fase1_legendado, split_legendado, final_mp4, work_dir)

    # Copia pro root de output tambem (path fixo)
    final_fixo = str(Path(OUTPUT_ROOT) / "renato_split_final.mp4")
    shutil.copy(final_mp4, final_fixo)

    dur_final = get_duracao(final_mp4)
    tamanho_mb = Path(final_mp4).stat().st_size / 1024 / 1024

    log(f"=== DONE ===")
    log(f"FASE 1 (tela cheia): {dur_fase1:.2f}s")
    log(f"FASE 2 (split):      {dur_split:.2f}s")
    log(f"Total final:         {dur_final:.2f}s")
    log(f"Tamanho:             {tamanho_mb:.2f} MB")
    log(f"Final: {final_mp4}")
    log(f"Copia fixa: {final_fixo}")

    return final_mp4


if __name__ == "__main__":
    try:
        out = main()
        print(f"\nOK: {out}")
        sys.exit(0)
    except Exception as e:
        import traceback
        traceback.print_exc()
        print(f"\nFAIL: {e}")
        sys.exit(1)
