"""Ajusta a câmera medida de cada cena a uma SOMA DE MOVIMENTOS com ease do AE (o rig do autor:
um null por movimento, 2 keyframes com velocidade zero nas pontas e influências de saída/entrada).
Cada movimento k: início a, duração D, influência de saída u (1º keyframe), de entrada w (2º keyframe),
e deltas em zoom (log2), mira x, mira y, giro. Canal(t) = valor_inicial + soma_k delta_k * ease_k(t).
Saída: camera/movimentos.json (por cena) + estatística das durações e influências."""
import json, sys, numpy as np
from scipy.optimize import least_squares
from scipy.signal import find_peaks, savgol_filter
sys.stdout.reconfigure(encoding="utf-8")
FPS = 24.0
def ease(tau, u, w):
    """Progresso 0..1 da curva temporal do AE com velocidade 0 nas duas pontas (bezier 0,u | 1-w,1)."""
    tau = np.clip(tau, 0, 1); s = tau.copy()
    for _ in range(30):  # Newton em x(s) = tau
        x = 3 * (1 - s) ** 2 * s * u + 3 * (1 - s) * s ** 2 * (1 - w) + s ** 3
        dx = 3 * (1 - s) ** 2 * u + 6 * (1 - s) * s * (1 - w - u) + 3 * s ** 2 * w
        s = np.clip(s - (x - tau) / np.maximum(dx, 1e-6), 0, 1)
    return 3 * (1 - s) * s ** 2 + s ** 3
def modelo(p, t, K, base):
    out = np.tile(base[:, None], (1, len(t))).astype(float)
    for k in range(K):
        a, D, u, w, dz, dx, dy, dg = p[8 * k: 8 * k + 8]
        e = ease((t - a) / max(D, 1 / FPS), u, w)
        out += np.array([dz, dx, dy, dg])[:, None] * e[None, :]
    return out
d = json.load(open("camera/camera-ancorada.json")); res = {}; todos = []
for cena, v in d.items():
    q = v["quadros"]; t = np.array([x["t"] for x in q])
    Y = np.array([np.log2([x["s"] for x in q]), [x["wx"] for x in q], [x["wy"] for x in q], [x["rot"] for x in q]])
    if cena in ("A-agenda", "B-docs", "C-alttab", "D-spotify", "E-notas"): Y[3] = 0
    esc = np.array([1.0, 1 / 200, 1 / 200, 1 / 10])        # pesos: 1 oitava de zoom ~ 200 px de mira ~ 10° de giro
    vel = np.sqrt((np.gradient(Y, axis=1) * esc[:, None] * FPS) ** 2).sum(0)
    vel = savgol_filter(vel, 5, 2) if len(vel) > 7 else vel
    pk, _ = find_peaks(vel, distance=6, prominence=max(0.15, 0.08 * vel.max()))
    K = max(1, len(pk)); p0, lo, hi = [], [], []
    for i in pk if len(pk) else [int(np.argmax(vel))]:
        a0 = t[i] - 0.2; D0 = 0.8
        j0, j1 = max(0, int(i - 5)), min(len(t) - 1, int(i + 14))
        dl = Y[:, j1] - Y[:, j0]
        p0 += [a0, D0, 0.25, 0.75, *dl]; lo += [t[0] - 1.5, 4 / FPS, 0.01, 0.01, -12, -5000, -5000, -90]; hi += [t[-1] + 0.5, 3.0, 1.0, 1.0, 12, 5000, 5000, 90]
    base = Y[:, 0].copy()
    def resid(p):
        return ((modelo(p, t, K, base) - Y) * esc[:, None]).ravel()
    r = least_squares(resid, np.clip(p0, lo, hi), bounds=(lo, hi), max_nfev=4000)
    erro = np.sqrt(np.mean(resid(r.x) ** 2)); amp = np.sqrt(np.mean(((Y - Y.mean(1, keepdims=True)) * esc[:, None]) ** 2))
    movs = []
    for k in range(K):
        a, D, u, w, dz, dx, dy, dg = r.x[8 * k: 8 * k + 8]
        movs.append({"ini": round(a, 3), "dur": round(D, 3), "infl_saida": round(u * 100, 1), "infl_entrada": round(w * 100, 1),
                     "zoom_log2": round(dz, 3), "mira_x": round(dx, 1), "mira_y": round(dy, 1), "giro": round(dg, 2)})
        if abs(dz) > 0.1 or np.hypot(dx, dy) > 30: todos.append((D, u, w))
    movs.sort(key=lambda m: m["ini"])
    res[cena] = {"base": {"zoom_log2": round(base[0], 3), "mira_x": round(base[1], 1), "mira_y": round(base[2], 1), "giro": round(base[3], 2)},
                 "movimentos": movs, "erro_rel": round(erro / max(amp, 1e-6), 3)}
    print(f"{cena:10s} {K:2d} movimentos | erro relativo {erro / max(amp, 1e-6):.3f}")
json.dump(res, open("camera/movimentos.json", "w", encoding="utf-8"), indent=1, ensure_ascii=False)
A = np.array(todos)
if len(A):
    print(f"\n{len(A)} movimentos relevantes | duração mediana {np.median(A[:,0]):.2f} s ({np.median(A[:,0])*FPS:.0f} quadros), quartis {np.percentile(A[:,0],25):.2f}–{np.percentile(A[:,0],75):.2f} s")
    print(f"influência de saída (1º key) mediana {np.median(A[:,1])*100:.0f}% (quartis {np.percentile(A[:,1],25)*100:.0f}–{np.percentile(A[:,1],75)*100:.0f}%)")
    print(f"influência de entrada (2º key) mediana {np.median(A[:,2])*100:.0f}% (quartis {np.percentile(A[:,2],25)*100:.0f}–{np.percentile(A[:,2],75)*100:.0f}%)")
