// scene1_chaos.jsx — Opening "chaos" scene.
//
// ~35 concept pills (Performance, Compa-Ratio, Pay Band, etc.) AND 45 salary-%
// chips (e.g. "3.42%") fly in from the top and pile up in the middle of the
// stage, driven by Matter.js physics. Concept pills are emphasised (bold,
// ultraviolet-tinted); salary chips are smaller and tier-coloured. Both share
// the same physics world.
//
// Z-order: pills sit above the caption so the falling animation is never
// masked by the bottom-left title block — viewer sees the pile bury the words.
//
// Animation stack:
//   Matter.js — gravity + chamfered (capsule) rigid bodies, walls + floor.
//   GSAP     — staggered opacity+scale fade-in when each pill is released.

function SeededRandom(seed) {
  let s = seed;
  return () => {
    s = (s * 9301 + 49297) % 233280;
    return s / 233280;
  };
}

// ── Pill data ───────────────────────────────────────────────────────────────
// Concept pills — bold emphasis = core framework terms.
const CONCEPT_PILLS = [
  { label: 'Performance',        emphasis: true,  kind: 'concept' },
  { label: 'Compa-Ratio',        emphasis: true,  kind: 'concept' },
  { label: 'Pay Band',           emphasis: true,  kind: 'concept' },
  { label: 'Budget',             emphasis: true,  kind: 'concept' },
  { label: 'Discretion',         emphasis: true,  kind: 'concept' },
  { label: 'Merit Matrix',       emphasis: true,  kind: 'concept' },
  { label: 'Market Midpoint',    emphasis: false, kind: 'concept' },
  { label: 'Tenure',             emphasis: false, kind: 'concept' },
  { label: 'Grade',              emphasis: false, kind: 'concept' },
  { label: 'Country',            emphasis: false, kind: 'concept' },
  { label: 'WTW P50',            emphasis: false, kind: 'concept' },
  { label: 'Survey Data',        emphasis: false, kind: 'concept' },
  { label: 'Manager Bias',       emphasis: false, kind: 'concept' },
  { label: 'Internal Equity',    emphasis: false, kind: 'concept' },
  { label: 'Job Family',         emphasis: false, kind: 'concept' },
  { label: 'High Performer',     emphasis: false, kind: 'concept' },
  { label: 'Promotion',          emphasis: false, kind: 'concept' },
  { label: 'Job Level',          emphasis: false, kind: 'concept' },
  { label: 'Inflation',          emphasis: false, kind: 'concept' },
  { label: 'Retention Risk',     emphasis: false, kind: 'concept' },
  { label: 'Headcount',          emphasis: false, kind: 'concept' },
  { label: 'Pay Gap',            emphasis: false, kind: 'concept' },
  { label: 'Cost of Living',     emphasis: false, kind: 'concept' },
  { label: 'Annual Increase',    emphasis: false, kind: 'concept' },
  { label: 'Merit Pool',         emphasis: false, kind: 'concept' },
  { label: 'Benchmarks',         emphasis: false, kind: 'concept' },
  { label: 'Range Spread',       emphasis: false, kind: 'concept' },
  { label: 'Role',               emphasis: false, kind: 'concept' },
  { label: 'Adjustment',         emphasis: false, kind: 'concept' },
  { label: 'Function',           emphasis: false, kind: 'concept' },
  { label: 'Base Salary',        emphasis: false, kind: 'concept' },
  { label: 'Guardrails',         emphasis: false, kind: 'concept' },
  { label: 'Median',             emphasis: false, kind: 'concept' },
  { label: 'Oracle',             emphasis: false, kind: 'concept' },
  { label: 'Percentile',         emphasis: false, kind: 'concept' },
];

