// master-timeline.jsx — Stitched master cut for "Charter to Code"
//
// Strategy: continuous chapter player. We keep each scene's existing Timeline
// (with its local `useT`) intact, but the master controls which scene is
// "playing" and reflects total runtime in a global progress bar.
//
// Each scene is a chapter with a known duration; the master:
//   - mounts ONE scene at a time (so its local Timeline runs from t=0)
//   - on scene end, advances to next chapter
//   - exposes prev/next, jump-to, play/pause, and a global scrubber that
//     translates a global ms position into (chapterIdx, localMs)
//
// This is the simplest stitched format that respects the per-scene clocks.

const CHAPTERS = [
  { id: 'act1',    label: 'Act I · Cold open → Hook → Pipeline',   dur: 18000, Comp: () => window.SceneAct1 && <window.SceneAct1 />, color: 'var(--orange)' },
  { id: 'act2',    label: 'Act II · Phase 1 · Charter',             dur: 24800, Comp: () => window.SceneAct2 && <window.SceneAct2 />, color: 'var(--orange)' },
  { id: 'act3',    label: 'Act III · Phase 2 · BRD + Eval Loop',    dur: 26800, Comp: () => window.SceneAct3 && <window.SceneAct3 />, color: 'var(--orange)' },
  { id: 'ffd',     label: 'Phase 3 · FFD',                          dur: 22800, Comp: () => window.SceneFFD && <window.SceneFFD />,   color: 'var(--orange)' },
  { id: 'maps',    label: 'Phase 4 · Process Maps',                 dur: 26800, Comp: () => window.SceneMaps && <window.SceneMaps />, color: 'var(--orange)' },
  { id: 'code',    label: 'Phase 5 · Code',                         dur: 26800, Comp: () => window.SceneCode && <window.SceneCode />, color: 'var(--orange)' },
  { id: 'uat',     label: 'Phase 6 · UAT',                          dur: 20800, Comp: () => window.SceneUAT && <window.SceneUAT />,   color: 'var(--orange)' },
  { id: 'endcard', label: 'End card',                                dur: 14000, Comp: () => window.SceneEndCard && <window.SceneEndCard />, color: 'var(--ink)' },
];

const TOTAL = CHAPTERS.reduce((a, c) => a + c.dur, 0);

function chapterOffset(idx) {
  let off = 0;
  for (let i = 0; i < idx; i++) off += CHAPTERS[i].dur;
  return off;
}
function globalToLocal(globalMs) {
  let off = 0;
  for (let i = 0; i < CHAPTERS.length; i++) {
    if (globalMs < off + CHAPTERS[i].dur) {
      return { idx: i, local: globalMs - off };
    }
    off += CHAPTERS[i].dur;
  }
  return { idx: CHAPTERS.length - 1, local: CHAPTERS[CHAPTERS.length - 1].dur };
}

function fmt(ms) {
  const s = Math.floor(ms / 1000);
  const m = Math.floor(s / 60);
  return `${m}:${String(s % 60).padStart(2, '0')}`;
}

function getQueryFlag(name) {
  if (typeof window === 'undefined') return false;
  return new URLSearchParams(window.location.search).has(name);
}

function MasterPlayer() {
  const [globalMs, setGlobalMs] = React.useState(0);
  const [playing, setPlaying] = React.useState(true);
  const rafRef = React.useRef(0);
  const lastRef = React.useRef(performance.now());

  const { idx: chapterIdx, local: localMs } = globalToLocal(globalMs);
  const chapter = CHAPTERS[chapterIdx];

  // Persist position — skipped on `?fresh=1` so the embed always starts clean.
  const fresh = getQueryFlag('fresh');
  React.useEffect(() => {
    if (fresh) {
      localStorage.removeItem('charter2code-master-t');
      return;
    }
    const saved = parseFloat(localStorage.getItem('charter2code-master-t') || '0');
    if (saved > 0 && saved < TOTAL) setGlobalMs(saved);
  }, []);
  React.useEffect(() => {
    if (fresh) return;
    localStorage.setItem('charter2code-master-t', String(globalMs));
  }, [globalMs, fresh]);

  // Drive global clock
  React.useEffect(() => {
    function tick(now) {
      const dt = now - lastRef.current;
      lastRef.current = now;
      if (playing) {
        setGlobalMs((g) => {
          const next = g + dt;
          if (next >= TOTAL) {
            // Loop
            return 0;
          }
          return next;
        });
      }
      rafRef.current = requestAnimationFrame(tick);
    }
    lastRef.current = performance.now();
    rafRef.current = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(rafRef.current);
  }, [playing]);

  // Render the active chapter wrapped in a "frozen-clock" Timeline override.
  // Trick: re-key on chapterIdx so React remounts; the inner Timeline starts
  // at 0, but we drive its `t` externally. Since inner scenes pull `t` from
  // TimelineCtx via Timeline's own raf loop, we instead bypass: render a
  // <ScopedTimeline localMs={localMs} duration={chapter.dur}> shim.

  return (
    <div className="mt-shell">
      <div className="mt-stage">
        <ScaledScene16x9>
          <ScopedTimeline localMs={localMs} duration={chapter.dur} key={chapter.id}>
            {chapter.Comp()}
          </ScopedTimeline>
        </ScaledScene16x9>
      </div>

      <MasterControls
        globalMs={globalMs}
        playing={playing}
        chapterIdx={chapterIdx}
        onSeek={(g) => setGlobalMs(Math.max(0, Math.min(TOTAL, g)))}
        onJumpChapter={(i) => setGlobalMs(chapterOffset(i))}
        onPlayPause={() => setPlaying((p) => !p)}
      />
    </div>
  );
}

