/* Corsa Privata — "The Atelier, under way." Scroll-scrubbed image sequence.
 * 201 frames of a bare aluminium chassis built into a finished Circuit, mapped
 * frame-by-frame to scroll position (the Apple-style canvas scrub). A pinned,
 * DPR-aware canvas; rAF-eased so the build feels weighted, never a flick.
 *
 * Compositing: the frames are pre-keyed offline (ffmpeg lumakey -> overlay on
 * #f3f3f3) so the studio plate AND its floor shadow are knocked out to a uniform
 * #f3f3f3, leaving only the rig (the monitors survive the key). The section and
 * the canvas share that exact #f3f3f3, so the rig composites directly onto the
 * page with no box, no edge band, and no floor shadow — it reads as floating.
 *
 * Layout keeps the rig in its own zone (right of the text on wide screens, below
 * it on narrow ones), so the copy never overlaps the rig and the rig is never
 * clipped by the header or the edges. Honours prefers-reduced-motion. Frames are
 * only fetched once the section nears the viewport (no above-the-fold contention),
 * and are subsampled on small screens to ease decode pressure. */

const AsmIcon = window.Icon;
const { Button: AsmButton } = window.CorsaPrivataDesignSystem_94a5c9;

const ASM_COUNT = 201;            // frame_000 .. frame_200
const ASM_W = 730;                // cropped frame width (rig union bbox; empty plate margins removed)
const ASM_H = 760;                // cropped frame height
const ASM_BP = 760;               // px: side-by-side above, stacked below
const ASM_PLATE_TOP = '#f3f3f3';  // the footage plate tone; the section + canvas fill match it exactly
const ASM_PLATE_BOT = '#f3f3f3';  // (kept uniform so the plate never reads as a box; the floor is feathered)
const ASM_STAGE_AT = [0, 0.30, 0.58, 0.85];   // scroll thresholds for the 4 captions
const ASM_CTA_AT = 0.93;          // CTA reveals while the finished caption still holds
/* ?v bumps when the frames are re-exported, so browsers fetch the cleaned set, not a cached old one */
const asmURL = (i) => 'assets/assembly/frame_' + String(i).padStart(3, '0') + '.jpg?v=4';
const asmClamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));

