/* fx3d/Objetos.jsx — a camada TRANSPARENTE dos objetos que seguem as mãos.
 *
 * Tudo o que é decisão (onde fica cada objeto em cada quadro, quando entra,
 * quando funde) já vem calculado do lib/video/camadas.js nas props: aqui só
 * se desenha. Assim a conta é testada no node, e o 3D só gira, brilha e voa.
 *
 * props: {n, fps, w, h, objetos:[{modelo, cor?, texto?, f0, f1, trilha:[[x,y,s]|0], funde?}],
 *         fusao?: {fj, fm, trilha:[[x,y,s]|0]}}
 *   x,y = centro do objeto em frações da tela; s = diâmetro em fração da altura. */
import React, { useMemo } from 'react';
import { AbsoluteFill, useCurrentFrame } from 'remotion';
import { ThreeCanvas } from '@remotion/three';
import * as THREE from 'three';
import { useThree } from '@react-three/fiber';
import { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment.js';
import { Modelo } from './modelos';

const FOV = 30;
const DIST = 10;
const ALTO = 2 * DIST * Math.tan((FOV * Math.PI) / 360);   // altura do mundo visível no plano z=0

const clamp = (x) => Math.max(0, Math.min(1, x));
const saida = (x) => 1 - Math.pow(1 - clamp(x), 3);
const volta = (x) => { const c = 1.9, t = clamp(x) - 1; return 1 + (c + 1) * t * t * t + c * t * t; };   // easeOutBack
const lerp = (a, b, k) => a + (b - a) * k;

function Ambiente() {
  const { gl, scene } = useThree();
  useMemo(() => {
    const pm = new THREE.PMREMGenerator(gl);
    scene.environment = pm.fromScene(new RoomEnvironment(), 0.04).texture;
    scene.environmentIntensity = 0.9;
    pm.dispose();
  }, [gl, scene]);
  return null;
}

// Frações da tela → mundo (no plano z=0 da câmera em perspectiva).
const mundo = (x, y, asp) => [(x - 0.5) * ALTO * asp, (0.5 - y) * ALTO, 0];

function Objeto({ o, f, fps, asp, fusao }) {
  const p = o.trilha[f - o.f0] || o.trilha[Math.min(o.trilha.length - 1, Math.max(0, f - o.f0))];
  if (!p) return null;
  let [x, y, s] = p;
  const local = (f - o.f0) / fps;
  let escala = volta(local / 0.5);                         // entra com mola em 0,5 s
  let giro = -Math.PI * 1.2 * (1 - saida(local / 0.7)) + Math.sin(local * 1.1) * 0.35;
  if (!o.funde && o.f1 - f < fps * 0.3) escala *= saida((o.f1 - f) / (fps * 0.3));   // sai encolhendo
  if (o.funde && fusao && f >= fusao.fj) {
    const m = fusao.trilha[Math.max(0, Math.min(fusao.trilha.length - 1, f - fusao.fj))];
    const k = saida((f - fusao.fj) / Math.max(1, fusao.fm - fusao.fj));
    if (m) { x = lerp(x, m[0], k); y = lerp(y, m[1], k); }
    escala *= 1 - 0.55 * k;
    giro += k * Math.PI * 3;
    if (f >= fusao.fm) return null;
  }
  const flutua = Math.sin(local * 2.4 + o.f0) * 0.012;
  const [wx, wy] = mundo(x, y + flutua, asp);
  const tam = s * ALTO * Math.max(0, escala);
  return (
    <group position={[wx, wy, 0]} rotation={[0.12 + Math.sin(local * 0.9) * 0.08, giro, Math.sin(local * 0.7) * 0.05]} scale={[tam, tam, tam]}>
      <Modelo modelo={o.modelo} cor={o.cor} texto={o.texto} />
      <pointLight position={[0, -0.6, 0.6]} intensity={0.6} distance={3} color={o.modelo === 'cerebro' ? '#ff9fb8' : '#ffffff'} />
    </group>
  );
}

/* O clarão da fusão: uma esfera que abre e some, um anel que corre e
   faíscas que saem do centro (semente fixa: o mesmo quadro sai igual). */
function Clarao({ fusao, f, fps, asp }) {
  const faiscas = useMemo(() => Array.from({ length: 46 }, (_, i) => {
    const a = (i * 2.399963) % (Math.PI * 2);
    const z = ((i * 0.618) % 1) * 2 - 1;
    const r = Math.sqrt(1 - z * z);
    return { d: [Math.cos(a) * r, Math.sin(a) * r, z], v: 0.7 + ((i * 0.37) % 1) * 0.9, c: i % 3 === 0 ? '#ffd27a' : i % 3 === 1 ? '#9fe8ff' : '#ffffff' };
  }), []);
  if (!fusao || f < fusao.fm) return null;
  const t = (f - fusao.fm) / fps;
  if (t > 0.9) return null;
  const m = fusao.trilha[Math.max(0, Math.min(fusao.trilha.length - 1, f - fusao.fj))];
  if (!m) return null;
  const [wx, wy] = mundo(m[0], m[1], asp);
  const R = m[2] * ALTO;
  const k = saida(t / 0.35);
  return (
    <group position={[wx, wy, 0.5]}>
      <mesh scale={[R * (0.3 + 1.4 * k), R * (0.3 + 1.4 * k), R * (0.3 + 1.4 * k)]}>
        <sphereGeometry args={[0.5, 32, 32]} />
        <meshBasicMaterial color="#fff6e0" transparent opacity={Math.max(0, 0.9 * (1 - t / 0.35))} blending={THREE.AdditiveBlending} depthWrite={false} />
      </mesh>
      <mesh scale={[R * (0.4 + 2.2 * k), R * (0.4 + 2.2 * k), 1]}>
        <ringGeometry args={[0.46, 0.5, 96]} />
        <meshBasicMaterial color="#bfefff" transparent opacity={Math.max(0, 0.8 * (1 - t / 0.5))} blending={THREE.AdditiveBlending} depthWrite={false} side={THREE.DoubleSide} />
      </mesh>
      {faiscas.map((p, i) => {
        const dd = R * p.v * (0.2 + 1.8 * saida(t / 0.8));
        const a = Math.max(0, 1 - t / 0.85);
        return (
          <mesh key={i} position={[p.d[0] * dd, p.d[1] * dd, p.d[2] * dd]} scale={[R * 0.035 * a + 0.001, R * 0.035 * a + 0.001, R * 0.035 * a + 0.001]}>
            <sphereGeometry args={[1, 10, 10]} />
            <meshBasicMaterial color={p.c} transparent opacity={a} blending={THREE.AdditiveBlending} depthWrite={false} />
          </mesh>
        );
      })}
    </group>
  );
}

export const Objetos = (props) => {
  const f = useCurrentFrame();
  const { w, h, fps } = props;
  const asp = w / h;
  const vivos = (props.objetos || []).filter((o) => f >= o.f0 && f < o.f1);
  return (
    <AbsoluteFill>
      <ThreeCanvas width={w} height={h} camera={{ position: [0, 0, DIST], fov: FOV, near: 0.1, far: 100 }}
        gl={{ antialias: true, alpha: true, preserveDrawingBuffer: true, premultipliedAlpha: false }}>
        <Ambiente />
        <ambientLight intensity={0.35} />
        <directionalLight position={[3, 5, 6]} intensity={2.6} />
        <directionalLight position={[-4, 2, 3]} intensity={1.0} color="#cfe6ff" />
        <directionalLight position={[0, -3, -4]} intensity={1.6} color="#ffd9b0" />
        {vivos.map((o, i) => <Objeto key={i} o={o} f={f} fps={fps} asp={asp} fusao={props.fusao} />)}
        <Clarao fusao={props.fusao} f={f} fps={fps} asp={asp} />
      </ThreeCanvas>
    </AbsoluteFill>
  );
};
