/* global React */
// Animated 3D network/globe visual — uses canvas 2D with projected 3D points.

const NetworkCanvas = ({ density = 80, glow = 1, sphere = true, mouseParallax = true, scrollReact = true }) => {
  const canvasRef = React.useRef(null);
  const stateRef = React.useRef({ rx: -0.2, ry: 0, mx: 0, my: 0, tx: 0, ty: 0, scroll: 0, scrollLerp: 0, visible: true });

  React.useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext("2d");
    let raf;
    let dpr = Math.min(window.devicePixelRatio || 1, 1.75); // cap DPR for perf
    let W = 0,H = 0;

    const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;

    const points = [];
    const NUM = density;
    const R = 1;

    // Generate points: spherical distribution + a few drifting nodes
    for (let i = 0; i < NUM; i++) {
      if (sphere && i < NUM * 0.8) {
        // Fibonacci sphere
        const phi = Math.acos(1 - 2 * (i + 0.5) / (NUM * 0.8));
        const theta = Math.PI * (1 + Math.sqrt(5)) * (i + 0.5);
        points.push({
          x: R * Math.cos(theta) * Math.sin(phi),
          y: R * Math.sin(theta) * Math.sin(phi),
          z: R * Math.cos(phi),
          drift: 0,
          ox: 0, oy: 0, oz: 0,
          phase: Math.random() * Math.PI * 2
        });
      } else {
        // Loose drifters
        const theta = Math.random() * Math.PI * 2;
        const phi = Math.acos(2 * Math.random() - 1);
        const r = R * (1.0 + Math.random() * 0.30);
        points.push({
          x: r * Math.cos(theta) * Math.sin(phi),
          y: r * Math.sin(theta) * Math.sin(phi),
          z: r * Math.cos(phi),
          drift: 1,
          ox: 0, oy: 0, oz: 0,
          phase: Math.random() * Math.PI * 2
        });
      }
    }

    // Pre-compute neighbor connections for the sphere
    const connections = [];
    for (let i = 0; i < points.length; i++) {
      for (let j = i + 1; j < points.length; j++) {
        const dx = points[i].x - points[j].x;
        const dy = points[i].y - points[j].y;
        const dz = points[i].z - points[j].z;
        const d = Math.sqrt(dx * dx + dy * dy + dz * dz);
        if (d < 0.58) connections.push([i, j, d]);
      }
    }

    const resize = () => {
      const rect = canvas.getBoundingClientRect();
      W = rect.width;
      H = rect.height;
      canvas.width = W * dpr;
      canvas.height = H * dpr;
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    };

    const onMouse = (e) => {
      if (!mouseParallax || reduceMotion) return;
      const rect = canvas.getBoundingClientRect();
      stateRef.current.mx = ((e.clientX - rect.left) / rect.width - 0.5) * 2;
      stateRef.current.my = ((e.clientY - rect.top) / rect.height - 0.5) * 2;
    };

    const onScroll = () => {
      if (!scrollReact) return;
      const rect = canvas.getBoundingClientRect();
      // 0 when canvas at top of viewport, ~1 when scrolled out below
      const p = 1 - Math.max(0, Math.min(1, (rect.top + rect.height) / (window.innerHeight + rect.height)));
      stateRef.current.scroll = p;
    };

    // Pause raf when offscreen
    const visibilityObserver = new IntersectionObserver(
      (entries) => {
        const e = entries[0];
        const wasVisible = stateRef.current.visible;
        stateRef.current.visible = e.isIntersecting;
        if (!wasVisible && e.isIntersecting) {
          t0 = performance.now();
          raf = requestAnimationFrame(draw);
        }
      },
      { threshold: 0.01 }
    );
    visibilityObserver.observe(canvas);

    resize();
    window.addEventListener("resize", resize);
    window.addEventListener("mousemove", onMouse, { passive: true });
    window.addEventListener("scroll", onScroll, { passive: true });
    onScroll();

    let t0 = performance.now();
    const draw = (t) => {
      if (!stateRef.current.visible) {
        raf = null;
        return;
      }
      const dt = Math.min((t - t0) / 1000, 0.05);
      t0 = t;
      const s = stateRef.current;
      // Auto rotate
      s.ry += dt * (reduceMotion ? 0 : 0.16);
      // Smooth scroll-driven tilt
      s.scrollLerp += (s.scroll - s.scrollLerp) * 0.08;
      // Smooth tilt to mouse
      s.tx += (s.mx * 0.25 - s.tx) * 0.05;
      s.ty += (s.my * 0.25 - s.ty) * 0.05;
      const rotY = s.ry + s.tx + s.scrollLerp * 0.6;
      const rotX = -0.25 + s.ty * 0.4 - s.scrollLerp * 0.35;

      const cy = Math.cos(rotY),sy = Math.sin(rotY);
      const cx = Math.cos(rotX),sx = Math.sin(rotX);

      const cw = W / 2;
      const ch = H / 2;
      const scale = Math.min(W, H) * 0.32;

      ctx.clearRect(0, 0, W, H);

      // Project all points
      const proj = points.map((p, i) => {
        // Drift offset
        if (p.drift) {
          const f = 0.35;
          const ox = Math.sin(t / 2000 + p.phase) * 0.04;
          const oy = Math.cos(t / 1800 + p.phase * 1.3) * 0.04;
          const oz = Math.sin(t / 2200 + p.phase * 0.7) * 0.04;
          p.ox = ox;p.oy = oy;p.oz = oz;
        }
        let X = p.x + (p.ox || 0);
        let Y = p.y + (p.oy || 0);
        let Z = p.z + (p.oz || 0);

        // Rotate Y axis
        let x1 = X * cy + Z * sy;
        let z1 = -X * sy + Z * cy;
        // Rotate X axis
        let y1 = Y * cx - z1 * sx;
        let z2 = Y * sx + z1 * cx;

        const persp = 1 / (2.4 - z2);
        const sx2 = cw + x1 * scale * persp * 2;
        const sy2 = ch + y1 * scale * persp * 2;
        const depth = (z2 + 1) / 2; // 0..1
        return { x: sx2, y: sy2, z: z2, depth };
      });

      // Draw connections
      for (const [i, j, d0] of connections) {
        const a = proj[i],b = proj[j];
        const avgDepth = (a.depth + b.depth) / 2;
        const alpha = Math.pow(avgDepth, 1.5) * 0.6;
        ctx.strokeStyle = `oklch(0.65 0.18 295 / ${alpha * 0.45})`;
        ctx.lineWidth = 0.7 * avgDepth;
        ctx.beginPath();
        ctx.moveTo(a.x, a.y);
        ctx.lineTo(b.x, b.y);
        ctx.stroke();
      }

      // Draw nodes (sorted back to front for glow halos)
      const order = proj.map((_, i) => i).sort((i, j) => proj[i].z - proj[j].z);
      for (const i of order) {
        const p = proj[i];
        const isDrift = points[i].drift;
        const baseR = (isDrift ? 1.5 : 1.3) + p.depth * 1.4;
        // Halo — only render for closer/brighter nodes (perf)
        if (p.depth > 0.35) {
          const haloAlpha = Math.pow(p.depth, 2.2) * 0.5 * glow;
          const haloR = baseR * 5;
          const grad = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, haloR);
          grad.addColorStop(0, `oklch(0.72 0.2 295 / ${haloAlpha})`);
          grad.addColorStop(1, `oklch(0.72 0.2 295 / 0)`);
          ctx.fillStyle = grad;
          ctx.beginPath();
          ctx.arc(p.x, p.y, haloR, 0, Math.PI * 2);
          ctx.fill();
        }

        // Core node
        const coreAlpha = 0.4 + p.depth * 0.6;
        ctx.fillStyle = isDrift ?
        `oklch(0.78 0.2 295 / ${coreAlpha})` :
        `oklch(${0.6 + p.depth * 0.3} 0.05 290 / ${coreAlpha})`;
        ctx.beginPath();
        ctx.arc(p.x, p.y, baseR, 0, Math.PI * 2);
        ctx.fill();

        // Bright core for front nodes
        if (p.depth > 0.75) {
          ctx.fillStyle = `oklch(0.95 0.05 290 / ${(p.depth - 0.75) * 2.5})`;
          ctx.beginPath();
          ctx.arc(p.x, p.y, baseR * 0.4, 0, Math.PI * 2);
          ctx.fill();
        }
      }

      raf = requestAnimationFrame(draw);
    };
    raf = requestAnimationFrame(draw);

    return () => {
      cancelAnimationFrame(raf);
      visibilityObserver.disconnect();
      window.removeEventListener("resize", resize);
      window.removeEventListener("mousemove", onMouse);
      window.removeEventListener("scroll", onScroll);
    };
  }, [density, glow, sphere, mouseParallax, scrollReact]);

  return <canvas ref={canvasRef} />;
};

window.NetworkCanvas = NetworkCanvas;