// effects.jsx
// Background grid + particle ambient + cursor trail + cursor crosshair
// All rendered to <canvas> elements that already exist in the DOM.

(function () {
  const { useEffect, useRef } = React;

  // ============================================================
  // 1. BACKGROUND GRID
  // ============================================================
  // Subtle yellow terminal grid. Drawn once on resize.
  function BgGrid() {
    const ref = useRef(null);

    useEffect(() => {
      const cvs = ref.current;
      const ctx = cvs.getContext('2d');

      function draw() {
        const dpr = Math.min(window.devicePixelRatio || 1, 2);
        const w = window.innerWidth;
        const h = window.innerHeight;
        cvs.width = w * dpr;
        cvs.height = h * dpr;
        cvs.style.width = w + 'px';
        cvs.style.height = h + 'px';
        ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
        ctx.clearRect(0, 0, w, h);

        // Minor grid 40px
        ctx.strokeStyle = 'rgba(242, 175, 13, 0.05)';
        ctx.lineWidth = 1;
        ctx.beginPath();
        for (let x = 0; x <= w; x += 40) {
          ctx.moveTo(x + 0.5, 0);
          ctx.lineTo(x + 0.5, h);
        }
        for (let y = 0; y <= h; y += 40) {
          ctx.moveTo(0, y + 0.5);
          ctx.lineTo(w, y + 0.5);
        }
        ctx.stroke();

        // Major grid 200px
        ctx.strokeStyle = 'rgba(242, 175, 13, 0.10)';
        ctx.lineWidth = 1;
        ctx.beginPath();
        for (let x = 0; x <= w; x += 200) {
          ctx.moveTo(x + 0.5, 0);
          ctx.lineTo(x + 0.5, h);
        }
        for (let y = 0; y <= h; y += 200) {
          ctx.moveTo(0, y + 0.5);
          ctx.lineTo(w, y + 0.5);
        }
        ctx.stroke();

        // Coordinate stamps in corners — instrument-panel detail
        ctx.fillStyle = 'rgba(242, 175, 13, 0.35)';
        ctx.font = '9px "Nippo", monospace';
        ctx.fillText('+00.00, +00.00', 14, h - 12);
        ctx.fillText('SYS.GRID/0.4', w - 86, 14);
      }

      draw();
      window.addEventListener('resize', draw);
      return () => window.removeEventListener('resize', draw);
    }, []);

    return <canvas id="bg-grid" ref={ref}></canvas>;
  }

  // ============================================================
  // 2. PARTICLE AMBIENT
  // ============================================================
  // 20-30 floating particles, brownian drift, subtle glow.
  function Particles({ density = 1, paused = false }) {
    const ref = useRef(null);
    const rafRef = useRef(0);
    const particlesRef = useRef([]);
    const densityRef = useRef(density);
    const pausedRef = useRef(paused);

    useEffect(() => { densityRef.current = density; }, [density]);
    useEffect(() => { pausedRef.current = paused; }, [paused]);

    useEffect(() => {
      const cvs = ref.current;
      const ctx = cvs.getContext('2d');
      const dpr = Math.min(window.devicePixelRatio || 1, 2);
      let w = 0, h = 0;

      function resize() {
        w = window.innerWidth;
        h = window.innerHeight;
        cvs.width = w * dpr;
        cvs.height = h * dpr;
        cvs.style.width = w + 'px';
        cvs.style.height = h + 'px';
        ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
      }
      resize();

      function makeParticles() {
        const n = Math.round(28 * densityRef.current);
        const arr = [];
        for (let i = 0; i < n; i++) {
          arr.push({
            x: Math.random() * w,
            y: Math.random() * h,
            vx: (Math.random() - 0.5) * 0.18,
            vy: (Math.random() - 0.5) * 0.12 - 0.04,
            r: Math.random() * 1.4 + 0.4,
            life: Math.random(),
            tone: Math.random() < 0.22 ? 'red' : 'yellow',
          });
        }
        particlesRef.current = arr;
      }
      makeParticles();
      let lastDensity = densityRef.current;

      function frame() {
        if (lastDensity !== densityRef.current) {
          makeParticles();
          lastDensity = densityRef.current;
        }
        ctx.clearRect(0, 0, w, h);
        if (pausedRef.current) {
          rafRef.current = requestAnimationFrame(frame);
          return;
        }

        const ps = particlesRef.current;
        for (let i = 0; i < ps.length; i++) {
          const p = ps[i];
          p.x += p.vx;
          p.y += p.vy;
          p.life += 0.004;

          // Wrap around screen
          if (p.x < -10) p.x = w + 10;
          if (p.x > w + 10) p.x = -10;
          if (p.y < -10) p.y = h + 10;
          if (p.y > h + 10) p.y = -10;

          const breath = (Math.sin(p.life * Math.PI * 2) + 1) / 2; // 0..1
          const alpha = 0.18 + breath * 0.32;

          if (p.tone === 'red') {
            ctx.fillStyle = `rgba(169, 0, 0, ${alpha})`;
          } else {
            ctx.fillStyle = `rgba(242, 175, 13, ${alpha * 0.85})`;
          }
          ctx.beginPath();
          ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2);
          ctx.fill();

          // Subtle glow on the bigger ones
          if (p.r > 1.2) {
            ctx.fillStyle = `rgba(242, 175, 13, ${alpha * 0.08})`;
            ctx.beginPath();
            ctx.arc(p.x, p.y, p.r * 4, 0, Math.PI * 2);
            ctx.fill();
          }
        }

        rafRef.current = requestAnimationFrame(frame);
      }
      rafRef.current = requestAnimationFrame(frame);

      window.addEventListener('resize', resize);
      return () => {
        cancelAnimationFrame(rafRef.current);
        window.removeEventListener('resize', resize);
      };
    }, []);

    return <canvas id="bg-particles" ref={ref}></canvas>;
  }

  // ============================================================
  // 3. CURSOR — crosshair + dot + trail
  // ============================================================
  function Cursor({ intensity = 1 }) {
    const cvsRef = useRef(null);
    const xRef = useRef(0);
    const yRef = useRef(0);
    const trailRef = useRef([]);
    const intensityRef = useRef(intensity);
    const rafRef = useRef(0);

    useEffect(() => { intensityRef.current = intensity; }, [intensity]);

    useEffect(() => {
      const cvs = cvsRef.current;
      const ctx = cvs.getContext('2d');
      const dpr = Math.min(window.devicePixelRatio || 1, 2);
      let w = 0, h = 0;

      function resize() {
        w = window.innerWidth;
        h = window.innerHeight;
        cvs.width = w * dpr;
        cvs.height = h * dpr;
        cvs.style.width = w + 'px';
        cvs.style.height = h + 'px';
        ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
      }
      resize();

      xRef.current = w / 2;
      yRef.current = h / 2;

      const crosshair = document.getElementById('cursor-crosshair');
      const dot = document.getElementById('cursor-dot');

      function onMove(e) {
        xRef.current = e.clientX;
        yRef.current = e.clientY;

        // Check if hovering a clickable / text field
        const el = document.elementFromPoint(e.clientX, e.clientY);
        const clickable = el && el.closest('button, a, [data-clickable], input, textarea');
        const textField = el && el.closest('input, textarea');
        crosshair.classList.toggle('is-clickable', !!clickable && !textField);
        crosshair.classList.toggle('is-text', !!textField);
      }
      window.addEventListener('mousemove', onMove);

      let last = performance.now();
      function frame(now) {
        const dt = Math.min((now - last) / 16.67, 3);
        last = now;
        ctx.clearRect(0, 0, w, h);

        // Push current pos onto trail
        const trail = trailRef.current;
        trail.push({ x: xRef.current, y: yRef.current, life: 1 });
        if (trail.length > 22) trail.shift();

        // Draw trail (older = fainter, smaller)
        const intens = intensityRef.current;
        for (let i = 0; i < trail.length; i++) {
          const p = trail[i];
          p.life -= 0.05 * dt;
          if (p.life <= 0) continue;
          const t = (i + 1) / trail.length; // 0..1, newer larger
          const r = 1 + t * 6 * intens;
          ctx.fillStyle = `rgba(255, 255, 255, ${p.life * 0.16 * intens})`;
          ctx.beginPath();
          ctx.arc(p.x, p.y, r, 0, Math.PI * 2);
          ctx.fill();
        }

        // Move crosshair + dot via transform (cheaper than re-painting)
        if (crosshair) {
          crosshair.style.transform = `translate(${xRef.current}px, ${yRef.current}px) translate(-50%, -50%)`;
        }
        if (dot) {
          dot.style.transform = `translate(${xRef.current}px, ${yRef.current}px) translate(-50%, -50%)`;
        }

        rafRef.current = requestAnimationFrame(frame);
      }
      rafRef.current = requestAnimationFrame(frame);

      window.addEventListener('resize', resize);
      return () => {
        cancelAnimationFrame(rafRef.current);
        window.removeEventListener('mousemove', onMove);
        window.removeEventListener('resize', resize);
      };
    }, []);

    return (
      <React.Fragment>
        <canvas id="cursor-trail" ref={cvsRef}></canvas>
        <div className="cursor" id="cursor-crosshair"></div>
        <div className="cursor-dot" id="cursor-dot"></div>
      </React.Fragment>
    );
  }

  // ============================================================
  // 4. CRT GLITCH — fired on navigation (folder transitions)
  // ============================================================
  // No random scheduler. Exposes window.crtBurst() so the app can jolt
  // the screen exactly when routes change.
  function CrtGlitch() {
    useEffect(() => {
      const overlay = document.getElementById('crt-glitch');
      const app = document.getElementById('app');
      if (!overlay || !app) return;

      function burst() {
        const gy = (8 + Math.random() * 80).toFixed(1) + '%';
        const gx = (Math.random() * 5 - 2.5).toFixed(1) + 'px';
        overlay.style.setProperty('--gy', gy);
        app.style.setProperty('--gx', gx);

        overlay.classList.remove('is-glitching');
        app.classList.remove('is-glitching');
        void overlay.offsetWidth; // restart animation
        overlay.classList.add('is-glitching');
        app.classList.add('is-glitching');

        if (window.Audio && window.Audio.isEnabled() && Math.random() < 0.5) {
          window.Audio.tick();
        }

        setTimeout(() => {
          overlay.classList.remove('is-glitching');
          app.classList.remove('is-glitching');
        }, 260);
      }

      // expose imperative trigger + occasional stutter
      window.crtBurst = function () {
        burst();
        if (Math.random() < 0.5) setTimeout(burst, 150 + Math.random() * 110);
      };

      return () => { delete window.crtBurst; };
    }, []);

    return <div id="crt-glitch"></div>;
  }

  // ============================================================
  // 5. GHOST WAVEFORM — pseudo-3D perspective ridge terrain
  // ============================================================
  // A field of stacked waveform rows projected in perspective (back rows
  // narrow + high + faint, front rows wide + low + bright), evoking the
  // classic "audio terrain" look. Lines run yellow at the base bleeding to
  // red at the peaks. Sits top-left, faint, behind the UI, click-through.
  function GhostWaveform() {
    const ref = useRef(null);
    const rafRef = useRef(0);

    useEffect(() => {
      const cvs = ref.current;
      const ctx = cvs.getContext('2d');
      const dpr = Math.min(window.devicePixelRatio || 1, 2);
      const W = 440, H = 260;
      cvs.width = W * dpr; cvs.height = H * dpr;
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);

      const ROWS = 24;
      const COLS = 60;
      // per-column phase offsets give an irregular ridgeline
      const ph = new Array(COLS).fill(0).map((_, i) => i * 0.5 + Math.random() * 0.6);
      let t = 0;

      function amp(col, rowT) {
        // travelling wave: ridges drift from back to front over time
        const p = ph[col];
        const a =
          Math.sin(p * 1.0 + rowT * 5.0 - t * 1.2) * 0.5 +
          Math.sin(p * 0.5 - rowT * 3.0 + t * 0.7) * 0.35 +
          Math.sin(p * 2.3 + t * 0.4) * 0.18;
        const env = Math.sin((col / (COLS - 1)) * Math.PI); // taper at the edges
        return Math.max(0, a) * env;
      }

      const lerp = (a, b, k) => a + (b - a) * k;

      let lvl = 0; // smoothed ambient level

      function frame() {
        // React to the real ambient bed when it's playing; otherwise drift gently.
        const audioOn = !!(window.Audio && window.Audio.isAmbient && window.Audio.isAmbient());
        const target = audioOn && window.Audio.getLevel ? window.Audio.getLevel() : 0;
        lvl += (target - lvl) * 0.14;
        const react = audioOn ? (0.5 + lvl * 3.4) : 1;      // amplitude multiplier
        const drift = audioOn ? (0.6 + lvl * 2.4) : 1;        // time/scroll speed

        t += 0.02 * drift;
        ctx.clearRect(0, 0, W, H);
        const cx = W / 2;
        const topY = 36, botY = H - 16;

        // back-to-front so nearer ridges sit visually in front
        for (let r = 0; r < ROWS; r++) {
          const rt = r / (ROWS - 1);          // 0 back → 1 front
          const sc = lerp(0.42, 1.0, rt);     // horizontal spread
          const yBase = lerp(topY, botY, rt); // front lower
          const ampPx = lerp(7, 74, rt) * react; // front taller, scaled by audio
          const alpha = lerp(0.1, 0.5, rt);

          // build points
          const pts = [];
          let maxH = 0;
          for (let c = 0; c < COLS; c++) {
            const h = amp(c, rt) * ampPx;
            if (h > maxH) maxH = h;
            const x = cx + ((c / (COLS - 1)) - 0.5) * (W - 36) * sc;
            const y = yBase - h;
            pts.push([x, y]);
          }

          // yellow base → red peaks gradient over this ridge's height band
          const grad = ctx.createLinearGradient(0, yBase, 0, yBase - ampPx);
          grad.addColorStop(0, `rgba(242,175,13,${alpha})`);
          grad.addColorStop(0.62, `rgba(242,175,13,${alpha})`);
          grad.addColorStop(1, `rgba(200,30,0,${Math.min(0.85, alpha + 0.25)})`);
          ctx.strokeStyle = grad;
          ctx.lineWidth = lerp(0.6, 1.1, rt);

          ctx.beginPath();
          ctx.moveTo(pts[0][0], pts[0][1]);
          for (let c = 1; c < COLS; c++) {
            const [x0, y0] = pts[c - 1];
            const [x1, y1] = pts[c];
            const mx = (x0 + x1) / 2, my = (y0 + y1) / 2;
            ctx.quadraticCurveTo(x0, y0, mx, my);
          }
          ctx.stroke();
        }

        rafRef.current = requestAnimationFrame(frame);
      }
      rafRef.current = requestAnimationFrame(frame);
      return () => cancelAnimationFrame(rafRef.current);
    }, []);

    return <canvas id="ghost-wave" ref={ref} width="440" height="260"></canvas>;
  }

  Object.assign(window, { BgGrid, Particles, Cursor, CrtGlitch, GhostWaveform });
})();
