// Atomic physics for the "Beyond" section.
// Pure functions operating on plain particle objects:
//   { x, y, vx, vy, r, mass, hue }
// No React, no DOM. Loaded as a global before beyond.jsx.

const BeyondPhysics = (() => {
  const BROWNIAN = 0.07;
  const CENTER_PULL = 0;
  const DAMPING = 0.999;
  const WALL_RESTITUTION = 0.98;
  const COLLISION_RESTITUTION = 0.98;
  const MAX_SPEED = 3.4;

  // Advance simulation by one frame. `dragged` is the index being held by the
  // user (-1 if none); that particle's velocity is driven externally.
  function step(particles, bounds, dragged) {
    const { w, h } = bounds;
    const cx = w / 2;
    const cy = h / 2;

    for (let i = 0; i < particles.length; i++) {
      if (i === dragged) continue;
      const p = particles[i];

      p.vx += (Math.random() - 0.5) * BROWNIAN;
      p.vy += (Math.random() - 0.5) * BROWNIAN;

      p.vx += (cx - p.x) * CENTER_PULL;
      p.vy += (cy - p.y) * CENTER_PULL;

      p.vx *= DAMPING;
      p.vy *= DAMPING;

      const sp = Math.hypot(p.vx, p.vy);
      if (sp > MAX_SPEED) {
        const k = MAX_SPEED / sp;
        p.vx *= k;
        p.vy *= k;
      }

      p.x += p.vx;
      p.y += p.vy;
    }

    for (const p of particles) {
      if (p.x - p.r < 0) {
        p.x = p.r;
        p.vx = Math.abs(p.vx) * WALL_RESTITUTION;
      } else if (p.x + p.r > w) {
        p.x = w - p.r;
        p.vx = -Math.abs(p.vx) * WALL_RESTITUTION;
      }
      if (p.y - p.r < 0) {
        p.y = p.r;
        p.vy = Math.abs(p.vy) * WALL_RESTITUTION;
      } else if (p.y + p.r > h) {
        p.y = h - p.r;
        p.vy = -Math.abs(p.vy) * WALL_RESTITUTION;
      }
    }

    for (let i = 0; i < particles.length; i++) {
      for (let j = i + 1; j < particles.length; j++) {
        resolveCollision(particles[i], particles[j]);
      }
    }
  }

  // Mass-weighted elastic collision along the contact normal.
  function resolveCollision(a, b) {
    const dx = b.x - a.x;
    const dy = b.y - a.y;
    const dist = Math.hypot(dx, dy) || 0.0001;
    const minDist = a.r + b.r;
    if (dist >= minDist) return;

    const overlap = minDist - dist;
    const nx = dx / dist;
    const ny = dy / dist;
    const totalMass = a.mass + b.mass;
    const aShare = b.mass / totalMass;
    const bShare = a.mass / totalMass;
    a.x -= nx * overlap * aShare;
    a.y -= ny * overlap * aShare;
    b.x += nx * overlap * bShare;
    b.y += ny * overlap * bShare;

    const dvx = b.vx - a.vx;
    const dvy = b.vy - a.vy;
    const vn = dvx * nx + dvy * ny;
    if (vn > 0) return;

    const j = -(1 + COLLISION_RESTITUTION) * vn / (1 / a.mass + 1 / b.mass);
    const jx = j * nx;
    const jy = j * ny;
    a.vx -= jx / a.mass;
    a.vy -= jy / a.mass;
    b.vx += jx / b.mass;
    b.vy += jy / b.mass;
  }

  function hitTest(particles, x, y) {
    for (let i = particles.length - 1; i >= 0; i--) {
      const p = particles[i];
      const dx = x - p.x;
      const dy = y - p.y;
      if (dx * dx + dy * dy <= p.r * p.r) return i;
    }
    return -1;
  }

  return { step, hitTest };
})();

window.BeyondPhysics = BeyondPhysics;
