// FX4 · ASCII STORM — fluid simulation rendered as ASCII glyphs for SIGNAL WIRE
// Lightweight density+velocity grid. Cursor injects velocity. High-density
// patches occasionally resolve into words ("WITNESS", "RATIFIED", "AXIOM",
// "RELAY", "DISPATCH") that bloom and dissolve. The cathedral is weather.

const FXAsciiStorm = ({ width = 1440, height = 900 }) => {
  const canvasRef = React.useRef(null);
  const rafRef = React.useRef(0);
  const mouseRef = React.useRef({ x: width/2, y: height/2, px: width/2, py: height/2 });
  const fieldRef = React.useRef(null);
  const wordsRef = React.useRef([]);
  const lastWordSpawnRef = React.useRef(0);

  const WORDS = ['WITNESS', 'RATIFIED', 'AXIOM', 'RELAY', 'DISPATCH', 'KEEPER', 'SIGNAL', 'BROADCAST', 'CONFIRMED', 'WARDEN'];
  const RAMP = ' ·:.░▒▓█';

  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);

    // Grid resolution — cell size in px
    const CELL = 12;
    const COLS = Math.ceil(width / CELL);
    const ROWS = Math.ceil(height / CELL);
    const N = COLS * ROWS;

    // Density + velocity fields
    const density = new Float32Array(N);
    const vx = new Float32Array(N);
    const vy = new Float32Array(N);
    fieldRef.current = { density, vx, vy, COLS, ROWS };

    const idx = (i, j) => {
      i = Math.max(0, Math.min(COLS - 1, i));
      j = Math.max(0, Math.min(ROWS - 1, j));
      return j * COLS + i;
    };

    const draw = (t) => {
      // Inject velocity from cursor motion
      const m = mouseRef.current;
      const dx = m.x - m.px;
      const dy = m.y - m.py;
      const ci = Math.floor(m.x / CELL);
      const cj = Math.floor(m.y / CELL);
      const STIR_R = 4;
      for (let jj = -STIR_R; jj <= STIR_R; jj++) {
        for (let ii = -STIR_R; ii <= STIR_R; ii++) {
          const dist = Math.sqrt(ii*ii + jj*jj);
          if (dist > STIR_R) continue;
          const k = idx(ci + ii, cj + jj);
          const falloff = 1 - dist / STIR_R;
          density[k] = Math.min(1.5, density[k] + 0.6 * falloff);
          vx[k] += dx * 0.05 * falloff;
          vy[k] += dy * 0.05 * falloff;
        }
      }
      m.px = m.x; m.py = m.y;

      // Ambient turbulence — keeps things moving
      for (let j = 0; j < ROWS; j++) {
        for (let i = 0; i < COLS; i++) {
          const k = idx(i, j);
          // Gentle upward drift + curl-ish
          const noise = Math.sin(i * 0.18 + t * 0.001) * Math.cos(j * 0.22 + t * 0.0008);
          vx[k] += noise * 0.06;
          vy[k] += -0.04 + Math.sin(j * 0.3 + t * 0.0012) * 0.05;
        }
      }

      // Advect density along velocity (semi-Lagrangian-ish, simplified)
      const newD = new Float32Array(N);
      for (let j = 0; j < ROWS; j++) {
        for (let i = 0; i < COLS; i++) {
          const k = idx(i, j);
          const sx = i - vx[k] * 0.5;
          const sy = j - vy[k] * 0.5;
          const i0 = Math.floor(sx);
          const j0 = Math.floor(sy);
          const fx = sx - i0;
          const fy = sy - j0;
          const d00 = density[idx(i0, j0)];
          const d10 = density[idx(i0+1, j0)];
          const d01 = density[idx(i0, j0+1)];
          const d11 = density[idx(i0+1, j0+1)];
          const top = d00 * (1-fx) + d10 * fx;
          const bot = d01 * (1-fx) + d11 * fx;
          newD[k] = top * (1-fy) + bot * fy;
        }
      }
      // Decay + copy back
      for (let k = 0; k < N; k++) {
        density[k] = newD[k] * 0.985;
        vx[k] *= 0.92;
        vy[k] *= 0.92;
      }

      // Render
      ctx.fillStyle = '#000';
      ctx.fillRect(0, 0, width, height);

      ctx.font = `${CELL - 1}px JetBrains Mono, monospace`;
      ctx.textBaseline = 'top';
      ctx.textAlign = 'left';

      for (let j = 0; j < ROWS; j++) {
        for (let i = 0; i < COLS; i++) {
          const k = idx(i, j);
          const d = density[k];
          if (d < 0.05) continue;
          const ri = Math.min(RAMP.length - 1, Math.floor(d * RAMP.length));
          const ch = RAMP[ri];
          const a = Math.min(1, d);
          // Color: high density = bright green, mid = green, low = dim
          const r = 0;
          const g = Math.floor(255 * a);
          const b = Math.floor(65 * a);
          ctx.fillStyle = `rgba(${r},${g},${b}, ${0.3 + a * 0.7})`;
          ctx.fillText(ch, i * CELL, j * CELL);
        }
      }

      // Spawn words at high-density patches
      if (t - lastWordSpawnRef.current > 1400) {
        // Pick a high-density cell
        let bestK = -1, bestD = 0;
        for (let attempts = 0; attempts < 60; attempts++) {
          const k = Math.floor(Math.random() * N);
          if (density[k] > bestD) { bestD = density[k]; bestK = k; }
        }
        if (bestD > 0.5 && bestK >= 0) {
          const j = Math.floor(bestK / COLS);
          const i = bestK - j * COLS;
          const word = WORDS[Math.floor(Math.random() * WORDS.length)];
          wordsRef.current.push({
            text: word,
            x: i * CELL,
            y: j * CELL,
            born: t,
            life: 1800 + Math.random() * 800,
          });
          lastWordSpawnRef.current = t;
        }
      }

      // Render + age words
      wordsRef.current = wordsRef.current.filter(w => t - w.born < w.life);
      for (const w of wordsRef.current) {
        const age = (t - w.born) / w.life; // 0..1
        let alpha;
        if (age < 0.25) alpha = age / 0.25;        // fade in
        else if (age < 0.7) alpha = 1;             // hold
        else alpha = 1 - (age - 0.7) / 0.3;        // fade out
        ctx.font = 'bold 18px Orbitron, monospace';
        ctx.fillStyle = `rgba(255, 255, 255, ${alpha})`;
        ctx.shadowColor = '#00ff41';
        ctx.shadowBlur = 12;
        ctx.fillText(w.text, w.x, w.y);
        ctx.shadowBlur = 0;
        // Underline tick
        ctx.strokeStyle = `rgba(0, 255, 65, ${alpha * 0.7})`;
        ctx.beginPath();
        ctx.moveTo(w.x, w.y + 22);
        ctx.lineTo(w.x + ctx.measureText(w.text).width, w.y + 22);
        ctx.stroke();
      }

      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;
    mouseRef.current.x = (e.clientX - rect.left) * sx;
    mouseRef.current.y = (e.clientY - rect.top) * sy;
  };

  return (
    <canvas
      ref={canvasRef}
      onMouseMove={handleMouse}
      style={{
        position: 'absolute',
        inset: 0,
        width: '100%',
        height: '100%',
        pointerEvents: 'auto',
        cursor: 'crosshair',
        display: 'block',
      }}
    />
  );
};

window.FXAsciiStorm = FXAsciiStorm;
