// swarm.jsx — Stark-flavored agent swarm renderer

function AgentSwarm() {
  const { time } = useTimeline();
  const canvasRef = React.useRef(null);
  const agents = useAgents();

  React.useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    ctx.clearRect(0, 0, W, H);
    drawSwarm(ctx, time, agents);
  }, [time, agents]);

  return (
    <canvas ref={canvasRef} width={W} height={H}
      style={{ position: 'absolute', inset: 0, width: W, height: H, imageRendering: 'pixelated' }}/>
  );
}

function drawSwarm(ctx, t, agents) {
  const { markAgents, floodAgents } = agents;

  const quantizeP = smoothstep(TL.quantizeStart, TL.quantizeEnd, t);
  const scatterP  = smoothstep(TL.scatterStart, TL.scatterEnd, t);
  const resolveP  = smoothstep(TL.resolveStart, TL.resolveEnd, t);
  const holdP     = smoothstep(TL.holdStart, TL.holdStart + 0.6, t);

  // Draw network lines BEFORE pixels so pixels sit on top
  if (t > TL.floodStart + 1 && t < TL.resolveEnd) {
    drawNetwork(ctx, t, markAgents, floodAgents);
  }

  for (const a of markAgents) {
    drawMarkAgent(ctx, a, t, { quantizeP, scatterP, resolveP, holdP });
  }

  if (t >= TL.floodStart - 0.1) {
    for (const a of floodAgents) {
      drawFloodAgent(ctx, a, t, { resolveP, holdP });
    }
  }
}

function smoothstep(a, b, x) {
  if (x <= a) return 0;
  if (x >= b) return 1;
  const t = (x - a) / (b - a);
  return t * t * (3 - 2 * t);
}
function lerp(a, b, t) { return a + (b - a) * t; }
function hexA(hex, a) {
  const aa = Math.max(0, Math.min(1, a));
  const n = parseInt(hex.slice(1), 16);
  const r = (n >> 16) & 255, g = (n >> 8) & 255, b = n & 255;
  return `rgba(${r},${g},${b},${aa})`;
}
function easeOutPow(t, p) { return 1 - Math.pow(1 - t, p); }

function agentColor(seed, opacity, hot = 0) {
  let color;
  if (seed < 0.65) color = ORANGE;
  else if (seed < 0.88) color = ORANGE_SOFT;
  else color = ORANGE_DIM;
  if (hot > 0.5) color = ORANGE_HOT;
  return hexA(color, opacity);
}