// Salary % chips — random tier + merit %, deterministic.
const SALARY_CHIPS = (() => {
  const rnd = SeededRandom(1337);
  const out = [];
  for (let i = 0; i < 45; i++) {
    const r = rnd();
    const tier  = r < 0.2 ? 'High' : r < 0.7 ? 'Meets' : 'Low';
    const merit = (1 + rnd() * 6);
    out.push({ label: merit.toFixed(2) + '%', tier, kind: 'salary' });
  }
  return out;
})();

const ALL_PILLS = [...CONCEPT_PILLS, ...SALARY_CHIPS];

// Back-compat stub (other code may import this).
const CHAOS_EMPLOYEES = [];

// ── Geometry ────────────────────────────────────────────────────────────────
// Walls near the stage edges — pile spreads across the full width, not just
// a center column.
const PLAY_LEFT   = 80;
const PLAY_RIGHT  = 1840;
const FLOOR_Y     = 1010;
const WALL_THICK  = 20;

const CONCEPT_FS = 24;
const CONCEPT_PAD_Y = 10;
const CONCEPT_PAD_X = 22;
const SALARY_FS = 20;
const SALARY_PAD_Y = 7;
const SALARY_PAD_X = 14;

// Delay pill release until after the caption has faded in — the user wants
// the text to read as a beat of its own before the animation begins.
const PHYSICS_START_TIME = 3.0;
const MAX_STEP_MS        = 33;
const SUB_STEP_MS        = 10;