// ScopedTimeline — wraps children with a TimelineCtx whose `t` is driven by
// the master, not by an internal raf. Replaces <Timeline> for stitched playback.
// We extract the children's children if they're a <Timeline>: each scene's
// outermost element is `<Timeline duration=... ><CaptionScene>...</></>`.
// To avoid double-Timeline, we look for `.props.children` on a Timeline-like
// element and unwrap. Cleaner: render the children inside our context AND
// suppress the inner Timeline by monkey-patching window.Timeline temporarily.
function ScopedTimeline({ localMs, duration, children }) {
  // Provide our own context value
  const value = React.useMemo(() => ({ t: localMs, duration }), [localMs, duration]);

  // We need to NOT render the inner Timeline's controls/raf. Do this by
  // swapping window.Timeline to a passthrough during this render only.
  // Since render is sync, we save/restore around the children's evaluation.

  return (
    <window.TimelineCtx.Provider value={value}>
      <ScopedRender>{children}</ScopedRender>
    </window.TimelineCtx.Provider>
  );
}

// ScopedRender swaps Timeline to a passthrough fragment so nested scenes
// don't spin their own raf or render scrubbers.
function ScopedRender({ children }) {
  // We expect each Scene component to return `<Timeline ...><CaptionScene>...</></>`.
  // Calling the scene component here would spawn a Timeline. Instead, we
  // intercept by setting window.Timeline = PassthroughTimeline before render.
  // React will use the swapped reference because each scene was transpiled
  // with `<Timeline ...>` referencing the global Timeline at mount time.
  // CAUTION: scenes use `<Timeline>` JSX which compiles to `React.createElement(Timeline, ...)`
  // referencing whatever `Timeline` identifier is in their closure scope.
  // Since each scene file ends with `window.SceneXxx = SceneXxx`, but uses
  // `Timeline` as a free identifier, Babel-standalone resolves it from the
  // global at *render time* — which is now.
  return <>{children}</>;
}

// Passthrough Timeline used only during master playback. Returns just children
// — the parent ScopedTimeline already provides the TimelineCtx.
function PassthroughTimeline({ children }) {
  return <>{children}</>;
}

// Install passthrough swap once master is mounted
function installTimelinePassthrough() {
  if (window.__originalTimeline) return; // already installed
  window.__originalTimeline = window.Timeline;
  window.Timeline = PassthroughTimeline;
}