function Assembly({ go, t }) {
  const a = t.assembly;
  const stages = a.stages;

  // motion preference once, synchronously, so first render picks the right layout
  const [reduced] = React.useState(() => {
    try { return !!(window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches); }
    catch (e) { return false; }
  });

  const trackRef = React.useRef(null);
  const canvasRef = React.useRef(null);
  const ctxRef = React.useRef(null);
  const imagesRef = React.useRef([]);
  const anim = React.useRef({ target: 0, current: 0, raf: 0, running: false, drawn: -1 });
  const lastPushed = React.useRef(-1);

  const [ready, setReady] = React.useState(false);
  const [loadPct, setLoadPct] = React.useState(0);
  const [progress, setProgress] = React.useState(0);   // 0..1, drives the overlay

  // ---- canvas helpers -------------------------------------------------------
  const sizeCanvas = React.useCallback(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const dpr = Math.min(window.devicePixelRatio || 1, 2);
    const cw = canvas.clientWidth || 1;
    const ch = canvas.clientHeight || 1;
    canvas.width = Math.round(cw * dpr);
    canvas.height = Math.round(ch * dpr);
    const ctx = canvas.getContext('2d', { alpha: false });
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    ctxRef.current = ctx;
  }, []);

  // the box the rig is contained within: right zone on wide screens, lower zone on narrow.
  // The frames are keyed (margins are invisible #f3f3f3), so the box can sit close to the
  // text — only the rig's own pixels matter, and they keep clear of the left column.
  const drawBox = (cw, ch) => {
    if (cw >= ASM_BP) {
      // sit the rig just to the right of the text column (left-aligned), so the two read as a
      // tight pair instead of being flung to opposite edges
      const textRight = asmClamp(cw * 0.05, 20, 72) + Math.min(cw * 0.38, 420);
      const gap = asmClamp(cw * 0.035, 30, 64);
      const padR = asmClamp(cw * 0.03, 16, 40);
      return { bl: textRight + gap, bt: 82, br: cw - padR, bb: ch - 34, align: 'left' };
    }
    const sp = asmClamp(cw * 0.04, 12, 22);
    return { bl: sp, bt: ch * 0.40, br: cw - sp, bb: ch - 116, align: 'center' };
  };

  const drawFrame = React.useCallback((idx) => {
    const canvas = canvasRef.current;
    const ctx = ctxRef.current;
    if (!canvas || !ctx) return;
    const imgs = imagesRef.current;
    const clamped = asmClamp(Math.round(idx), 0, ASM_COUNT - 1);
    let img = imgs[clamped];
    if (!img || !img.complete || !img.naturalWidth) {
      for (let d = 1; d < ASM_COUNT; d++) {        // nearest decoded frame; never blank during load
        const lo = imgs[clamped - d], hi = imgs[clamped + d];
        if (lo && lo.complete && lo.naturalWidth) { img = lo; break; }
        if (hi && hi.complete && hi.naturalWidth) { img = hi; break; }
      }
    }
    if (!img || !img.naturalWidth) return;

    const cw = canvas.clientWidth, ch = canvas.clientHeight;
    // fill: vertical gradient mirroring the plate, so the page and the plate are one surface
    const bg = ctx.createLinearGradient(0, 0, 0, ch);
    bg.addColorStop(0, ASM_PLATE_TOP);
    bg.addColorStop(1, ASM_PLATE_BOT);
    ctx.fillStyle = bg;
    ctx.fillRect(0, 0, cw, ch);

    const box = reduced
      ? (() => { const p = Math.min(cw, ch) * 0.04; return { bl: p, bt: p, br: cw - p, bb: ch - p, align: 'center' }; })()  // reduced: centered in its own frame
      : drawBox(cw, ch);
    const { bl, bt, br, bb, align } = box;
    const bw = Math.max(40, br - bl), bh = Math.max(40, bb - bt);
    const scale = Math.min(bw / ASM_W, bh / ASM_H);            // contain (never clipped)
    const dw = ASM_W * scale, dh = ASM_H * scale;
    const dx = align === 'left' ? bl : bl + (bw - dw) / 2;     // hug the text on wide screens; centered otherwise
    const dy = bt + (bh - dh) / 2;
    // Frames are pre-keyed to a uniform #f3f3f3 plate (the studio floor shadow is removed
    // offline), so the rig composites directly onto the matching fill with no box and no
    // feather needed — it reads as floating on the page.
    ctx.drawImage(img, dx, dy, dw, dh);

    // Atelier documentation layer: registration corners, a dimension line, and an
    // honest frame/percentage readout. It reads as process, not product shot, and
    // fades away as the Circuit finishes so the final object stands clean.
    if (!reduced) {
      const p = clamped / (ASM_COUNT - 1);
      const tech = asmClamp((0.90 - p) / 0.08, 0, 1);
      if (tech > 0.01) {
        ctx.save();
        ctx.globalAlpha = tech;
        ctx.strokeStyle = 'rgba(27,29,31,0.28)';
        ctx.lineWidth = 1;
        const L = 14;
        [[bl, bt, 1, 1], [br, bt, -1, 1], [bl, bb, 1, -1], [br, bb, -1, -1]].forEach(([x, y, sx, sy]) => {
          ctx.beginPath();
          ctx.moveTo(x + sx * L, y + 0.5); ctx.lineTo(x + 0.5, y + 0.5); ctx.lineTo(x + 0.5, y + sy * L);
          ctx.stroke();
        });
        const yDim = Math.min(bb - 2, dy + dh + 18) + 0.5;
        ctx.beginPath();
        ctx.moveTo(dx + 0.5, yDim - 4); ctx.lineTo(dx + 0.5, yDim + 4);
        ctx.moveTo(dx, yDim); ctx.lineTo(dx + dw, yDim);
        ctx.moveTo(dx + dw - 0.5, yDim - 4); ctx.lineTo(dx + dw - 0.5, yDim + 4);
        ctx.stroke();
        if (cw >= ASM_BP) {
          ctx.font = '500 10px "Space Grotesk", sans-serif';
          ctx.textAlign = 'right';
          ctx.fillStyle = 'rgba(27,29,31,0.42)';
          const rx = br - 3, ry = bt + 22;
          ctx.fillText(String(clamped).padStart(3, '0') + ' / ' + (ASM_COUNT - 1), rx, ry + 10);
          ctx.fillText(String(Math.round(p * 100)).padStart(2, '0') + ' %', rx, ry + 26);
          ctx.fillStyle = 'rgba(194,161,101,0.9)';
          ctx.fillRect(rx - 34, ry + 33, 34, 1.5);
        }
        ctx.restore();
      }
    }
  }, []);

  // ---- preload + wiring -----------------------------------------------------
  React.useEffect(() => {
    const startFrame = reduced ? ASM_COUNT - 1 : 0;     // reduced shows the finished Circuit
    anim.current.current = startFrame;
    anim.current.target = startFrame;
    anim.current.drawn = -1;

    const imgs = new Array(ASM_COUNT);
    imagesRef.current = imgs;
    let started = false, loaded = 0, queued = 0, readyFired = false;

    const startPreload = () => {
      if (started) return;
      started = true;
      const stride = (window.innerWidth < ASM_BP) ? 2 : 1;   // ease decode pressure on phones
      const wanted = new Set();
      for (let i = 0; i < ASM_COUNT; i += stride) wanted.add(i);
      wanted.add(ASM_COUNT - 1); wanted.add(startFrame);
      queued = wanted.size;
      const onLoad = (i) => () => {
        loaded++;
        setLoadPct(Math.round((loaded / queued) * 100));
        if (!readyFired) {                 // first usable frame: paint immediately, never tied to one file
          readyFired = true;
          sizeCanvas(); drawFrame(startFrame); anim.current.drawn = startFrame; setReady(true);
        } else if (reduced || i === anim.current.drawn) {
          drawFrame(reduced ? startFrame : i);   // reduced converges toward the finished frame; scrub sharpens the held frame
        }
      };
      wanted.forEach((i) => {
        const img = new Image();
        img.decoding = 'async';
        img.onload = onLoad(i);
        img.onerror = onLoad(i);
        img.src = asmURL(i);
        imgs[i] = img;
      });
    };

    // defer fetching until the section is within ~1 viewport, so it never contends with above-the-fold load
    let io = null;
    if ('IntersectionObserver' in window && trackRef.current) {
      io = new IntersectionObserver((entries) => {
        if (entries.some((e) => e.isIntersecting)) { startPreload(); if (io) { io.disconnect(); io = null; } }
      }, { rootMargin: '600px 0px 1000px 0px' });
      io.observe(trackRef.current);
    } else {
      startPreload();
    }

    if (reduced) {
      const onResize = () => { sizeCanvas(); drawFrame(ASM_COUNT - 1); };
      window.addEventListener('resize', onResize);
      return () => { window.removeEventListener('resize', onResize); if (io) io.disconnect(); };
    }

    const tick = () => {
      const s = anim.current;
      const diff = s.target - s.current;
      if (Math.abs(diff) < 0.08) s.current = s.target;
      else s.current += diff * 0.16;          // ease toward target; no spring, no overshoot
      const idx = Math.round(s.current);
      if (idx !== s.drawn) { drawFrame(idx); s.drawn = idx; }
      if (s.current !== s.target) { s.raf = requestAnimationFrame(tick); s.running = true; }
      else { s.running = false; }
    };
    const kick = () => { const s = anim.current; if (!s.running) { s.running = true; s.raf = requestAnimationFrame(tick); } };

    const computeProgress = () => {
      const el = trackRef.current;
      if (!el) return 0;
      const rect = el.getBoundingClientRect();
      const total = rect.height - window.innerHeight;
      if (total <= 0) return 0;
      return Math.min(1, Math.max(0, -rect.top / total));
    };

    const onScroll = () => {
      const p = computeProgress();
      anim.current.target = p * (ASM_COUNT - 1);
      kick();
      if (p === 0 || p === 1 || Math.abs(p - lastPushed.current) >= 0.004) {
        lastPushed.current = p;
        setProgress(p);
      }
    };

    const onResize = () => { sizeCanvas(); onScroll(); drawFrame(Math.round(anim.current.target)); };

    window.addEventListener('scroll', onScroll, { passive: true });
    window.addEventListener('resize', onResize);
    sizeCanvas();
    onScroll();

    return () => {
      window.removeEventListener('scroll', onScroll);
      window.removeEventListener('resize', onResize);
      cancelAnimationFrame(anim.current.raf);
      anim.current.running = false;
      if (io) io.disconnect();
    };
  }, [reduced, sizeCanvas, drawFrame]);

  // active stage from progress
  let stageIdx = 0;
  for (let i = 0; i < ASM_STAGE_AT.length; i++) if (progress >= ASM_STAGE_AT[i]) stageIdx = i;
  const narrating = progress >= 0.10;                 // subhead at start, then captions
  const ctaVisible = progress >= ASM_CTA_AT;
  const counter = a.stageLabel + ' ' + String(stageIdx + 1).padStart(2, '0') + ' / ' + String(stages.length).padStart(2, '0');

  // ---- shared bits ----------------------------------------------------------
  const eyebrow = (
    <span style={{
      display: 'inline-flex', alignItems: 'center', gap: 12,
      fontFamily: 'var(--font-display)', fontSize: 12, fontWeight: 'var(--fw-medium)',
      letterSpacing: 'var(--ls-wider)', textTransform: 'uppercase', color: 'var(--cp-gold-700)',
    }}>
      {a.eyebrow}
      <span style={{ width: 38, height: 1, background: 'var(--gradient-gold)' }} />
    </span>
  );
  const headline = (
    <h2 style={{
      fontFamily: 'var(--font-display)', fontWeight: 'var(--fw-bold)',
      fontSize: 'clamp(32px, 4.6vw, 60px)', lineHeight: 1.0, letterSpacing: '-0.03em',
      color: 'var(--text-strong)', margin: '18px 0 0', textWrap: 'balance',
    }}>
      {a.headline}
    </h2>
  );

  // ======== REDUCED MOTION: static, ordered, no scrub ========================
  if (reduced) {
    return (
      <section id="atelier" className="cp-asm" style={{ position: 'relative', background: ASM_PLATE_TOP, borderTop: '1px solid var(--border-hairline)', borderBottom: '1px solid var(--border-hairline)' }}>
        <div style={{ maxWidth: 'var(--container-max)', margin: '0 auto', padding: 'clamp(72px, 9vw, 112px) 32px' }}>
          {eyebrow}
          {headline}
          <p style={{ fontFamily: 'var(--font-body)', fontSize: 'clamp(16px,1.5vw,19px)', lineHeight: 1.6, color: 'var(--text-body)', margin: '22px 0 0', maxWidth: '54ch', textWrap: 'pretty' }}>{a.sub}</p>

          <div style={{ position: 'relative', width: '100%', maxWidth: 1000, margin: '40px auto 0', aspectRatio: '3 / 2', background: ASM_PLATE_TOP }}>
            <canvas ref={canvasRef} style={{ position: 'absolute', inset: 0, width: '100%', height: '100%' }} role="img" aria-label={a.headline} />
          </div>

          <ol className="cp-asm-stages" style={{ listStyle: 'none', display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 1, margin: '40px 0 0', padding: '0', borderTop: '1px solid var(--border-hairline)' }}>
            {stages.map((s, i) => (
              <li key={i} style={{ padding: '22px 18px 0' }}>
                <span style={{ display: 'block', height: 2, width: 28, background: 'var(--gradient-gold)', marginBottom: 16 }} />
                <span style={{ display: 'block', fontFamily: 'var(--font-display)', fontSize: 11, fontWeight: 'var(--fw-semibold)', letterSpacing: 'var(--ls-wider)', textTransform: 'uppercase', color: 'var(--text-strong)' }}>{s.label}</span>
                <span style={{ display: 'block', fontFamily: 'var(--font-body)', fontSize: 13.5, lineHeight: 1.5, color: 'var(--text-muted)', marginTop: 8 }}>{s.sub}</span>
              </li>
            ))}
          </ol>

          <div style={{ marginTop: 40 }}>
            <AsmButton onClick={() => go('configure')}>{a.cta}</AsmButton>
          </div>
        </div>
      </section>
    );
  }

  // ======== SCRUB: pinned canvas, scroll-driven ==============================
  return (
    <section id="atelier" className="cp-asm" style={{ position: 'relative', background: 'linear-gradient(180deg, ' + ASM_PLATE_TOP + ' 0%, ' + ASM_PLATE_BOT + ' 100%)' }}>
      <div style={{ position: 'absolute', top: 0, left: 0, right: 0, height: 1, background: 'var(--border-hairline)', zIndex: 3 }} />
      <div ref={trackRef} className="cp-asm-track" style={{ position: 'relative', height: '320vh' }}>
        <div className="cp-asm-stage" style={{ position: 'sticky', top: 0, height: '100vh', overflow: 'hidden' }}>

          {/* the rig */}
          <canvas ref={canvasRef} className="cp-asm-canvas" style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', display: 'block' }} aria-hidden="true" />

          {/* room atmosphere: a whisper of warm vignette so the plate reads as a lit
            * space, not catalog white. Max 7% at the corners; the plate stays seamless. */}
          <div aria-hidden="true" style={{ position: 'absolute', inset: 0, pointerEvents: 'none', background: 'radial-gradient(130% 115% at 62% 42%, transparent 55%, rgba(37,33,26,0.07) 100%)' }} />

          {/* overlay copy — lives in its own column, never over the rig */}
          <div className="cp-asm-overlay" style={{ position: 'absolute', inset: 0 }}>
            <div className="cp-asm-textcol">
              {eyebrow}
              {headline}

              {/* narration: subhead at the start, then the active stage caption, same anchor */}
              <div className="cp-asm-narr">
                <p aria-hidden={narrating ? 'true' : 'false'} style={{
                  position: 'absolute', inset: 0, margin: 0,
                  fontFamily: 'var(--font-body)', fontSize: 'clamp(15px,1.35vw,18px)', lineHeight: 1.6,
                  color: 'var(--text-body)', textWrap: 'pretty',
                  opacity: narrating ? 0 : 1,
                  transition: 'opacity var(--dur-base) var(--ease-standard)',
                }}>{a.sub}</p>
                {stages.map((s, i) => {
                  const on = narrating && i === stageIdx;
                  return (
                    <div key={i} aria-hidden={on ? 'false' : 'true'} style={{
                      position: 'absolute', inset: 0, opacity: on ? 1 : 0,
                      transition: 'opacity var(--dur-base) var(--ease-standard)',
                    }}>
                      <span style={{ display: 'block', fontFamily: 'var(--font-display)', fontSize: 'clamp(14px,1.5vw,17px)', fontWeight: 'var(--fw-semibold)', letterSpacing: 'var(--ls-wider)', textTransform: 'uppercase', color: 'var(--text-strong)' }}>{s.label}</span>
                      <span style={{ display: 'block', fontFamily: 'var(--font-body)', fontSize: 'clamp(13px,1.2vw,15px)', lineHeight: 1.55, color: 'var(--text-muted)', marginTop: 10, textWrap: 'pretty' }}>{s.sub}</span>
                    </div>
                  );
                })}
              </div>

              {/* CTA arrives once the Circuit is finished */}
              <div style={{
                marginTop: 8, visibility: ctaVisible ? 'visible' : 'hidden',
                opacity: ctaVisible ? 1 : 0, transform: ctaVisible ? 'none' : 'translateY(6px)',
                transition: 'opacity var(--dur-slow) var(--ease-standard), transform var(--dur-slow) var(--ease-standard)',
              }}>
                <AsmButton onClick={() => go('configure')} tabIndex={ctaVisible ? 0 : -1} aria-hidden={ctaVisible ? 'false' : 'true'}>{a.cta}</AsmButton>
              </div>
            </div>

            {/* stage counter */}
            <div className="cp-asm-counter">
              <span style={{ fontFamily: 'var(--font-display)', fontSize: 12, fontWeight: 'var(--fw-medium)', letterSpacing: 'var(--ls-wider)', textTransform: 'uppercase', color: 'var(--text-faint)' }}>{counter}</span>
            </div>

            {/* scroll hint at the very start */}
            <div aria-hidden="true" className="cp-asm-hint" style={{
              opacity: progress < 0.04 && ready ? 0.7 : 0,
              transition: 'opacity var(--dur-slow) var(--ease-standard)',
            }}>
              <span style={{ fontFamily: 'var(--font-display)', fontSize: 10.5, fontWeight: 'var(--fw-medium)', letterSpacing: 'var(--ls-widest)', textTransform: 'uppercase', color: 'var(--text-faint)' }}>{a.scrollHint}</span>
              <AsmIcon name="chevron-down" size={16} stroke={1.6} color="var(--text-faint)" />
            </div>
          </div>

          {/* progress hairline along the bottom edge */}
          <div aria-hidden="true" style={{ position: 'absolute', left: 0, right: 0, bottom: 0, height: 2, background: 'rgba(27,29,31,0.06)', zIndex: 2 }}>
            <div style={{ height: '100%', width: (progress * 100) + '%', background: 'var(--gradient-gold)', transition: 'width 80ms linear' }} />
          </div>

          {/* preloader — fades out the instant the first frame is ready */}
          <div aria-hidden="true" style={{
            position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column',
            alignItems: 'center', justifyContent: 'center', gap: 18, background: ASM_PLATE_TOP,
            opacity: ready ? 0 : 1, visibility: ready ? 'hidden' : 'visible',
            transition: 'opacity var(--dur-slow) var(--ease-standard), visibility var(--dur-slow)',
          }}>
            <span style={{ fontFamily: 'var(--font-display)', fontSize: 12, fontWeight: 'var(--fw-medium)', letterSpacing: 'var(--ls-wider)', textTransform: 'uppercase', color: 'var(--text-muted)' }}>{a.loading}</span>
            <div style={{ width: 160, height: 2, background: 'rgba(27,29,31,0.1)' }}>
              <div style={{ height: '100%', width: loadPct + '%', background: 'var(--gradient-gold)', transition: 'width 120ms linear' }} />
            </div>
          </div>
        </div>
      </div>
      <div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: 1, background: 'var(--border-hairline)', zIndex: 3 }} />
    </section>
  );
}

window.Assembly = Assembly;
