/* kit/base.jsx — a gramática de movimento que as peças do kit dividem.
 *
 * Copiado para o projeto Remotion (src/kit/) por remotion.js. Tudo aqui é
 * função do QUADRO: nada de Math.random, Date.now nem transição CSS — o mesmo
 * plano dá o mesmo vídeo em qualquer máquina.
 *
 * As regras vêm do guia MOTION VOX STYLE: ataque rápido, desaceleração
 * forte, repouso seco, sem bounce. Elemento tem energia (mola SNAPPY);
 * câmera não (bezier longa). */
import React from 'react';
import { spring, Img, staticFile } from 'remotion';

// ── o tempo ──────────────────────────────────────────────────────────────
export const SNAPPY = { damping: 26, stiffness: 240, mass: 0.65 };

/* A mola do elemento: 0 antes de "de", ~1 em uns 10 quadros, sem passar de 1
   (o amortecimento 26 com massa 0,65 fica acima do crítico: não quica). */
export function mola(t, de, fps, config = SNAPPY) {
  const f = Math.round((t - de) * fps);
  if (f < 0) return 0;
  return spring({ frame: f, fps, config });
}

/* Bezier cúbica como no CSS: resolve x → t por Newton e devolve y. */
export function bezier(x1, y1, x2, y2) {
  const cx = 3 * x1, bx = 3 * (x2 - x1) - cx, ax = 1 - cx - bx;
  const cy = 3 * y1, by = 3 * (y2 - y1) - cy, ay = 1 - cy - by;
  const sx = (u) => ((ax * u + bx) * u + cx) * u;
  const sy = (u) => ((ay * u + by) * u + cy) * u;
  const dx = (u) => (3 * ax * u + 2 * bx) * u + cx;
  return (x) => {
    if (x <= 0) return 0;
    if (x >= 1) return 1;
    let u = x;
    for (let i = 0; i < 8; i++) {
      const e = sx(u) - x;
      const d = dx(u);
      if (Math.abs(e) < 1e-5 || Math.abs(d) < 1e-6) break;
      u -= e / d;
    }
    return sy(Math.min(1, Math.max(0, u)));
  };
}

/* As curvas novas, que o Cena.jsx também aceita nos quadros-chave:
     editorial  ataque rápido, desaceleração forte (a entrada de elemento)
     camera     longa e sem tranco nas pontas (câmera nunca tem energia)
     arrasto    a mão que desenha: rápida no começo, arrastando no fim
     acelera    a câmera que ganha velocidade antes do corte */
export const CURVAS_KIT = {
  editorial: bezier(0.16, 1, 0.3, 1),
  camera: bezier(0.55, 0.05, 0.2, 1),
  arrasto: bezier(0.12, 0.8, 0.25, 1),
  acelera: bezier(0.6, 0, 0.9, 0.4),
};

export const entre = (t, a, b) => (b <= a ? (t >= a ? 1 : 0) : Math.min(1, Math.max(0, (t - a) / (b - a))));
export const lerp = (a, b, k) => a + (b - a) * k;

/* Pseudoaleatório pela SEMENTE: o mesmo número dá sempre o mesmo sorteio. */
export function acaso(n) {
  const x = Math.sin(n * 12.9898 + 78.233) * 43758.5453;
  return x - Math.floor(x);
}

// ── o carimbo ────────────────────────────────────────────────────────────
/* A palavra carimba: chega com leve rotação e escala maior, assenta em
   poucos quadros e a tinta absorve em duas fases. Nunca desliza, nunca faz
   fade — antes do quadro dela, não existe; no quadro dela, já está lá. */
export function carimbo(t, de, fps, semente = 1, forca = 1) {
  if (t < de) return { opacity: 0 };
  const m = mola(t, de, fps);
  const f = (t - de) * fps;
  const rot0 = (acaso(semente) - 0.5) * 7 * forca;
  const tinta = f < 2 ? 1.1 : Math.max(0, 0.5 * (1 - (f - 2) / 7));
  return {
    opacity: 1,
    transform: 'rotate(' + (rot0 * (1 - m)).toFixed(3) + 'deg) scale(' + (1 + 0.2 * forca * (1 - m)).toFixed(4) + ')',
    filter: tinta > 0.02 ? 'blur(' + tinta.toFixed(2) + 'px)' : undefined,
  };
}

// ── o traço que se desenha ──────────────────────────────────────────────
/* Um caminho SVG que se desenha de 0 a 1. pathLength=1 deixa o traço
   independente do tamanho: o dasharray é sempre 1. */
export function Traco({ d, p, cor, espessura = 4, opacidade = 1, tampa = 'round', filtro }) {
  if (p <= 0) return null;
  return (
    <path d={d} pathLength={1} fill="none" stroke={cor} strokeWidth={espessura} strokeLinecap={tampa}
      strokeLinejoin="round" strokeDasharray="1 1" strokeDashoffset={1 - Math.min(1, p)} opacity={opacidade}
      filter={filtro} />
  );
}