// ─── MARK AGENT ────────────────────────────────────────────────────────────
function drawMarkAgent(ctx, a, t, phases) {
  const { quantizeP, scatterP, resolveP, holdP } = phases;
  if (quantizeP <= 0.001) return;

  // Per-agent scatter progress, staggered
  const scatStart = TL.scatterStart + a.staggerDelay;
  const scatEnd = TL.scatterEnd;
  const myScatterP = smoothstep(scatStart, scatEnd, t);

  let x, y;
  if (resolveP < 0.001) {
    const theta = a.scatterAngle + a.swirl * myScatterP * 1.2;
    // Sharp launch: explosive easing (easeOutExpo-ish)
    const dist = a.scatterDist * easeOutPow(myScatterP, 2.2);
    x = a.x0 + MARK_CELL / 2 + Math.cos(theta) * dist;
    y = a.y0 + MARK_CELL / 2 + Math.sin(theta) * dist;
    // Very slight wobble pre-launch (mark vibrating before explosion)
    if (myScatterP < 0.1 && quantizeP > 0.3) {
      const pre = (quantizeP - 0.3) / 0.7;
      x += Math.sin(t * 60 + a.seed * 30) * 2 * pre;
      y += Math.cos(t * 60 + a.seed * 30) * 2 * pre;
    }
  } else {
    // Resolve: blend from scattered pos to target
    const theta = a.scatterAngle + a.swirl * 1.2;
    const dist = a.scatterDist;
    const sx = a.x0 + MARK_CELL / 2 + Math.cos(theta) * dist;
    const sy = a.y0 + MARK_CELL / 2 + Math.sin(theta) * dist;
    const r = Easing.easeInOutCubic(resolveP);
    x = lerp(sx, a.tx + a.tJitterX, r);
    y = lerp(sy, a.ty + a.tJitterY, r);
  }

  // Sparkle when locked
  if (holdP > 0.01) {
    x += Math.sin(t * 5 + a.seed * 30) * 0.6 * holdP;
  }

  const baseSize = MARK_CELL;
  const targetSize = WORDMARK_CELL - 2;
  let size;
  if (resolveP < 0.001) {
    size = baseSize * (0.3 + 0.7 * Easing.easeOutCubic(quantizeP));
  } else {
    size = lerp(baseSize, targetSize, Easing.easeInOutCubic(resolveP));
  }

  let op;
  if (resolveP < 0.001) {
    op = Easing.easeOutCubic(quantizeP);
    op *= 1 - 0.2 * myScatterP;
  } else {
    op = 0.85 + 0.15 * resolveP;
  }

  // Longer, brighter trail during scatter — streaks of light
  if (myScatterP > 0.05 && resolveP < 0.4) {
    const trailLen = 42 * myScatterP * (1 - resolveP) * (1 - myScatterP * 0.6);
    if (trailLen > 3) {
      const theta = a.scatterAngle + a.swirl * myScatterP * 1.2;
      // Gradient trail: dim tail → hot head
      ctx.strokeStyle = hexA(ORANGE, 0.32 * op);
      ctx.lineWidth = size * 0.75;
      ctx.lineCap = 'round';
      ctx.beginPath();
      ctx.moveTo(x, y);
      ctx.lineTo(x - Math.cos(theta) * trailLen, y - Math.sin(theta) * trailLen);
      ctx.stroke();
    }
  }

  // Hot-launch flash: very bright during the first 0.15 of scatter
  const hot = myScatterP > 0 && myScatterP < 0.25 ? 1 : 0;
  ctx.fillStyle = agentColor(a.seed, op, hot);
  ctx.fillRect(x - size / 2, y - size / 2, size, size);
}

// ─── FLOOD AGENT ───────────────────────────────────────────────────────────
function drawFloodAgent(ctx, a, t, phases) {
  const { resolveP, holdP } = phases;
  const localStart = TL.floodStart + a.spawnOffset;
  if (t < localStart) return;

  const life = t - localStart;
  const travelDur = TL.floodEnd - localStart;
  const approach = clamp(life / travelDur, 0, 1);

  const r = Easing.easeInOutCubic(approach);
  const wx = Math.cos(a.wanderAngle + t * 1.2 + a.seed * 6) * a.wanderAmp * (1 - approach);
  const wy = Math.sin(a.wanderAngle + t * 1.5 + a.seed * 6) * a.wanderAmp * (1 - approach);

  let x = lerp(a.x0, a.tx + a.tJitterX, r) + wx;
  let y = lerp(a.y0, a.ty + a.tJitterY, r) + wy;

  if (resolveP > 0) {
    const snap = Easing.easeInOutCubic(resolveP);
    x = lerp(x, a.tx + a.tJitterX, snap);
    y = lerp(y, a.ty + a.tJitterY, snap);
  }

  const ramp = clamp(life / 0.3, 0, 1);
  const size = a.size * (0.3 + 0.7 * ramp);
  let op = ramp * 0.95;

  // Longer directional trails — streaks
  if (approach < 0.9 && resolveP < 0.4) {
    const dx = (a.tx - a.x0);
    const dy = (a.ty - a.y0);
    const mag = Math.max(1, Math.hypot(dx, dy));
    const tx = dx / mag, ty = dy / mag;
    const trailLen = 34 * (1 - approach) * ramp;
    ctx.strokeStyle = hexA(ORANGE, 0.22 * op);
    ctx.lineWidth = size * 0.55;
    ctx.lineCap = 'round';
    ctx.beginPath();
    ctx.moveTo(x, y);
    ctx.lineTo(x - tx * trailLen, y - ty * trailLen);
    ctx.stroke();
  }

  if (holdP > 0.01) {
    x += Math.sin(t * 5 + a.seed * 30) * 0.5 * holdP;
  }

  ctx.fillStyle = agentColor(a.seed, op);
  ctx.fillRect(x - size / 2, y - size / 2, size, size);
}