function Scene1Chaos() {
  const { localTime } = useSprite();

  const engineRef   = React.useRef(null);
  const entriesRef  = React.useRef([]);
  const pillRefsRef = React.useRef([]);
  const prevTRef    = React.useRef(0);
  const [ready, setReady] = React.useState(false);

  // ── Setup: measure pills, build engine + bodies ────────────────────────
  React.useEffect(() => {
    if (!window.Matter) return;
    const { Engine, Bodies, Composite, Body } = window.Matter;

    // Ghost-measure each pill's rendered width/height.
    const ghost = document.createElement('div');
    ghost.style.cssText =
      'position:absolute; top:-9999px; left:-9999px; visibility:hidden; ' +
      'font-family: Inter, sans-serif;';
    document.body.appendChild(ghost);

    const measured = ALL_PILLS.map((p) => {
      if (p.kind === 'concept') {
        const weight = p.emphasis ? 700 : 500;
        ghost.innerHTML =
          `<span style="display:inline-block; padding: ${CONCEPT_PAD_Y}px ${CONCEPT_PAD_X}px; ` +
          `white-space: nowrap; font-size: ${CONCEPT_FS}px; font-weight: ${weight}; ` +
          `border: 1px solid transparent; box-sizing: content-box;">${p.label}</span>`;
      } else {
        // Salary chip: dot + merit % text.
        ghost.innerHTML =
          `<span style="display:inline-flex; align-items:center; gap: 6px; ` +
          `padding: ${SALARY_PAD_Y}px ${SALARY_PAD_X}px ${SALARY_PAD_Y}px ${SALARY_PAD_X - 2}px; ` +
          `white-space: nowrap; font-size: ${SALARY_FS}px; font-weight: 600; ` +
          `border: 1px solid transparent; box-sizing: content-box;">` +
          `<span style="width:10px;height:10px;border-radius:5px;background:#000;display:inline-block"></span>` +
          `<span>${p.label}</span></span>`;
      }
      const el = ghost.firstChild;
      return { ...p, width: el.offsetWidth + 2, height: el.offsetHeight + 2 };
    });
    document.body.removeChild(ghost);

    // Engine + world
    const engine = Engine.create({
      positionIterations: 8,
      velocityIterations: 6,
      gravity: { x: 0, y: 1.6 },
    });
    const world = engine.world;
    const playMidX = (PLAY_LEFT + PLAY_RIGHT) / 2;

    // Caption platform — an invisible ramp at the top edge of the caption
    // text. Pills falling into the left 900px of the stage land on this
    // ramp (as if the text were a floor), then slide right down the slope
    // into the empty space past the caption. Low friction keeps them sliding.
    const captionTop   = 755;   // just above the heading's top edge
    const captionRampW = 900;
    const captionRampAngle = 0.055; // +~3°, right side lower → slides right
    const captionRamp = Bodies.rectangle(
      PLAY_LEFT + captionRampW / 2,
      captionTop,
      captionRampW, 18,
      { isStatic: true, friction: 0.18, restitution: 0.15, angle: captionRampAngle }
    );

    Composite.add(world, [
      // Stage floor (full width)
      Bodies.rectangle(playMidX, FLOOR_Y + WALL_THICK/2, (PLAY_RIGHT - PLAY_LEFT) + 400, WALL_THICK, { isStatic: true, friction: 0.85 }),
      // Side walls
      Bodies.rectangle(PLAY_LEFT  - WALL_THICK/2, 540, WALL_THICK, 2400, { isStatic: true }),
      Bodies.rectangle(PLAY_RIGHT + WALL_THICK/2, 540, WALL_THICK, 2400, { isStatic: true }),
      // Text-as-floor ramp
      captionRamp,
    ]);

    // Pill bodies — deferred add-to-world so they release in a stagger.
    //
    // Release timing is shaped in three bursts ("fast · slow · fast · trickle")
    // rather than uniform, so the fall reads as a natural cascade. Drop speed,
    // air friction, and angular velocity also vary per pill so some plummet
    // and some drift.
    const rnd = SeededRandom(42);
    const shapeBurst = (r) => {
      if (r < 0.30) return 0.00 + (r / 0.30) * 0.55;       // 30% — fast opening burst 0.00 → 0.55s
      if (r < 0.55) return 0.80 + ((r - 0.30) / 0.25) * 0.60;  // 25% — slow stretch 0.80 → 1.40s
      if (r < 0.85) return 1.60 + ((r - 0.55) / 0.30) * 0.90;  // 30% — main burst 1.60 → 2.50s
      return 2.70 + ((r - 0.85) / 0.15) * 0.70;                // 15% — trailing drift 2.70 → 3.40s
    };

    const entries = measured.map((p, i) => {
      const spawnX = PLAY_LEFT + 40 + rnd() * (PLAY_RIGHT - PLAY_LEFT - 80);
      const spawnY = -140 - rnd() * 520;
      const angle  = (rnd() - 0.5) * 0.4;
      // Varied air friction → some pills "float" slower, some plummet.
      const airF   = 0.006 + rnd() * 0.024;
      const body = Bodies.rectangle(spawnX, spawnY, p.width, p.height, {
        chamfer:     { radius: p.height / 2 },
        restitution: 0.18,
        friction:    0.55,
        frictionAir: airF,
        density:     0.0022,
        angle,
      });
      Body.setAngularVelocity(body, (rnd() - 0.5) * 0.08);
      // Horizontal component: small drift toward center, plus noise.
      const initialVx = (playMidX - spawnX) * 0.003 + (rnd() - 0.5) * 2.5;
      // Varied initial Y velocity: some pills "dropped fast" (positive vy),
      // some gently released (near 0). Creates mixed arrival rhythms.
      const initialVy = -1 + rnd() * 14;
      Body.setVelocity(body, { x: initialVx, y: initialVy });
      body.__spawnX     = spawnX;
      body.__spawnY     = spawnY;
      body.__spawnVx    = initialVx;
      body.__spawnVy    = initialVy;
      body.__spawnAngle = angle;
      return {
        body,
        width:       p.width,
        height:      p.height,
        spawnDelay:  shapeBurst(rnd()),  // bursty, non-uniform release
        released:    false,
      };
    });

    engineRef.current  = engine;
    entriesRef.current = entries;
    prevTRef.current   = 0;
    // Expose for debugging.
    window.__scene1Engine = engine;
    window.__scene1Entries = entries;
    setReady(true);

    return () => { try { Engine.clear(engine); } catch (e) {} };
  }, []);

  // ── Per-frame: release + step + push transforms ─────────────────────────
  React.useEffect(() => {
    const engine = engineRef.current;
    if (!engine || !window.Matter) return;
    const { Composite, Engine, Body } = window.Matter;
    const entries = entriesRef.current;
    const gsap    = window.gsap;

    for (let i = 0; i < entries.length; i++) {
      const e = entries[i];
      const dueAt = PHYSICS_START_TIME + e.spawnDelay;
      if (!e.released && localTime >= dueAt) {
        Composite.add(engine.world, e.body);
        e.released = true;
        // Soft opacity fade-in via CSS transition (see transition on pill div).
        // We previously used GSAP here but its `scale` tween wrote to `transform`
        // which my per-frame `translate+rotate` update overwrote, leaving opacity
        // stuck at 0. CSS-transition-only = no fight with transform.
        const el = pillRefsRef.current[i];
        if (el) el.style.opacity = '1';
      } else if (e.released && localTime < dueAt) {
        const el = pillRefsRef.current[i];
        if (el) el.style.opacity = '0';
        try { Composite.remove(engine.world, e.body); } catch (err) {}
        Body.setPosition(e.body, { x: e.body.__spawnX, y: e.body.__spawnY });
        Body.setVelocity(e.body, { x: e.body.__spawnVx, y: e.body.__spawnVy ?? 0 });
        Body.setAngle(e.body, e.body.__spawnAngle);
        Body.setAngularVelocity(e.body, 0);
        e.released = false;
      }
    }

    // Advance the simulation. For normal playback rawDt is ~16ms per frame;
    // on seek/reload it can be arbitrarily large, so we cap at 20s of sim
    // time (any further we treat as "already settled") and use ~12ms steps
    // so the stall from seeking isn't unbounded.
    const rawDt = (localTime - prevTRef.current) * 1000;
    prevTRef.current = localTime;
    if (rawDt > 0) {
      const totalMs = Math.min(rawDt, 20000);
      const stepMs  = 12;
      const steps   = Math.max(1, Math.ceil(totalMs / stepMs));
      const sub     = totalMs / steps;
      for (let i = 0; i < steps; i++) Engine.update(engine, sub);
    }

    for (let i = 0; i < entries.length; i++) {
      const el = pillRefsRef.current[i];
      if (!el) continue;
      const { body, width, height } = entries[i];
      el.style.transform =
        `translate(${body.position.x - width / 2}px, ${body.position.y - height / 2}px) ` +
        `rotate(${body.angle}rad)`;
    }
  }, [localTime, ready]);  // `ready` ensures the effect fires AFTER the render that populated pillRefsRef

  const enterP   = clamp(localTime / 1.5, 0, 1);          // scene fade-in 0 → 1.5s
  const exitP    = clamp((localTime - 15.7) / 1.5, 0, 1); // scene fade-out 15.7 → 17.2s

  // VO_1 starts at scene local 1.5s. Let the voice carry the opening beat
  // for ~2s on its own before anything appears below. The three caption
  // pieces then reveal in sequence — the bottom paragraph fades in slowly so
  // the viewer can absorb it under the ongoing narration.
  const bannerP     = clamp((localTime - 0.3) / 1.2, 0, 1);          // banner still reads as the scene's opening
  const topLabelP   = clamp((localTime - 3.5) / 1.0, 0, 1);          // "TODAY · ANNUAL SALARY…"
  const headingP    = clamp((localTime - 5.5) / 1.8, 0, 1);          // "Managers decide in the dark."
  const bottomP     = clamp((localTime - 8.0) / 3.0, 0, 1);          // sub-paragraph — slow 3s fade
  const maskP       = clamp((localTime - 3.0) / 1.5, 0, 1);          // mask appears just before the first caption line

  const tierColor = (tier) =>
    tier === 'High'  ? ASI_COLORS.perfHigh
  : tier === 'Meets' ? ASI_COLORS.perfMeets
                     : ASI_COLORS.perfLow;

  return (
    <div style={{
      position: 'absolute', inset: 0,
      background: ASI_COLORS.canvas,
      overflow: 'hidden',
      opacity: enterP * (1 - exitP),
      transform: `translateX(${-exitP * 420}px)`, // exit: slide left
    }}>
      {/* Subtle dot grid (z:0) */}
      <div style={{
        position:'absolute', inset:0, zIndex: 0,
        backgroundImage: 'radial-gradient(#d4d7dc 1px, transparent 1px)',
        backgroundSize: '28px 28px',
        opacity: 0.35,
      }}/>

      {/* Caption mask (z:25) — ABOVE pills so it clears a readable zone
          behind the caption. Radial+linear multi-stop gives the edge a soft
          feathered blend into the pile. Fades in just before the first caption
          line so the mask never precedes any visible text. */}
      <div style={{
        position: 'absolute', left: 0, bottom: 0, width: 1200, height: 520, zIndex: 25,
        background: [
          'radial-gradient(ellipse 85% 75% at 20% 85%, rgba(240,241,243,1) 20%, rgba(240,241,243,0.92) 42%, rgba(240,241,243,0.62) 62%, rgba(240,241,243,0.25) 82%, rgba(240,241,243,0) 100%)',
          'linear-gradient(to top right, rgba(240,241,243,0.6) 0%, rgba(240,241,243,0.3) 55%, rgba(240,241,243,0) 90%)',
        ].join(', '),
        opacity: maskP * (1 - exitP),
        pointerEvents: 'none',
      }}/>

      {/* Caption (z:26) — three pieces with staggered, voice-led reveal */}
      <div style={{
        position: 'absolute', left: 80, bottom: 100, zIndex: 26,
        maxWidth: 820,
        fontFamily: 'Inter, sans-serif',
        color: ASI_COLORS.ink,
      }}>
        <div style={{
          fontSize: 16, fontWeight: 600, letterSpacing: '0.06em',
          textTransform: 'uppercase', color: ASI_COLORS.accent,
          marginBottom: 12,
          opacity: topLabelP * (1 - exitP),
          transform: `translateY(${(1 - topLabelP) * 8}px)`,
        }}>Today · annual salary increase cycle</div>
        <div style={{
          fontSize: 60, fontWeight: 600, lineHeight: 1.05,
          letterSpacing: '-0.02em', color: ASI_COLORS.ink,
          textWrap: 'pretty',
          opacity: headingP * (1 - exitP),
          transform: `translateY(${(1 - headingP) * 10}px)`,
        }}>
          Managers decide <span style={{color: ASI_COLORS.accent}}>in the dark.</span>
        </div>
        <div style={{
          marginTop: 14, fontSize: 19, color: ASI_COLORS.inkSoft,
          lineHeight: 1.4, maxWidth: 720,
          opacity: bottomP * (1 - exitP),
          transform: `translateY(${(1 - bottomP) * 6}px)`,
        }}>
          No pay ranges. No merit matrix. Inconsistent increases,<br/>uncorrelated to performance or pay position.
        </div>
      </div>

      {/* Process banner at top (z:5) — no inner Sprite wrapper, so it stays
          mounted for the full duration of scene 1 (previously the inner
          Sprite's end=17 was in GLOBAL time and was unmounting the banner
          mid-voice). Fades naturally with the scene's exit. */}
      <ProcessBanner localProgress={bannerP}/>

      {/* Physics pills (z:20) — ABOVE caption so the pile visibly buries the text */}
      {ready && entriesRef.current.map((_, i) => {
        const p = ALL_PILLS[i];
        const isConcept = p.kind === 'concept';
        return (
          <div
            key={i}
            ref={el => { pillRefsRef.current[i] = el; }}
            style={{
              position: 'absolute', left: 0, top: 0, zIndex: 20,
              width: entriesRef.current[i].width,
              height: entriesRef.current[i].height,
              boxSizing: 'border-box',
              display: 'flex', alignItems: 'center', justifyContent: 'center',
              gap: isConcept ? 0 : 6,
              background: '#ffffff',
              border: `1px solid ${isConcept && p.emphasis ? ASI_COLORS.accent + '40' : '#d4d7dc'}`,
              borderRadius: 999,
              paddingLeft:  isConcept ? 0 : SALARY_PAD_X - 2,
              paddingRight: isConcept ? 0 : SALARY_PAD_X,
              fontFamily: 'Inter, sans-serif',
              fontSize:   isConcept ? CONCEPT_FS : SALARY_FS,
              fontWeight: isConcept ? (p.emphasis ? 700 : 500) : 600,
              color:      isConcept
                            ? (p.emphasis ? ASI_COLORS.accentDark : ASI_COLORS.ink)
                            : tierColor(p.tier),
              letterSpacing: '-0.005em',
              fontVariantNumeric: 'tabular-nums',
              whiteSpace: 'nowrap',
              boxShadow: isConcept && p.emphasis
                ? '0 1px 3px rgba(127,53,178,0.14), 0 6px 18px rgba(127,53,178,0.10)'
                : '0 1px 3px rgba(0,0,0,0.06), 0 4px 12px rgba(0,0,0,0.04)',
              opacity: 0,
              willChange: 'transform, opacity',
              pointerEvents: 'none',
              transformOrigin: 'center center',
              transition: 'opacity 360ms cubic-bezier(0.22, 1, 0.36, 1)',
            }}
          >
            {!isConcept && (
              <span style={{
                width: 10, height: 10, borderRadius: 5,
                background: tierColor(p.tier),
                flex: '0 0 auto',
              }}/>
            )}
            <span>{p.label}</span>
          </div>
        );
      })}
    </div>
  );
}

