// ===================== Bubble physics =====================
const { useState, useEffect, useRef, useMemo, useCallback } = React;

function BubbleStage({ items }) {
  const stageRef = useRef(null);
  const animRef = useRef(0);
  const mouseRef = useRef({ x: -9999, y: -9999, active: false });
  const [size, setSize] = useState({ w: 1000, h: 520 });

  // initialise bubbles
  const bubblesRef = useRef(null);
  if (bubblesRef.current === null) {
    bubblesRef.current = items.map((label, i) => {
      const r = 64 + (label.length > 12 ? 18 : 0) + Math.random() * 14;
      return {
        label,
        r,
        x: 0, y: 0, vx: (Math.random() - .5) * 0.4, vy: (Math.random() - .5) * 0.4,
        tone: i % 4,
      };
    });
  }

  // measure stage + seed positions
  useEffect(() => {
    const measure = () => {
      const el = stageRef.current; if (!el) return;
      const rect = el.getBoundingClientRect();
      setSize({ w: rect.width, h: rect.height });
      // place bubbles inside on first measure
      bubblesRef.current.forEach((b, i) => {
        if (b.x === 0 && b.y === 0) {
          b.x = b.r + Math.random() * (rect.width - b.r * 2);
          b.y = b.r + Math.random() * (rect.height - b.r * 2);
        }
      });
    };
    measure();
    const ro = new ResizeObserver(measure);
    if (stageRef.current) ro.observe(stageRef.current);
    return () => ro.disconnect();
  }, []);

  // mouse tracking (stage-local)
  useEffect(() => {
    const el = stageRef.current; if (!el) return;
    const onMove = (e) => {
      const r = el.getBoundingClientRect();
      mouseRef.current.x = e.clientX - r.left;
      mouseRef.current.y = e.clientY - r.top;
      mouseRef.current.active = true;
    };
    const onLeave = () => { mouseRef.current.active = false; };
    el.addEventListener('mousemove', onMove);
    el.addEventListener('mouseleave', onLeave);
    return () => { el.removeEventListener('mousemove', onMove); el.removeEventListener('mouseleave', onLeave); };
  }, []);

  // animation loop
  useEffect(() => {
    let last = performance.now();
    const tick = (now) => {
      const dt = Math.min(33, now - last); last = now;
      const W = size.w, H = size.h;
      const bs = bubblesRef.current;
      const m = mouseRef.current;

      for (let i = 0; i < bs.length; i++) {
        const b = bs[i];
        // gentle drift impulses
        b.vx += (Math.random() - .5) * 0.005;
        b.vy += (Math.random() - .5) * 0.005;

        // cursor repulsion
        if (m.active) {
          const dx = b.x - m.x, dy = b.y - m.y;
          const dist2 = dx * dx + dy * dy;
          const reach = 140;
          if (dist2 < reach * reach && dist2 > 0.01) {
            const d = Math.sqrt(dist2);
            const force = (1 - d / reach) * 0.6;
            b.vx += (dx / d) * force;
            b.vy += (dy / d) * force;
          }
        }

        // damping + cap
        b.vx *= 0.985; b.vy *= 0.985;
        const sp = Math.hypot(b.vx, b.vy);
        const max = 1.6;
        if (sp > max) { b.vx = (b.vx / sp) * max; b.vy = (b.vy / sp) * max; }

        b.x += b.vx * dt * 0.06;
        b.y += b.vy * dt * 0.06;

        // walls
        if (b.x < b.r) { b.x = b.r; b.vx *= -0.7; }
        if (b.x > W - b.r) { b.x = W - b.r; b.vx *= -0.7; }
        if (b.y < b.r) { b.y = b.r; b.vy *= -0.7; }
        if (b.y > H - b.r) { b.y = H - b.r; b.vy *= -0.7; }
      }

      // pairwise collisions
      for (let i = 0; i < bs.length; i++) {
        for (let j = i + 1; j < bs.length; j++) {
          const a = bs[i], c = bs[j];
          const dx = c.x - a.x, dy = c.y - a.y;
          const dist = Math.hypot(dx, dy) || 0.0001;
          const min = a.r + c.r;
          if (dist < min) {
            const overlap = (min - dist) / 2;
            const ux = dx / dist, uy = dy / dist;
            a.x -= ux * overlap; a.y -= uy * overlap;
            c.x += ux * overlap; c.y += uy * overlap;
            // exchange a little velocity
            const av = a.vx * ux + a.vy * uy;
            const cv = c.vx * ux + c.vy * uy;
            const diff = (cv - av) * 0.6;
            a.vx += ux * diff; a.vy += uy * diff;
            c.vx -= ux * diff; c.vy -= uy * diff;
          }
        }
      }

      // write to DOM
      const nodes = stageRef.current ? stageRef.current.children : null;
      if (nodes) {
        for (let i = 0; i < bs.length && i < nodes.length; i++) {
          const b = bs[i];
          nodes[i].style.transform = `translate3d(${b.x - b.r}px, ${b.y - b.r}px, 0)`;
        }
      }

      animRef.current = requestAnimationFrame(tick);
    };
    animRef.current = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(animRef.current);
  }, [size]);

  const tones = [
    'linear-gradient(135deg, #EFE6FB 0%, #E0CFF7 100%)',
    'linear-gradient(135deg, #E5F4EB 0%, #CDE9D8 100%)',
    'linear-gradient(135deg, #FCE6D6 0%, #F8CFAE 100%)',
    'linear-gradient(135deg, #FAD9E4 0%, #F2BBCE 100%)',
  ];
  const inks = ['#5A3F8C', '#2C5C42', '#8C4A20', '#8C2C4F'];

  return (
    <div ref={stageRef} className="bubble-stage">
      {bubblesRef.current.map((b, i) => (
        <div
          key={i}
          className="bubble"
          style={{
            width: b.r * 2, height: b.r * 2,
            background: tones[b.tone],
            color: inks[b.tone],
            fontSize: b.label.length > 12 ? 13 : 14,
            transform: `translate3d(${b.x - b.r}px, ${b.y - b.r}px, 0)`,
          }}
        >
          {b.label}
        </div>
      ))}
    </div>
  );
}

window.BubbleStage = BubbleStage;