function MasterControls({ globalMs, playing, chapterIdx, onSeek, onJumpChapter, onPlayPause }) {
  return (
    <div className="mt-controls">
      <button className="mt-pp" onClick={onPlayPause} aria-label={playing ? 'Pause' : 'Play'}>
        {playing ? '❚❚' : '▶'}
      </button>

      <div className="mt-time">{fmt(globalMs)} <span>/</span> {fmt(TOTAL)}</div>

      <div className="mt-track-wrap">
        {/* Chapter tick labels above track */}
        <div className="mt-tick-row">
          {CHAPTERS.map((c, i) => {
            const off = chapterOffset(i);
            const leftPct = (off / TOTAL) * 100;
            return (
              <React.Fragment key={c.id}>
                <div className="mt-tick" style={{ left: `${leftPct}%` }} />
                <div className="mt-tick-label" style={{ left: `${leftPct}%` }}>
                  {String(i + 1).padStart(2, '0')}
                </div>
              </React.Fragment>
            );
          })}
        </div>
        {/* Chapter segmented track */}
        <div className="mt-chapter-track">
          {CHAPTERS.map((c, i) => {
            const off = chapterOffset(i);
            const widthPct = (c.dur / TOTAL) * 100;
            const active = i === chapterIdx;
            const filled = globalMs > off;
            const fillPct = active
              ? Math.max(0, Math.min(100, ((globalMs - off) / c.dur) * 100))
              : (filled ? 100 : 0);
            return (
              <div
                key={c.id}
                className={`mt-seg ${active ? 'active' : ''}`}
                style={{ width: `${widthPct}%` }}
                title={c.label}
                onClick={() => onJumpChapter(i)}
              >
                <div className="mt-seg-fill" style={{ width: `${fillPct}%` }} />
              </div>
            );
          })}
        </div>
        {/* Hidden range input over track for precise scrubbing */}
        <input
          type="range" min={0} max={TOTAL} step={50}
          value={globalMs}
          onChange={(e) => onSeek(parseInt(e.target.value, 10))}
          className="mt-range"
          aria-label="Master scrubber"
        />
      </div>

      <div className="mt-chapters">
        {CHAPTERS.map((c, i) => (
          <button
            key={c.id}
            className={`mt-chapter ${i === chapterIdx ? 'active' : ''}`}
            onClick={() => onJumpChapter(i)}
          >
            <span className="mt-chapter-num">{String(i + 1).padStart(2, '0')}</span>
            <span className="mt-chapter-label">{c.label}</span>
          </button>
        ))}
      </div>
    </div>
  );
}

// ScaledScene16x9 — fits 1920x1080 into the available width while preserving 16:9 letterbox
function ScaledScene16x9({ children }) {
  const wrapRef = React.useRef(null);
  const [scale, setScale] = React.useState(0.5);

  React.useEffect(() => {
    function fit() {
      if (!wrapRef.current) return;
      const w = wrapRef.current.clientWidth;
      const h = wrapRef.current.clientHeight;
      const sX = w / 1920;
      const sY = h / 1080;
      setScale(Math.min(sX, sY));
    }
    fit();
    const ro = new ResizeObserver(fit);
    ro.observe(wrapRef.current);
    window.addEventListener('resize', fit);
    return () => { ro.disconnect(); window.removeEventListener('resize', fit); };
  }, []);

  return (
    <div ref={wrapRef} className="mt-stage-inner">
      <div style={{
        position: 'absolute',
        top: '50%', left: '50%',
        width: 1920, height: 1080,
        transform: `translate(-50%, -50%) scale(${scale})`,
        transformOrigin: 'center',
      }}>
        {children}
      </div>
    </div>
  );
}

// PreviewPlayer — renders only the endcard scene, looping the six-step
// lighting sequence (master cut 2:47-2:54). Each cycle: tagline appears,
// then six phase cells light up one by one. No controls, no topbar, no
// localStorage writes. Used for the embedded preview on charter-to-code.html.
const PREVIEW_LOOP_START = 200;    // tagline begins fading in
const PREVIEW_LOOP_END   = 7200;   // all six cells fully lit
const ENDCARD_DURATION   = 14000;

function PreviewPlayer() {
  const [localMs, setLocalMs] = React.useState(PREVIEW_LOOP_START);
  const rafRef = React.useRef(0);
  const lastRef = React.useRef(performance.now());

  React.useEffect(() => {
    function tick(now) {
      const dt = now - lastRef.current;
      lastRef.current = now;
      setLocalMs((t) => {
        const next = t + dt;
        if (next >= PREVIEW_LOOP_END) return PREVIEW_LOOP_START;
        return next;
      });
      rafRef.current = requestAnimationFrame(tick);
    }
    lastRef.current = performance.now();
    rafRef.current = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(rafRef.current);
  }, []);

  return (
    <div className="mt-shell mt-preview">
      <div className="mt-stage">
        <ScaledScene16x9>
          <ScopedTimeline localMs={localMs} duration={ENDCARD_DURATION} key="endcard-preview">
            {window.SceneEndCard ? <window.SceneEndCard /> : null}
          </ScopedTimeline>
        </ScaledScene16x9>
      </div>
    </div>
  );
}

// Install passthrough BEFORE first render of MasterPlayer
installTimelinePassthrough();

window.MasterPlayer = MasterPlayer;
window.PreviewPlayer = PreviewPlayer;
window.MASTER_TOTAL = TOTAL;
window.MASTER_CHAPTERS = CHAPTERS;
