// ParticleNetwork.jsx — lightweight Canvas 2D network
// Agents as nodes, data flowing between them. Mouse-reactive + scroll-reactive.
// Keeps particle count modest for mobile performance.

function ParticleNetwork({
  density = 1,
  accent = '#ff5a1f',
  accent2 = '#ffa726',
  ink = '#f5f2ec',
  speed = 1,
  showLabels = true,
  dark = true,
}) {
  const canvasRef = React.useRef(null);
  const wrapRef = React.useRef(null);
  const stateRef = React.useRef({
    nodes: [],
    mouse: { x: -1000, y: -1000, active: false },
    scroll: 0,
    tick: 0,
    rafId: 0,
    size: { w: 0, h: 0, dpr: 1 },
  });

  // Key tool labels (drawn near anchor nodes for "technical" feel)
  const LABELS = React.useMemo(() => [
    'n8n', 'Claude', 'HubSpot', 'Salesforce', 'Apollo', 'Supabase',
    'Airtable', 'Close', 'Zoominfo', 'Vercel',
  ], []);

  const init = React.useCallback(() => {
    const canvas = canvasRef.current;
    const wrap = wrapRef.current;
    if (!canvas || !wrap) return;
    const rect = wrap.getBoundingClientRect();
    const dpr = Math.min(window.devicePixelRatio || 1, 2);
    canvas.width = rect.width * dpr;
    canvas.height = rect.height * dpr;
    canvas.style.width = rect.width + 'px';
    canvas.style.height = rect.height + 'px';
    stateRef.current.size = { w: rect.width, h: rect.height, dpr };

    // Node count based on area, capped for mobile
    const area = rect.width * rect.height;
    const target = Math.min(90, Math.max(28, Math.round(area / 14000 * density)));

    const nodes = [];
    for (let i = 0; i < target; i++) {
      const labelIdx = i < LABELS.length ? i : -1;
      nodes.push({
        x: Math.random() * rect.width,
        y: Math.random() * rect.height,
        z: Math.random() * 0.8 + 0.2, // depth 0.2..1
        vx: (Math.random() - 0.5) * 0.25,
        vy: (Math.random() - 0.5) * 0.25,
        r: Math.random() * 1.8 + 1.0,
        label: labelIdx >= 0 ? LABELS[labelIdx] : null,
        pulse: Math.random() * Math.PI * 2,
        pulseSpeed: 0.01 + Math.random() * 0.02,
      });
    }

    // Add a few "packet" particles that travel between nodes
    stateRef.current.nodes = nodes;
    stateRef.current.packets = Array.from({ length: 8 }, () => spawnPacket(nodes));
  }, [density, LABELS]);

  function spawnPacket(nodes) {
    const from = nodes[(Math.random() * nodes.length) | 0];
    const to = nodes[(Math.random() * nodes.length) | 0];
    return { from, to, t: 0, speed: 0.004 + Math.random() * 0.008 };
  }

  const render = React.useCallback(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    const { w, h, dpr } = stateRef.current.size;
    const { nodes, mouse } = stateRef.current;
    const packets = stateRef.current.packets || [];

    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    ctx.clearRect(0, 0, w, h);

    stateRef.current.tick += 1;

    // Update nodes
    for (const n of nodes) {
      n.x += n.vx * speed;
      n.y += n.vy * speed;
      n.pulse += n.pulseSpeed;

      // Mouse repel (subtle)
      if (mouse.active) {
        const dx = n.x - mouse.x;
        const dy = n.y - mouse.y;
        const d2 = dx * dx + dy * dy;
        if (d2 < 180 * 180) {
          const d = Math.sqrt(d2) || 1;
          const f = (180 - d) / 180 * 0.6;
          n.x += (dx / d) * f;
          n.y += (dy / d) * f;
        }
      }

      if (n.x < -20) n.x = w + 20;
      if (n.x > w + 20) n.x = -20;
      if (n.y < -20) n.y = h + 20;
      if (n.y > h + 20) n.y = -20;
    }

    // Links
    const maxDist = 130;
    for (let i = 0; i < nodes.length; i++) {
      for (let j = i + 1; j < nodes.length; j++) {
        const a = nodes[i], b = nodes[j];
        const dx = a.x - b.x, dy = a.y - b.y;
        const d2 = dx * dx + dy * dy;
        if (d2 < maxDist * maxDist) {
          const d = Math.sqrt(d2);
          const alpha = (1 - d / maxDist) * 0.35 * Math.min(a.z, b.z);
          ctx.strokeStyle = hexWithAlpha(accent, alpha);
          ctx.lineWidth = 0.6;
          ctx.beginPath();
          ctx.moveTo(a.x, a.y);
          ctx.lineTo(b.x, b.y);
          ctx.stroke();
        }
      }
    }

    // Links to mouse
    if (mouse.active) {
      for (const n of nodes) {
        const dx = n.x - mouse.x, dy = n.y - mouse.y;
        const d2 = dx * dx + dy * dy;
        if (d2 < 180 * 180) {
          const d = Math.sqrt(d2);
          const alpha = (1 - d / 180) * 0.7;
          ctx.strokeStyle = hexWithAlpha(accent2, alpha);
          ctx.lineWidth = 1;
          ctx.beginPath();
          ctx.moveTo(n.x, n.y);
          ctx.lineTo(mouse.x, mouse.y);
          ctx.stroke();
        }
      }
    }

    // Nodes
    for (const n of nodes) {
      const pulseScale = 1 + Math.sin(n.pulse) * 0.25;
      const r = n.r * pulseScale * n.z;
      // Glow
      const g = ctx.createRadialGradient(n.x, n.y, 0, n.x, n.y, r * 8);
      g.addColorStop(0, hexWithAlpha(accent2, 0.35 * n.z));
      g.addColorStop(1, hexWithAlpha(accent2, 0));
      ctx.fillStyle = g;
      ctx.beginPath();
      ctx.arc(n.x, n.y, r * 8, 0, Math.PI * 2);
      ctx.fill();

      // Core
      ctx.fillStyle = hexWithAlpha(accent, 0.8 + 0.2 * n.z);
      ctx.beginPath();
      ctx.arc(n.x, n.y, r, 0, Math.PI * 2);
      ctx.fill();

      // Label
      if (showLabels && n.label) {
        ctx.font = '10px "JetBrains Mono", ui-monospace, monospace';
        ctx.fillStyle = 'rgba(245,242,236,0.5)';
        ctx.fillText(n.label.toUpperCase(), n.x + 10, n.y + 4);
      }
    }

    // Packets along lines
    for (const p of packets) {
      p.t += p.speed * speed;
      if (p.t >= 1) {
        const next = spawnPacket(nodes);
        Object.assign(p, next);
        continue;
      }
      const x = p.from.x + (p.to.x - p.from.x) * p.t;
      const y = p.from.y + (p.to.y - p.from.y) * p.t;
      // Trail
      ctx.strokeStyle = hexWithAlpha(accent2, 0.55);
      ctx.lineWidth = 1.6;
      const tailT = Math.max(0, p.t - 0.08);
      const tx = p.from.x + (p.to.x - p.from.x) * tailT;
      const ty = p.from.y + (p.to.y - p.from.y) * tailT;
      ctx.beginPath();
      ctx.moveTo(tx, ty);
      ctx.lineTo(x, y);
      ctx.stroke();
      // Head
      ctx.fillStyle = '#ffffff';
      ctx.beginPath(); ctx.arc(x, y, 2.2, 0, Math.PI * 2); ctx.fill();
      ctx.fillStyle = hexWithAlpha(accent2, 0.9);
      ctx.beginPath(); ctx.arc(x, y, 3.6, 0, Math.PI * 2); ctx.fill();
    }

    stateRef.current.rafId = requestAnimationFrame(render);
  }, [accent, accent2, speed, showLabels, dark, ink]);

  React.useEffect(() => {
    init();
    stateRef.current.rafId = requestAnimationFrame(render);

    const onResize = () => { init(); };
    const wrap = wrapRef.current;
    const onMove = (e) => {
      const rect = wrap.getBoundingClientRect();
      stateRef.current.mouse.x = e.clientX - rect.left;
      stateRef.current.mouse.y = e.clientY - rect.top;
      stateRef.current.mouse.active = true;
    };
    const onLeave = () => { stateRef.current.mouse.active = false; };

    window.addEventListener('resize', onResize);
    window.addEventListener('mousemove', onMove);
    wrap.addEventListener('mouseleave', onLeave);

    return () => {
      cancelAnimationFrame(stateRef.current.rafId);
      window.removeEventListener('resize', onResize);
      window.removeEventListener('mousemove', onMove);
      wrap.removeEventListener('mouseleave', onLeave);
    };
  }, [init, render]);

  return (
    <div ref={wrapRef} style={{ position: 'absolute', inset: 0, overflow: 'hidden' }}>
      <canvas ref={canvasRef} style={{ display: 'block', width: '100%', height: '100%' }} />
    </div>
  );
}

function hexWithAlpha(hex, a) {
  // supports #rrggbb
  if (!hex.startsWith('#')) return hex;
  const r = parseInt(hex.slice(1,3), 16);
  const g = parseInt(hex.slice(3,5), 16);
  const b = parseInt(hex.slice(5,7), 16);
  return `rgba(${r},${g},${b},${a})`;
}

window.ParticleNetwork = ParticleNetwork;
