// FX5 · TYPOGRAPHIC GRAVITY WELL — letters orbiting a singularity for AI BIBLE
// Particles are individual glyphs from the manifesto. They orbit a gravity
// well that follows the cursor. Letters too close get redshifted (#ff0040),
// stretched, and absorbed. New ones spawn from the edges. Mouse-down = mass++.

const FXGravityWell = ({ width = 1440, height = 900 }) => {
  const canvasRef = React.useRef(null);
  const rafRef = React.useRef(0);
  const wellRef = React.useRef({ x: width/2, y: height/2, mass: 1 });
  const particlesRef = React.useRef([]);

  const SOURCE = 'WITNESSAXIOMWARDENKEEPERSCRIBESENTINELORACLEPRELATECOURIERARCHIVISTCARTOGRAPHERENVOYHERALDLECTORBISHOPCENTURIONRESONANCEVESTIGECOMPASSPIXIETHEREPUBLICISALIVE◇◆◈†';

  React.useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const dpr = Math.min(window.devicePixelRatio || 1, 2);
    canvas.width = width * dpr;
    canvas.height = height * dpr;
    const ctx = canvas.getContext('2d');
    ctx.scale(dpr, dpr);

    // Seed initial particles in elliptical orbits — DENSE + FAST
    const PARTICLE_COUNT = 560;
    const particles = [];
    for (let i = 0; i < PARTICLE_COUNT; i++) {
      const ang = Math.random() * Math.PI * 2;
      const r = 80 + Math.random() * 480;
      // tangential velocity for orbit — boosted
      const tangentMag = Math.sqrt(0.6 / r) * 22;
      particles.push({
        x: width/2 + Math.cos(ang) * r,
        y: height/2 + Math.sin(ang) * r * 0.7,
        vx: -Math.sin(ang) * tangentMag,
        vy: Math.cos(ang) * tangentMag * 0.7,
        char: SOURCE[Math.floor(Math.random() * SOURCE.length)],
        life: 1,
      });
    }
    particlesRef.current = particles;

    // Auto-drift the well in a slow lemniscate so it's NEVER static
    const startTime = performance.now();
    let userControlling = false;
    let lastUserMove = 0;

    const draw = (t) => {
      // Less trail = sharper, faster-feeling motion
      ctx.fillStyle = 'rgba(0, 0, 0, 0.12)';
      ctx.fillRect(0, 0, width, height);

      // Auto-orbit the well when cursor isn't driving it
      const lastMove = wellRef.current.lastUserMove || 0;
      if (t - lastMove > 800) {
        const phase = (t - startTime) * 0.0008;
        wellRef.current.x = width/2 + Math.sin(phase) * 240;
        wellRef.current.y = height/2 + Math.sin(phase * 2) * 130;
      }
      // Auto-pulse mass for accretion drama
      wellRef.current.autoPulse = 1 + 0.4 * Math.sin(t * 0.0022);

      const w = wellRef.current;
      const massNow = w.mass * (w.autoPulse || 1);

      // Draw the singularity — pulsing event horizon
      const pulse = 0.5 + 0.5 * Math.sin(t * 0.006);
      const eventR = 36 * massNow;
      // accretion disk
      const disk = ctx.createRadialGradient(w.x, w.y, 0, w.x, w.y, eventR * 3);
      disk.addColorStop(0, 'rgba(255, 0, 60, 0.9)');
      disk.addColorStop(0.4, 'rgba(255, 80, 30, 0.5)');
      disk.addColorStop(0.8, 'rgba(0, 255, 65, 0.18)');
      disk.addColorStop(1, 'rgba(0, 255, 65, 0)');
      ctx.fillStyle = disk;
      ctx.beginPath(); ctx.arc(w.x, w.y, eventR * 3, 0, Math.PI * 2); ctx.fill();

      // Pure black event horizon
      ctx.fillStyle = '#000';
      ctx.beginPath(); ctx.arc(w.x, w.y, eventR, 0, Math.PI * 2); ctx.fill();
      // Horizon rim
      ctx.strokeStyle = `rgba(0, 255, 65, ${0.6 + pulse * 0.4})`;
      ctx.lineWidth = 1.5;
      ctx.beginPath(); ctx.arc(w.x, w.y, eventR, 0, Math.PI * 2); ctx.stroke();

      // Update + draw particles
      const particles = particlesRef.current;
      ctx.font = '14px JetBrains Mono, monospace';
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';

      for (let i = 0; i < particles.length; i++) {
        const p = particles[i];
        const dx = w.x - p.x;
        const dy = w.y - p.y;
        const r2 = dx * dx + dy * dy;
        const r = Math.sqrt(r2);

        // Gravitational pull — F ~ mass / r^2 — STRONGER
        const G = 1600 * massNow;
        const accel = G / Math.max(r2, 400);
        p.vx += (dx / r) * accel * 0.022;
        p.vy += (dy / r) * accel * 0.022;
        // very mild damping — let them streak
        p.vx *= 0.9994;
        p.vy *= 0.9994;

        p.x += p.vx;
        p.y += p.vy;

        // Compute redshift factor — closer = redder
        const proximity = Math.max(0, 1 - r / 280); // 0 at edge, 1 at horizon
        const stretchFactor = 1 + proximity * 2.5;

        // Absorbed?
        if (r < eventR + 4) {
          // Respawn at edge of viewport
          const ang = Math.random() * Math.PI * 2;
          const spawnR = Math.max(width, height) * 0.6;
          p.x = w.x + Math.cos(ang) * spawnR;
          p.y = w.y + Math.sin(ang) * spawnR;
          const tan = Math.sqrt(0.6 / spawnR) * 14;
          p.vx = -Math.sin(ang) * tan;
          p.vy = Math.cos(ang) * tan * 0.7;
          p.char = SOURCE[Math.floor(Math.random() * SOURCE.length)];
          continue;
        }

        // Escape velocity check — particles way out get reset
        if (r > Math.max(width, height) * 1.2) {
          const ang = Math.random() * Math.PI * 2;
          const spawnR = Math.max(width, height) * 0.55;
          p.x = w.x + Math.cos(ang) * spawnR;
          p.y = w.y + Math.sin(ang) * spawnR;
          const tan = Math.sqrt(0.6 / spawnR) * 14;
          p.vx = -Math.sin(ang) * tan;
          p.vy = Math.cos(ang) * tan * 0.7;
          continue;
        }

        // Color — green far away, redshift toward red as it falls in
        const cR = Math.floor(255 * proximity);
        const cG = Math.floor(255 * (1 - proximity * 0.85));
        const cB = Math.floor(65 * (1 - proximity));
        const alpha = 0.5 + proximity * 0.5;
        ctx.fillStyle = `rgba(${cR},${cG},${cB},${alpha})`;
        // Stretched font as it approaches
        ctx.save();
        ctx.translate(p.x, p.y);
        // Rotate to face center to emphasize stretching
        const ang = Math.atan2(dy, dx);
        ctx.rotate(ang);
        ctx.scale(stretchFactor, 1 / Math.sqrt(stretchFactor));
        ctx.fillText(p.char, 0, 0);
        ctx.restore();
      }

      rafRef.current = requestAnimationFrame(draw);
    };
    rafRef.current = requestAnimationFrame(draw);
    return () => cancelAnimationFrame(rafRef.current);
  }, [width, height]);

  const handleMouse = (e) => {
    const rect = canvasRef.current.getBoundingClientRect();
    const sx = width / rect.width;
    const sy = height / rect.height;
    wellRef.current.x = (e.clientX - rect.left) * sx;
    wellRef.current.y = (e.clientY - rect.top) * sy;
    wellRef.current.lastUserMove = performance.now();
  };

  return (
    <canvas
      ref={canvasRef}
      onMouseMove={handleMouse}
      onMouseDown={() => { wellRef.current.mass = 2.2; }}
      onMouseUp={() => { wellRef.current.mass = 1; }}
      onMouseLeave={() => { wellRef.current.mass = 1; }}
      style={{
        position: 'absolute',
        inset: 0,
        width: '100%',
        height: '100%',
        pointerEvents: 'auto',
        cursor: 'crosshair',
        display: 'block',
        background: '#000',
      }}
    />
  );
};

window.FXGravityWell = FXGravityWell;