/* A elipse de caneta: não fecha certinho, passa um pouco do começo e treme
   de leve — é a mão do professor apontando, não um <ellipse>. */
export function elipseMao(cx, cy, rx, ry, semente = 3) {
  const pts = [];
  const n = 26;
  const a0 = -2.2 + acaso(semente) * 0.4;
  for (let i = 0; i <= n; i++) {
    const a = a0 + (i / n) * Math.PI * 2 * 1.12;
    const tremor = 1 + (acaso(semente * 7 + i) - 0.5) * 0.05;
    const espiral = 1 + (i / n) * 0.06;
    pts.push([cx + Math.cos(a) * rx * tremor * espiral, cy + Math.sin(a) * ry * tremor * espiral]);
  }
  return suave(pts);
}

/* O sublinhado de caneta: uma linha que sobe um pouquinho no fim. */
export function sublinhadoMao(x, y, w, semente = 5) {
  const pts = [];
  for (let i = 0; i <= 8; i++) {
    const k = i / 8;
    pts.push([x + k * w, y + Math.sin(k * 3.1 + semente) * w * 0.006 - k * k * w * 0.012]);
  }
  return suave(pts);
}

/* Catmull-Rom → cúbicas: pontos soltos viram uma curva sem quina. */
export function suave(pts, fechado = false) {
  if (pts.length < 2) return '';
  const P = (i) => pts[fechado ? (i + pts.length) % pts.length : Math.max(0, Math.min(pts.length - 1, i))];
  let d = 'M ' + pts[0][0].toFixed(1) + ' ' + pts[0][1].toFixed(1);
  const fim = fechado ? pts.length : pts.length - 1;
  for (let i = 0; i < fim; i++) {
    const p0 = P(i - 1), p1 = P(i), p2 = P(i + 1), p3 = P(i + 2);
    const c1 = [p1[0] + (p2[0] - p0[0]) / 6, p1[1] + (p2[1] - p0[1]) / 6];
    const c2 = [p2[0] - (p3[0] - p1[0]) / 6, p2[1] - (p3[1] - p1[1]) / 6];
    d += ' C ' + c1[0].toFixed(1) + ' ' + c1[1].toFixed(1) + ' ' + c2[0].toFixed(1) + ' ' + c2[1].toFixed(1) + ' ' + p2[0].toFixed(1) + ' ' + p2[1].toFixed(1);
  }
  return fechado ? d + ' Z' : d;
}

/* A mancha líquida: um círculo de raio R com lóbulos que respiram. É o
   que a transição "liquido" recorta, e o que a peça "bolha" desenha. */
export function bolhaCaminho(cx, cy, R, fase = 0, semente = 2, lobos = 9) {
  const pts = [];
  for (let i = 0; i < lobos; i++) {
    const a = (i / lobos) * Math.PI * 2;
    const r = R * (1 + 0.16 * Math.sin(a * 3 + fase * 2.1 + semente) + 0.08 * Math.sin(a * 5 - fase * 3.3));
    pts.push([cx + Math.cos(a) * r, cy + Math.sin(a) * r]);
  }
  return suave(pts, true);
}

// ── fonte e cor ──────────────────────────────────────────────────────────
export const familia = (f, reserva = 'system-ui, sans-serif') => (f ? '"' + f + '", ' + reserva : reserva);
export const MONO = (f) => familia(f || 'JetBrains Mono', 'ui-monospace, monospace');

export function rgb(hex) {
  const h = String(hex || '').replace('#', '');
  const n = h.length === 3 ? h.split('').map((c) => c + c).join('') : h.padEnd(6, '0').slice(0, 6);
  return [0, 2, 4].map((i) => parseInt(n.slice(i, i + 2), 16) || 0);
}
export const luz = (c) => { const [r, g, b] = rgb(c); return (0.299 * r + 0.587 * g + 0.114 * b) / 255; };
export const sobre = (c) => (luz(c) > 0.6 ? '#111114' : '#ffffff');
export function mistura(a, b, k) {
  const A = rgb(a), B = rgb(b);
  return '#' + A.map((v, i) => Math.round(v + (B[i] - v) * k).toString(16).padStart(2, '0')).join('');
}
export const alfa = (c, a) => { const [r, g, b] = rgb(c); return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')'; };

// A sombra de contato: o objeto encosta no papel (curta e escura) e flutua um pouco (longa e clara).
export const SOMBRA_CONTATO = '0 1px 1px rgba(20,16,10,0.22), 0 3px 6px rgba(20,16,10,0.14), 0 18px 34px -12px rgba(20,16,10,0.32)';