// ─── Network lines — hairline connections between nearby agents ───────────
// Draws a small, random subset of short-range connections to give the
// swarm a "mesh intelligence" feel without bogging down the frame rate.
function drawNetwork(ctx, t, markAgents, floodAgents) {
  // Only during the mid-swarm window
  const p = interpolate(
    [TL.floodStart + 0.5, TL.floodStart + 1.8, TL.resolveStart, TL.resolveEnd - 0.2],
    [0, 0.7, 0.7, 0],
    Easing.easeInOutCubic
  )(t);
  if (p <= 0.02) return;

  // Sample a small number of pairs deterministically by time bucket
  const bucket = Math.floor(t * 6); // refresh ~6x/sec
  const rng = makeRng(bucket * 97 + 11);
  const all = [...markAgents, ...floodAgents];
  ctx.strokeStyle = hexA(ORANGE, 0.12 * p);
  ctx.lineWidth = 1;

  for (let i = 0; i < 80; i++) {
    const a = all[Math.floor(rng() * all.length)];
    const b = all[Math.floor(rng() * all.length)];
    // Quick current positions — approximate using approach logic inline.
    const ap = agentPos(a, t);
    const bp = agentPos(b, t);
    const d = Math.hypot(ap.x - bp.x, ap.y - bp.y);
    if (d < 180 && d > 30) {
      const op = (1 - d / 180) * 0.2 * p;
      ctx.strokeStyle = hexA(ORANGE, op);
      ctx.beginPath();
      ctx.moveTo(ap.x, ap.y);
      ctx.lineTo(bp.x, bp.y);
      ctx.stroke();
    }
  }
}

// Simplified position sampler for network lines (matches main logic loosely).
function agentPos(a, t) {
  if (a.origin === 'mark') {
    const scatStart = TL.scatterStart + (a.staggerDelay || 0);
    const myScatterP = smoothstep(scatStart, TL.scatterEnd, t);
    const resolveP = smoothstep(TL.resolveStart, TL.resolveEnd, t);
    const theta = a.scatterAngle + a.swirl * myScatterP * 1.2;
    const dist = a.scatterDist * easeOutPow(myScatterP, 2.2);
    let x = a.x0 + MARK_CELL / 2 + Math.cos(theta) * dist;
    let y = a.y0 + MARK_CELL / 2 + Math.sin(theta) * dist;
    if (resolveP > 0) {
      const r = Easing.easeInOutCubic(resolveP);
      x = lerp(x, a.tx, r);
      y = lerp(y, a.ty, r);
    }
    return { x, y };
  } else {
    const localStart = TL.floodStart + a.spawnOffset;
    if (t < localStart) return { x: a.x0, y: a.y0 };
    const life = t - localStart;
    const travelDur = TL.floodEnd - localStart;
    const approach = clamp(life / travelDur, 0, 1);
    const r = Easing.easeInOutCubic(approach);
    const wx = Math.cos(a.wanderAngle + t * 1.2 + a.seed * 6) * a.wanderAmp * (1 - approach);
    const wy = Math.sin(a.wanderAngle + t * 1.5 + a.seed * 6) * a.wanderAmp * (1 - approach);
    let x = lerp(a.x0, a.tx, r) + wx;
    let y = lerp(a.y0, a.ty, r) + wy;
    const resolveP = smoothstep(TL.resolveStart, TL.resolveEnd, t);
    if (resolveP > 0) {
      const snap = Easing.easeInOutCubic(resolveP);
      x = lerp(x, a.tx, snap);
      y = lerp(y, a.ty, snap);
    }
    return { x, y };
  }
}

Object.assign(window, { AgentSwarm });
