/* Cosmic plexus background — multilayered purple network/nebula scene.
   Layers (back-to-front):
   1. radial nebula gradient + drifting fog blobs
   2. ambient star dust (small slow particles)
   3. flowing wave-grid of points (large undulating sheet, low-left)
   4. plexus network (animated nodes + thin lines)
   Performance: single canvas, single rAF, pauses offscreen + on hidden tab,
   respects prefers-reduced-motion. */

const ParticleNetwork = () => {
  const ref = React.useRef(null);
  const wrapRef = React.useRef(null);

  React.useEffect(() => {
    const canvas = ref.current;
    const wrap = wrapRef.current;
    if (!canvas || !wrap) return;
    const ctx = canvas.getContext('2d');

    const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    const dpr = Math.min(window.devicePixelRatio || 1, 2);

    let W = 0, H = 0;
    let nodes = [];   // plexus
    let dust = [];    // ambient stars
    let fog  = [];    // soft drifting glow blobs
    let wave;         // flowing point-grid
    const mouse = { x: -9999, y: -9999, tx: -9999, ty: -9999 };
    let running = true, visible = true;
    let t0 = performance.now();

    function resize() {
      const rect = wrap.getBoundingClientRect();
      W = rect.width;  H = rect.height;
      canvas.width = Math.floor(W * dpr);
      canvas.height = Math.floor(H * dpr);
      canvas.style.width = W + 'px';
      canvas.style.height = H + 'px';
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);

      // Plexus density tuned by area. Higher count for richness.
      const isMobile = W < 720;
      const nodeCount = isMobile ? 90 : Math.min(220, Math.floor((W * H) / 9000));
      nodes = new Array(nodeCount).fill(0).map(() => spawnNode());

      // Ambient dust
      const dustCount = isMobile ? 80 : 220;
      dust = new Array(dustCount).fill(0).map(() => ({
        x: Math.random() * W,
        y: Math.random() * H,
        r: 0.4 + Math.random() * 1.1,
        a: 0.18 + Math.random() * 0.55,
        vx: (Math.random() - 0.5) * 0.04,
        vy: (Math.random() - 0.5) * 0.04,
        twk: Math.random() * Math.PI * 2,
      }));

      // Fog blobs — give the cosmic milky-way feel
      fog = [
        { x: W * 0.18, y: H * 0.30, r: Math.max(W, H) * 0.55, hue: 270, sat: 70, l: 32, a: 0.18, vx: 0.02 },
        { x: W * 0.78, y: H * 0.22, r: Math.max(W, H) * 0.50, hue: 280, sat: 65, l: 28, a: 0.14, vx: -0.015 },
        { x: W * 0.50, y: H * 0.72, r: Math.max(W, H) * 0.55, hue: 265, sat: 75, l: 30, a: 0.13, vx: 0.018 },
        { x: W * 0.20, y: H * 0.85, r: Math.max(W, H) * 0.40, hue: 290, sat: 60, l: 26, a: 0.10, vx: 0.025 },
      ];

      // Flowing wave grid — point sheet that undulates, anchored bottom-left
      const cols = isMobile ? 26 : 44;
      const rows = isMobile ? 16 : 22;
      wave = {
        cols, rows,
        spacingX: W / (cols - 1),
        spacingY: H * 0.55 / (rows - 1),
        originX: -W * 0.05,
        originY: H * 0.55,
        // tilt parameters (3D-ish skew)
        skew: 0.22,
      };
    }

    function spawnNode() {
      // 80% violet, 15% bright violet, 5% warm coral hint
      const r = Math.random();
      let col;
      if (r < 0.80)      col = [165, 130, 255]; // violet primary
      else if (r < 0.95) col = [200, 170, 255]; // light violet
      else               col = [251, 130, 150]; // warm pinch
      return {
        x: Math.random() * W,
        y: Math.random() * H,
        vx: (Math.random() - 0.5) * 0.22,
        vy: (Math.random() - 0.5) * 0.22,
        r: 1 + Math.random() * 1.6,
        col,
        a: 0.55 + Math.random() * 0.4,
      };
    }

    function drawFog(time) {
      // Soft animated radial blobs that breathe and drift
      ctx.save();
      ctx.globalCompositeOperation = 'lighter';
      for (const f of fog) {
        f.x += f.vx;
        if (f.x < -f.r) f.x = W + f.r;
        if (f.x > W + f.r) f.x = -f.r;

        const breath = 0.85 + Math.sin(time * 0.0004 + f.hue) * 0.12;
        const grad = ctx.createRadialGradient(f.x, f.y, 0, f.x, f.y, f.r * breath);
        grad.addColorStop(0, `hsla(${f.hue}, ${f.sat}%, ${f.l}%, ${f.a})`);
        grad.addColorStop(0.5, `hsla(${f.hue}, ${f.sat}%, ${f.l}%, ${f.a * 0.35})`);
        grad.addColorStop(1, `hsla(${f.hue}, ${f.sat}%, ${f.l}%, 0)`);
        ctx.fillStyle = grad;
        ctx.fillRect(f.x - f.r, f.y - f.r, f.r * 2, f.r * 2);
      }
      ctx.restore();
    }

    function drawDust(time) {
      ctx.save();
      ctx.globalCompositeOperation = 'lighter';
      for (const d of dust) {
        d.x += d.vx; d.y += d.vy;
        if (d.x < 0) d.x = W; else if (d.x > W) d.x = 0;
        if (d.y < 0) d.y = H; else if (d.y > H) d.y = 0;
        const tw = 0.55 + Math.sin(time * 0.002 + d.twk) * 0.45;
        ctx.fillStyle = `rgba(220, 200, 255, ${d.a * tw})`;
        ctx.beginPath();
        ctx.arc(d.x, d.y, d.r, 0, Math.PI * 2);
        ctx.fill();
      }
      ctx.restore();
    }

    function drawWave(time) {
      // Flowing point grid that undulates with 2-axis sin/cos noise.
      // Drawn with mild 3D perspective (skew + scale by row).
      ctx.save();
      ctx.globalCompositeOperation = 'lighter';
      const { cols, rows, spacingX, spacingY, originX, originY, skew } = wave;
      const tt = time * 0.0007;
      for (let j = 0; j < rows; j++) {
        const depth = j / (rows - 1);                 // 0 (front) → 1 (back)
        const persp = 1 - depth * 0.55;                // points further away → smaller
        const yBase = originY + j * spacingY * 0.6;
        for (let i = 0; i < cols; i++) {
          const xBase = originX + i * spacingX + (j * spacingX * skew);
          const wave1 = Math.sin(i * 0.35 + j * 0.18 + tt * 1.6) * 14;
          const wave2 = Math.cos(i * 0.18 - j * 0.22 + tt * 1.1) * 10;
          const wave3 = Math.sin((i + j) * 0.12 + tt * 0.8) * 6;
          const yy = yBase + (wave1 + wave2 + wave3) * persp;
          const xx = xBase + Math.sin(j * 0.4 + tt * 0.7) * 8;
          // Fade by distance from center vertical
          const distFromCenter = Math.abs(xx - W * 0.5) / (W * 0.5);
          const horizFade = 1 - Math.min(1, Math.max(0, distFromCenter * 0.6));
          const alpha = 0.55 * persp * horizFade;
          if (alpha < 0.04) continue;
          ctx.fillStyle = `rgba(170, 130, 255, ${alpha})`;
          ctx.beginPath();
          ctx.arc(xx, yy, 1 * persp + 0.4, 0, Math.PI * 2);
          ctx.fill();
        }
      }
      ctx.restore();
    }

    function drawPlexus() {
      // Mouse damping
      mouse.x += (mouse.tx - mouse.x) * 0.06;
      mouse.y += (mouse.ty - mouse.y) * 0.06;

      // Update + draw nodes
      for (const p of nodes) {
        p.x += p.vx;
        p.y += p.vy;
        // gentle mouse repulsion
        if (mouse.x > -9000) {
          const dx = p.x - mouse.x;
          const dy = p.y - mouse.y;
          const d2 = dx*dx + dy*dy;
          const R = 110;
          if (d2 < R*R) {
            const d = Math.sqrt(d2) || 1;
            const f = (1 - d / R) * 0.6;
            p.x += (dx / d) * f;
            p.y += (dy / d) * f;
          }
        }
        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;
      }

      // Lines first (under nodes for cleaner glow)
      const maxLink = 150;
      ctx.lineWidth = 0.7;
      for (let i = 0; i < nodes.length; i++) {
        const a = nodes[i];
        for (let j = i + 1; j < nodes.length; j++) {
          const b = nodes[j];
          const dx = a.x - b.x, dy = a.y - b.y;
          const d2 = dx*dx + dy*dy;
          if (d2 < maxLink * maxLink) {
            const d = Math.sqrt(d2);
            const t = 1 - d / maxLink;
            ctx.strokeStyle = `rgba(165, 130, 255, ${t * 0.32})`;
            ctx.beginPath();
            ctx.moveTo(a.x, a.y);
            ctx.lineTo(b.x, b.y);
            ctx.stroke();
          }
        }
      }

      // Nodes with subtle glow (lighter blend)
      ctx.save();
      ctx.globalCompositeOperation = 'lighter';
      for (const p of nodes) {
        const g = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, p.r * 4);
        g.addColorStop(0, `rgba(${p.col[0]},${p.col[1]},${p.col[2]},${p.a})`);
        g.addColorStop(1, `rgba(${p.col[0]},${p.col[1]},${p.col[2]},0)`);
        ctx.fillStyle = g;
        ctx.fillRect(p.x - p.r * 4, p.y - p.r * 4, p.r * 8, p.r * 8);
        ctx.fillStyle = `rgba(${p.col[0]},${p.col[1]},${p.col[2]},${Math.min(1, p.a * 1.2)})`;
        ctx.beginPath();
        ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2);
        ctx.fill();
      }
      ctx.restore();
    }

    function frame(now) {
      if (!running || !visible) {
        rafId = requestAnimationFrame(frame);
        return;
      }
      ctx.clearRect(0, 0, W, H);
      const time = now - t0;
      drawFog(time);
      drawWave(time);
      drawDust(time);
      drawPlexus();
      rafId = requestAnimationFrame(frame);
    }

    let rafId;
    resize();

    if (reduced) {
      // single static frame
      const time = 0;
      drawFog(time);
      drawWave(time);
      drawDust(time);
      drawPlexus();
      return () => {};
    }

    rafId = requestAnimationFrame(frame);

    function onResize() { resize(); }
    function onMouse(e) {
      const rect = wrap.getBoundingClientRect();
      mouse.tx = e.clientX - rect.left;
      mouse.ty = e.clientY - rect.top;
    }
    function onLeave() { mouse.tx = -9999; mouse.ty = -9999; }
    function onVis() { visible = !document.hidden; }

    const io = new IntersectionObserver((entries) => {
      for (const e of entries) running = e.isIntersecting;
    }, { threshold: 0.05 });
    io.observe(wrap);

    window.addEventListener('resize', onResize, { passive: true });
    window.addEventListener('mousemove', onMouse, { passive: true });
    wrap.addEventListener('mouseleave', onLeave, { passive: true });
    document.addEventListener('visibilitychange', onVis);

    return () => {
      cancelAnimationFrame(rafId);
      window.removeEventListener('resize', onResize);
      window.removeEventListener('mousemove', onMouse);
      wrap.removeEventListener('mouseleave', onLeave);
      document.removeEventListener('visibilitychange', onVis);
      io.disconnect();
    };
  }, []);

  return (
    <div ref={wrapRef} className="particle-net-wrap">
      <canvas ref={ref} className="particle-net-canvas"/>
    </div>
  );
};

window.ParticleNetwork = ParticleNetwork;