// ── a marca (logo, glifo ou monograma) ──────────────────────────────────
/* O monograma de quem não tem logo: um quadrado de canto redondo vazado e
   um brilho de quatro pontas dentro. Só caminho, sem texto: como imagem de
   máscara (data URI) o SVG não carrega fonte, e a letra sairia em Times. */
const MONOGRAMA = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><path fill-rule="evenodd" fill="#fff" d="'
  + 'M30 0H70A30 30 0 0 1 100 30V70A30 30 0 0 1 70 100H30A30 30 0 0 1 0 70V30A30 30 0 0 1 30 0Z'
  + 'M32 11H68A21 21 0 0 1 89 32V68A21 21 0 0 1 68 89H32A21 21 0 0 1 11 68V32A21 21 0 0 1 32 11Z'
  + 'M50 27C52.5 43 57 47.5 73 50C57 52.5 52.5 57 50 73C47.5 57 43 52.5 27 50C43 47.5 47.5 43 50 27Z"/></svg>';
export const MONOGRAMA_URI = 'url("data:image/svg+xml;utf8,' + encodeURIComponent(MONOGRAMA) + '")';

/* A marca desenhada no tamanho pedido. Com logo (PNG/SVG da pasta), é a
   imagem; sem logo, o monograma. "tinta" pinta a forma inteira (a logo vira
   máscara) — é o que deixa a mesma marca sair cromada, bordada ou chapada
   na cor da marca. */
export function Marca({ logo, tamanho = 200, cor = '#fff', tinta }) {
  const s = tamanho;
  if (logo && !tinta) return <Img src={staticFile(logo)} style={{ width: s, height: s, objectFit: 'contain', display: 'block' }} />;
  const url = logo ? 'url(' + staticFile(logo) + ')' : MONOGRAMA_URI;
  return <div style={{ width: s, height: s, background: tinta || cor, WebkitMaskImage: url, maskImage: url, WebkitMaskSize: 'contain', maskSize: 'contain',
    WebkitMaskRepeat: 'no-repeat', maskRepeat: 'no-repeat', WebkitMaskPosition: 'center', maskPosition: 'center' }} />;
}

// ── o acabamento do quadro inteiro ──────────────────────────────────────
/* Os filtros SVG que o quadro usa: a textura de tinta (a letra impressa no
   papel, com a borda comida) e o grão. Definidos uma vez por cena. */
export function Filtros() {
  return (
    <svg width="0" height="0" style={{ position: 'absolute' }} aria-hidden>
      <defs>
        <filter id="pc-tinta" x="-2%" y="-10%" width="104%" height="120%">
          <feTurbulence type="fractalNoise" baseFrequency="0.9" numOctaves="2" seed="7" result="ruido" />
          <feDisplacementMap in="SourceGraphic" in2="ruido" scale="1.6" xChannelSelector="R" yChannelSelector="G" result="borda" />
          <feColorMatrix in="ruido" type="matrix" values="0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 -0.9 1.28" result="poros" />
          <feComposite in="borda" in2="poros" operator="in" />
        </filter>
      </defs>
    </svg>
  );
}

/* Papel: a cor do papel, a fibra e as manchas de luz. Vai na camada do fundo
   (profundidade 0,12) com sobra de 116%: a câmera anda e a borda nunca aparece. */
export function Papel({ el }) {
  const cor = el.cor || '#efece6';
  const sobra = el.sobra || 1.16;
  return (
    <div style={{ position: 'absolute', left: ((1 - sobra) / 2) * 100 + '%', top: ((1 - sobra) / 2) * 100 + '%', width: sobra * 100 + '%', height: sobra * 100 + '%',
      background: 'radial-gradient(ellipse at 30% 25%, ' + mistura(cor, '#ffffff', 0.35) + ' 0%, ' + cor + ' 55%, ' + mistura(cor, '#8a7a60', 0.12) + ' 100%)' }}>
      <svg width="100%" height="100%" style={{ position: 'absolute', inset: 0, opacity: el.fibra ?? 0.5, mixBlendMode: 'multiply' }}>
        <filter id="pc-fibra">
          <feTurbulence type="fractalNoise" baseFrequency="0.012 0.05" numOctaves="3" seed="11" />
          <feColorMatrix type="matrix" values="0 0 0 0 0.45  0 0 0 0 0.40  0 0 0 0 0.32  0 0 0 0.35 0" />
        </filter>
        <rect width="100%" height="100%" filter="url(#pc-fibra)" />
      </svg>
    </div>
  );
}

/* O grão, a vinheta, a luz passando e a grade. Era um bloco dentro do
   Video.jsx; agora é peça do kit e vale para qualquer estilo. */
