/* =========================================================================
   SkillPlay — skill tiles as real, physics-driven building blocks.
   • Grab a tile anywhere and it hangs from that exact point and swings like a
     real object (Verlet pendulum — grab a corner and it dangles correctly).
   • Release: it falls under gravity with its momentum, tumbles, lands on the
     floor or on other tiles, and rights itself as it settles.
   • Default = three tidy towers, one per group. Scoped to this section.
   The simulation writes transforms straight to the DOM each frame (no React
   re-render in the loop) so motion stays smooth and predictable.
   ========================================================================= */
function SkillPlay({ groups, t }) {
  const { useRef, useLayoutEffect, useEffect } = React;
  const areaRef = useRef(null);
  const elRefs = useRef({});
  const baseRefs = useRef([]);
  const sizeRef = useRef({ w: 700, h: 480 });
  const HRef = useRef(38);
  const zRef = useRef(1);
  const bodiesRef = useRef([]);
  const pointerRef = useRef({ x: 0, y: 0 });
  const heldRef = useRef(null);
  const rafRef = useRef(0);
  const lastTsRef = useRef(0);

  const PAD = 12;
  const GRAV = 2600;     // px/s²
  const FRICT = 0.992;   // pendulum air drag
  const REST_FR = 0.86;  // horizontal friction after a throw
  const ANG_DAMP = 7;    // how fast a resting tile rights itself

  const clampX = (x, w, tw) => Math.max(PAD, Math.min(w - PAD - tw, x));
  const hOverlap = (a, b) => Math.min(a.x + a.w, b.x + b.w) - Math.max(a.x, b.x) > 6;

  // pairwise AABB separation — no two tiles may interpenetrate.
  // The held tile is kinematic: it pushes others, never moves itself.
  function resolveCollisions(bodies, w) {
    let moved = false;
    for (let iter = 0; iter < 4; iter++) {
      let any = false;
      for (let i = 0; i < bodies.length; i++) {
        for (let j = i + 1; j < bodies.length; j++) {
          const a = bodies[i], b = bodies[j];
          const ox = Math.min(a.x + a.w, b.x + b.w) - Math.max(a.x, b.x);
          const oy = Math.min(a.y + a.h, b.y + b.h) - Math.max(a.y, b.y);
          if (ox <= 0.5 || oy <= 0.5) continue;
          any = true; moved = true;
          if (oy <= ox) {
            // separate vertically — the upper tile rests on the lower one
            const aUp = (a.y + a.h / 2) <= (b.y + b.h / 2);
            const up = aUp ? a : b, down = aUp ? b : a;
            if (up.held) { down.y += oy; if (down.vy < 0) down.vy = 0; }
            else if (down.held) { up.y -= oy; if (up.vy > 0) up.vy = 0; }
            else { up.y -= oy; if (up.vy > 0) up.vy = 0; }
          } else {
            // separate horizontally — tiles shoulder each other aside
            const aLeft = (a.x + a.w / 2) <= (b.x + b.w / 2);
            const L = aLeft ? a : b, R = aLeft ? b : a;
            if (L.held) { R.x = clampX(R.x + ox, w, R.w); R.vx = Math.max(R.vx, 40); }
            else if (R.held) { L.x = clampX(L.x - ox, w, L.w); L.vx = Math.min(L.vx, -40); }
            else {
              L.x = clampX(L.x - ox / 2, w, L.w);
              R.x = clampX(R.x + ox / 2, w, R.w);
              const t = (L.vx + R.vx) / 2; L.vx = t - 8; R.vx = t + 8;
            }
          }
        }
      }
      if (!any) break;
    }
    return moved;
  }

  function supportTop(b, bodies) {
    let top = sizeRef.current.h - PAD - b.h;
    for (const o of bodies) {
      if (o === b || o.held) continue;
      if (o.y > b.y + 1 && hOverlap(b, o)) top = Math.min(top, o.y - b.h);
    }
    return top;
  }

  // write a body's current state to its DOM node
  function paint(b) {
    const node = elRefs.current[b.id];
    if (!node) return;
    node.style.left = b.x + 'px';
    node.style.top = b.y + 'px';
    node.style.zIndex = b.z;
    node.style.transformOrigin = b.ox + 'px ' + b.oy + 'px';
    node.style.transform = 'rotate(' + (b.angle * 180 / Math.PI) + 'deg)';
    node.classList.toggle('dragging', !!b.held);
  }

  // tidy default: one tower per group, columns centred & never overlapping
  function tidy() {
    const w = sizeRef.current.w, h = sizeRef.current.h, H = HRef.current;
    const bodies = bodiesRef.current;
    const n = groups.length;
    const floorTop = h - PAD - H;
    const gap = 16;
    const colW = [];
    bodies.forEach((b) => { colW[b.gi] = Math.max(colW[b.gi] || 0, b.w); });
    const total = colW.reduce((a, c) => a + (c || 0), 0) + gap * (n - 1);
    const avail = w - 2 * PAD;
    let cx = PAD + Math.max(0, (avail - total) / 2);
    const centers = [];
    for (let gi = 0; gi < n; gi++) { centers[gi] = cx + (colW[gi] || 0) / 2; cx += (colW[gi] || 0) + gap; }
    const counts = {};
    bodies.forEach((b) => {
      const idx = (counts[b.gi] = (counts[b.gi] ?? -1) + 1);
      b.x = clampX(centers[b.gi] - b.w / 2, w, b.w);
      b.y = floorTop - idx * H;
      b.vx = 0; b.vy = 0; b.angle = 0; b.omega = 0;
      b.held = false; b.ox = b.w / 2; b.oy = b.h / 2;
      paint(b);
    });
    baseRefs.current.forEach((node, gi) => { if (node) node.style.left = centers[gi] + 'px'; });
  }

  // ---- the simulation step ---------------------------------------------
  function step(ts) {
    const last = lastTsRef.current || ts;
    let dt = (ts - last) / 1000;
    lastTsRef.current = ts;
    if (dt > 0.05) dt = 0.05;
    const { w } = sizeRef.current;
    const bodies = bodiesRef.current;
    let active = false;

    const held = heldRef.current;
    if (held) {
      active = true;
      const p = pointerRef.current;
      const vx = (held.cx - held.pcx) * FRICT;
      const vy = (held.cy - held.pcy) * FRICT;
      held.pcx = held.cx; held.pcy = held.cy;
      held.cx += vx;
      held.cy += vy + GRAV * dt * dt;
      const dx = held.cx - p.x, dy = held.cy - p.y;
      const dist = Math.hypot(dx, dy) || 0.0001;
      const corr = (dist - held.L) / dist;
      held.cx -= dx * corr; held.cy -= dy * corr;
      held.angle = Math.atan2(held.cy - p.y, held.cx - p.x) - held.phi;
      held.x = clampX(p.x - held.gx, w, held.w);
      held.y = p.y - held.gy;
      held.ox = held.gx; held.oy = held.gy;
      paint(held);
    }

    const dynamic = bodies.filter((b) => !b.held).sort((a, b) => b.y - a.y);
    for (const b of dynamic) {
      const rest = supportTop(b, bodies);
      const airborne = b.y < rest - 0.5;
      if (airborne) {
        b.vy += GRAV * dt;
        b.y += b.vy * dt;
        if (b.y >= rest) { b.y = rest; b.vy = 0; }
        active = true;
      } else { b.y = rest; b.vy = 0; }

      if (Math.abs(b.vx) > 1) {
        b.x = clampX(b.x + b.vx * dt, w, b.w);
        b.vx *= REST_FR;
        active = true;
      } else b.vx = 0;

      if (!airborne) {
        const k = Math.min(1, ANG_DAMP * dt);
        b.angle += (0 - b.angle) * k;
        b.ox += (b.w / 2 - b.ox) * k;
        b.oy += (b.h / 2 - b.oy) * k;
        b.omega = 0;
        if (Math.abs(b.angle) > 0.002) active = true; else b.angle = 0;
      } else {
        b.angle += b.omega * dt;
        b.omega *= 0.99;
      }
    }

    // hard rule: tiles never interpenetrate — shoulder each other aside
    if (resolveCollisions(bodies, w)) active = true;
    for (const b of bodies) { if (!b.held) paint(b); }

    if (active) rafRef.current = requestAnimationFrame(step);
    else { rafRef.current = 0; lastTsRef.current = 0; }
  }

  function startLoop() {
    if (!rafRef.current) { lastTsRef.current = 0; rafRef.current = requestAnimationFrame(step); }
  }

  // ---- pointer interaction ---------------------------------------------
  function onDown(e, body) {
    e.preventDefault();
    const node = elRefs.current[body.id];
    if (!node) return;
    const ar = areaRef.current.getBoundingClientRect();
    // grab point in the tile's local (unrotated) frame
    const gx = (e.clientX - ar.left) - body.x;
    const gy = (e.clientY - ar.top) - body.y;
    pointerRef.current = { x: e.clientX - ar.left, y: e.clientY - ar.top };
    body.held = true;
    body.z = ++zRef.current;
    body.gx = gx; body.gy = gy;
    body.cx = body.x + body.w / 2;
    body.cy = body.y + body.h / 2;
    body.pcx = body.cx; body.pcy = body.cy;
    body.L = Math.hypot(body.w / 2 - gx, body.h / 2 - gy) || 0.0001;
    body.phi = Math.atan2(body.h / 2 - gy, body.w / 2 - gx);
    heldRef.current = body;
    if (node.setPointerCapture && e.pointerId != null) { try { node.setPointerCapture(e.pointerId); } catch (_) {} }
    window.addEventListener('pointermove', onMove);
    window.addEventListener('pointerup', onUp);
    window.addEventListener('pointercancel', onUp);
    startLoop();
  }
  function onMove(e) {
    const ar = areaRef.current.getBoundingClientRect();
    pointerRef.current = { x: e.clientX - ar.left, y: e.clientY - ar.top };
    startLoop();
  }
  function onUp() {
    const b = heldRef.current;
    window.removeEventListener('pointermove', onMove);
    window.removeEventListener('pointerup', onUp);
    window.removeEventListener('pointercancel', onUp);
    if (b) {
      b.vx = (b.cx - b.pcx) * 60;
      b.vy = (b.cy - b.pcy) * 60;
      b.omega = Math.max(-12, Math.min(12, b.vx / (b.L || 1)));
      b.held = false;
    }
    heldRef.current = null;
    startLoop();
  }

  const reset = () => { tidy(); };

  // ---- measure once, then lay out tidy towers --------------------------
  function measure() {
    const el = areaRef.current;
    if (!el) return;
    const r = el.getBoundingClientRect();
    sizeRef.current = { w: r.width, h: r.height };
    let H = HRef.current;
    bodiesRef.current.forEach((b) => {
      const node = elRefs.current[b.id];
      if (node) { H = node.offsetHeight; b.w = node.offsetWidth; b.h = node.offsetHeight; }
    });
    HRef.current = H;
  }

  useLayoutEffect(() => {
    measure();
    tidy();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  useEffect(() => {
    let raf = 0;
    function onResize() {
      cancelAnimationFrame(raf);
      raf = requestAnimationFrame(() => {
        const prev = sizeRef.current.w;
        const el = areaRef.current;
        if (!el) return;
        if (Math.abs(el.getBoundingClientRect().width - prev) < 2) return;
        measure();
        tidy();
      });
    }
    window.addEventListener('resize', onResize);
    return () => { window.removeEventListener('resize', onResize); cancelAnimationFrame(raf); };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  useEffect(() => () => { if (rafRef.current) cancelAnimationFrame(rafRef.current); }, []);

  // build bodies once (stable across renders)
  if (!bodiesRef.current.length) {
    const out = [];
    groups.forEach((g, gi) => g.items.forEach((label, ii) => {
      out.push({ id: gi + '-' + ii, gi, label, x: 16 + ii * 4, y: 12 + ii * 4,
        w: 130, h: 38, vx: 0, vy: 0, angle: 0, omega: 0, z: ii + 1,
        held: false, ox: 65, oy: 19 });
    }));
    bodiesRef.current = out;
  }

  const bodies = bodiesRef.current;
  return (
    <div className="skill-play">
      <div className="sp-toolbar">
        <span className="sp-hint">{t.faehigkeiten.hint}</span>
        <button className="sp-reset" onClick={reset}>{t.faehigkeiten.reshuffle}</button>
      </div>
      <div className="sp-area" ref={areaRef}>
        {groups.map((g, gi) => (
          <div key={'b' + gi} ref={(n) => { baseRefs.current[gi] = n; }} className="sp-base">{g.label}</div>
        ))}
        {bodies.map((b) => (
          <div key={b.id}
               ref={(n) => { elRefs.current[b.id] = n; }}
               className={'sp-token g' + b.gi}
               style={{ left: b.x, top: b.y, zIndex: b.z }}
               onPointerDown={(e) => onDown(e, b)}>
            <span className="sp-label">{b.label}</span>
          </div>
        ))}
      </div>
    </div>
  );
}
window.SkillPlay = SkillPlay;
