// "Beyond Code" section — atomic field of interests rendered on canvas.
// Circles bounce elastically (Brownian motion + center pull + collisions),
// each one trailing a floating DOM label that's always visible.

const FACETS = [
  { n: '/01', mid: <>Patagonia <em>native</em>.</>, bot: 'Bariloche · mountains', r: 32, accent: true },
  { n: '/02', mid: <><em>Psychology</em> & neuroscience.</>, bot: 'Reading · passion', r: 28, accent: false },
  { n: '/03', mid: <>Books of <em>all kinds</em>.</>, bot: 'Literature · essays', r: 34, accent: true },
  { n: '/04', mid: <>Physics at <em>30k ft</em>.</>, bot: 'Science · notes', r: 26, accent: false },
  { n: '/05', mid: <>Travel, <em>everywhere</em>.</>, bot: 'World', r: 30, accent: true },
  { n: '/06', mid: <>Writing at <em>odd</em> hours.</>, bot: 'Notes · essays', r: 25, accent: false },
  { n: '/07', mid: <>The <em>piano</em>, quietly.</>, bot: 'Music', r: 28, accent: false },
  { n: '/08', mid: <><em>Meditation</em>.</>, bot: 'Practice · daily', r: 26, accent: false },
  { n: '/09', mid: <><em>Coffee</em>, a lot.</>, bot: 'Ritual · morning', r: 23, accent: false },
  { n: '/10', mid: <>Family, <em>always</em>.</>, bot: 'Home', r: 36, accent: true },
  { n: '/11', mid: <>Hans Zimmer, <em>'90s cinema</em>.</>, bot: 'Cinema · scores', r: 30, accent: false },
];

function scaleRadius(r, w) {
  if (w < 520) return Math.max(16, r * 0.62);
  if (w < 800) return Math.max(20, r * 0.78);
  return r;
}

function readAccent() {
  const v = getComputedStyle(document.documentElement)
    .getPropertyValue('--accent').trim() || '#00ff88';
  return BeyondRender.hexToRgb(v);
}

function Beyond() {
  const containerRef = React.useRef(null);
  const canvasRef = React.useRef(null);
  const labelsRef = React.useRef([]);
  const particlesRef = React.useRef([]);
  const sizeRef = React.useRef({ w: 1200, h: 580 });
  const draggedRef = React.useRef(-1);
  const hoveredRef = React.useRef(-1);
  const dragOffsetRef = React.useRef({ x: 0, y: 0 });
  const lastPtrRef = React.useRef({ x: 0, y: 0, t: 0 });
  const accentRef = React.useRef([0, 255, 136]);

  const init = React.useCallback(() => {
    const { w, h } = sizeRef.current;
    const placed = [];
    for (let i = 0; i < FACETS.length; i++) {
      const r = scaleRadius(FACETS[i].r, w);
      let x = w / 2, y = h / 2;
      for (let attempt = 0; attempt < 80; attempt++) {
        x = r + Math.random() * (w - 2 * r);
        y = r + Math.random() * (h - 2 * r);
        if (placed.every(p => Math.hypot(p.x - x, p.y - y) > p.r + r + 12)) break;
      }
      const a = Math.random() * Math.PI * 2;
      const s = 1.2 + Math.random() * 0.6;
      placed.push({ x, y, vx: Math.cos(a) * s, vy: Math.sin(a) * s, r, mass: (r * r) / 800 });
    }
    particlesRef.current = placed;
  }, []);

  React.useEffect(() => {
    const el = containerRef.current;
    const cv = canvasRef.current;
    const dpr = Math.min(window.devicePixelRatio, 2);
    const onResize = () => {
      const rect = el.getBoundingClientRect();
      sizeRef.current = { w: rect.width, h: rect.height };
      cv.width = Math.round(rect.width * dpr);
      cv.height = Math.round(rect.height * dpr);
      cv.style.width = rect.width + 'px';
      cv.style.height = rect.height + 'px';
      if (particlesRef.current.length === 0) init();
    };
    onResize();
    accentRef.current = readAccent();
    const ro = new ResizeObserver(onResize);
    ro.observe(el);
    const acc = setInterval(() => { accentRef.current = readAccent(); }, 1000);
    return () => { ro.disconnect(); clearInterval(acc); };
  }, [init]);

  React.useEffect(() => {
    const ctx = canvasRef.current.getContext('2d');
    const dpr = Math.min(window.devicePixelRatio, 2);
    let raf;
    const tick = () => {
      const sz = sizeRef.current;
      BeyondPhysics.step(particlesRef.current, sz, draggedRef.current);
      BeyondRender.clear(ctx, dpr, sz.w, sz.h);
      BeyondRender.drawBonds(ctx, particlesRef.current, accentRef.current);
      BeyondRender.drawParticles(ctx, particlesRef.current, FACETS,
        hoveredRef.current, draggedRef.current, accentRef.current);
      syncLabels(sz);
      raf = requestAnimationFrame(tick);
    };
    tick();
    return () => cancelAnimationFrame(raf);
  }, []);

  const syncLabels = (sz) => {
    const cx = sz.w / 2, cy = sz.h / 2;
    for (let i = 0; i < FACETS.length; i++) {
      const el = labelsRef.current[i];
      const p = particlesRef.current[i];
      if (!el || !p) continue;
      let dx = p.x - cx, dy = p.y - cy;
      const d = Math.hypot(dx, dy) || 1;
      dx /= d; dy /= d;
      const offset = p.r + 16;
      const lx = p.x + dx * offset;
      const ly = p.y + dy * offset;
      const flipLeft = dx < 0;
      const align = flipLeft ? 'translate(-100%, -50%)' : 'translate(0, -50%)';
      el.style.transform = `translate3d(${lx}px, ${ly}px, 0) ${align}`;
      const flip = flipLeft ? '1' : '0';
      if (el.dataset.flip !== flip) el.dataset.flip = flip;
      const active = (i === hoveredRef.current || i === draggedRef.current) ? '1' : '0';
      if (el.dataset.active !== active) el.dataset.active = active;
    }
  };

  const onPointerDown = (e) => {
    const r = containerRef.current.getBoundingClientRect();
    const x = e.clientX - r.left, y = e.clientY - r.top;
    const i = BeyondPhysics.hitTest(particlesRef.current, x, y);
    if (i < 0) return;
    draggedRef.current = i;
    const p = particlesRef.current[i];
    dragOffsetRef.current = { x: p.x - x, y: p.y - y };
    lastPtrRef.current = { x, y, t: performance.now() };
    try { e.currentTarget.setPointerCapture(e.pointerId); } catch {}
  };

  const onPointerMove = (e) => {
    const r = containerRef.current.getBoundingClientRect();
    const x = e.clientX - r.left, y = e.clientY - r.top;
    const di = draggedRef.current;
    if (di >= 0) {
      const p = particlesRef.current[di];
      const now = performance.now();
      const dt = Math.max(8, now - lastPtrRef.current.t);
      p.x = x + dragOffsetRef.current.x;
      p.y = y + dragOffsetRef.current.y;
      p.vx = (x - lastPtrRef.current.x) * (16 / dt);
      p.vy = (y - lastPtrRef.current.y) * (16 / dt);
      lastPtrRef.current = { x, y, t: now };
    } else {
      const h = BeyondPhysics.hitTest(particlesRef.current, x, y);
      hoveredRef.current = h;
      containerRef.current.style.cursor = h >= 0 ? 'grab' : 'default';
    }
  };

  const onPointerUp = (e) => {
    if (draggedRef.current < 0) return;
    draggedRef.current = -1;
    try { e.currentTarget.releasePointerCapture(e.pointerId); } catch {}
    containerRef.current.style.cursor = 'default';
  };

  return (
    <section className="beyond" id="beyond" data-screen-label="05 Beyond">
      <div className="section-head">
        <div className="section-num">04 / Beyond Code</div>
        <h2 className="section-title">Off the <em>clock</em>.</h2>
        <div className="section-desc">Eleven atoms, one field.<br />Drag any of them.</div>
      </div>
      <div className="beyond-hint">
        <span>◉ Hover to highlight · drag to launch · they re-settle.</span>
        <button className="beyond-reset" onClick={init}>↻ Re-scatter</button>
      </div>
      <div
        className="atom-stage"
        ref={containerRef}
        onPointerDown={onPointerDown}
        onPointerMove={onPointerMove}
        onPointerUp={onPointerUp}
        onPointerCancel={onPointerUp}
        onPointerLeave={() => { hoveredRef.current = -1; }}
      >
        <canvas ref={canvasRef} className="atom-canvas" />
        {FACETS.map((f, i) => (
          <div
            key={i}
            ref={(el) => { labelsRef.current[i] = el; }}
            className="atom-label"
            data-flip="0"
            data-active="0"
          >
            <div className="atom-label-num">{f.n}</div>
            <div className="atom-label-mid">{f.mid}</div>
            <div className="atom-label-bot">{f.bot}</div>
          </div>
        ))}
      </div>
    </section>
  );
}

window.Beyond = Beyond;