export function Acabamento({ ac, frame }) {
  if (!ac) return null;
  return (
    <>
      {ac.luz && (
        <div style={{ position: 'absolute', inset: 0, pointerEvents: 'none', mixBlendMode: 'screen', opacity: ac.luzForca ?? 0.22,
          background: 'radial-gradient(circle at ' + (50 + 40 * Math.sin(frame / 38)) + '% ' + (30 + 25 * Math.cos(frame / 51)) + '%, '
            + ac.luz[0] + ' 0%, transparent 45%), radial-gradient(circle at ' + (50 - 40 * Math.sin(frame / 44)) + '% '
            + (75 - 20 * Math.sin(frame / 33)) + '%, ' + (ac.luz[1] || ac.luz[0]) + ' 0%, transparent 40%)' }} />
      )}
      {ac.grade && (
        <div style={{ position: 'absolute', inset: 0, pointerEvents: 'none', opacity: ac.grade,
          backgroundImage: 'linear-gradient(rgba(255,255,255,0.5) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,0.5) 1px, transparent 1px)',
          backgroundSize: (ac.passoGrade || 120) + 'px ' + (ac.passoGrade || 120) + 'px',
          backgroundPosition: (frame * 0.2) + 'px ' + (frame * 0.1) + 'px' }} />
      )}
      {ac.vinheta && (
        <div style={{ position: 'absolute', inset: 0, pointerEvents: 'none',
          background: 'radial-gradient(ellipse at center, transparent 55%, rgba(' + (ac.corVinheta || '0,0,0') + ',' + ac.vinheta + ') 100%)' }} />
      )}
      {ac.grao && (
        <div style={{ position: 'absolute', inset: 0, pointerEvents: 'none', opacity: ac.grao, mixBlendMode: 'overlay' }}>
          <svg width="100%" height="100%">
            <filter id="grao"><feTurbulence type="fractalNoise" baseFrequency="0.85" numOctaves="2" seed={frame % 60} /></filter>
            <rect width="100%" height="100%" filter="url(#grao)" />
          </svg>
        </div>
      )}
    </>
  );
}

/* O HUD do motion designer: cantos de enquadramento, o nome e a seção no
   topo, o timecode e os quadros por segundo embaixo, e a régua do vídeo. */
export function Hud({ hud, frame, fps, total, W, H, secao, escuro }) {
  const cor = hud.cor || (escuro ? 'rgba(255,255,255,0.78)' : 'rgba(17,17,20,0.72)');
  const m = Math.round(Math.min(W, H) * 0.028);
  const t = frame / fps;
  const tc = [Math.floor(t / 3600), Math.floor(t / 60) % 60, Math.floor(t) % 60, frame % fps].map((n) => String(n).padStart(2, '0')).join(':');
  const tam = Math.max(11, Math.round(Math.min(W, H) * 0.0125));
  const canto = (x, y, sx, sy) => (
    <div key={x + '-' + y} style={{ position: 'absolute', left: x, top: y, width: m * 0.8, height: m * 0.8,
      borderLeft: sx > 0 ? '1.5px solid ' + cor : undefined, borderRight: sx < 0 ? '1.5px solid ' + cor : undefined,
      borderTop: sy > 0 ? '1.5px solid ' + cor : undefined, borderBottom: sy < 0 ? '1.5px solid ' + cor : undefined,
      transform: 'translate(' + (sx < 0 ? '-100%' : '0') + ',' + (sy < 0 ? '-100%' : '0') + ')' }} />
  );
  const rotulo = { position: 'absolute', fontFamily: MONO(hud.fonte), fontSize: tam, letterSpacing: '0.14em', color: cor, textTransform: 'uppercase', whiteSpace: 'nowrap' };
  return (
    <div style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}>
      {canto(m, m, 1, 1)}{canto(W - m, m, -1, 1)}{canto(m, H - m, 1, -1)}{canto(W - m, H - m, -1, -1)}
      <div style={{ ...rotulo, left: m * 1.6, top: m * 1.25 }}><b style={{ fontWeight: 700 }}>{hud.marca || ''}</b>{hud.rotulo ? <span style={{ opacity: 0.55 }}>{'  ·  ' + hud.rotulo}</span> : null}</div>
      {secao ? <div style={{ ...rotulo, right: m * 1.6, top: m * 1.25 }}>{secao}</div> : null}
      <div style={{ ...rotulo, left: m * 1.6, bottom: m * 1.25 }}>{tc}<span style={{ opacity: 0.55 }}>{'   ' + fps + ' FPS'}</span></div>
      <div style={{ ...rotulo, right: m * 1.6, bottom: m * 1.25 }}>{hud.direita || ''}</div>
      <div style={{ position: 'absolute', left: m * 1.6, bottom: m * 0.7, height: 1.5, width: (W - m * 3.2) * Math.min(1, frame / Math.max(1, total - 1)), background: cor, opacity: 0.6 }} />
    </div>
  );
}