function ProcessBanner({ localProgress }) {
  const steps = [
    { label: 'Survey data', sub: 'WTW market P50' },
    { label: 'Reward and Finance calculate affordability', sub: 'Budget negotiation' },
    { label: 'Manager discretion in allocating increases', sub: 'Per-country budget %' },
  ];
  return (
    <div style={{
      position: 'absolute', top: 56, left: '50%',
      transform: 'translateX(-50%)',
      display: 'flex', alignItems: 'center', gap: 16,
      opacity: clamp(localProgress * 2, 0, 1),
      zIndex: 5,
    }}>
      {steps.map((s, i) => (
        <React.Fragment key={i}>
          <div style={{
            background: '#fff', borderRadius: 10, padding: '12px 18px',
            boxShadow: '0 1px 3px rgba(0,0,0,0.06)',
            display: 'flex', flexDirection: 'column', gap: 2,
            width: 260, minHeight: 110,
            boxSizing: 'border-box',
          }}>
            <div style={{ fontSize: 16, fontWeight: 600, letterSpacing: '0.04em',
                          textTransform:'uppercase', color: ASI_COLORS.inkMuted }}>
              Step {i+1}
            </div>
            <div style={{ fontSize: 20, fontWeight: 600, color: ASI_COLORS.ink }}>{s.label}</div>
            <div style={{ fontSize: 16, color: ASI_COLORS.inkSoft }}>{s.sub}</div>
          </div>
          {i < steps.length - 1 && (
            <svg width="18" height="12" viewBox="0 0 18 12" fill="none">
              <path d="M1 6h15M12 1l5 5-5 5" stroke={ASI_COLORS.inkMuted} strokeWidth="1.5"
                    strokeLinecap="round" strokeLinejoin="round"/>
            </svg>
          )}
        </React.Fragment>
      ))}
    </div>
  );
}

Object.assign(window, { Scene1Chaos, CHAOS_EMPLOYEES, CONCEPT_PILLS, SALARY_CHIPS, ALL_PILLS });
