// Workspace stage components — consumes store, wires primary actions.

// ---------- Reference Pack ----------
function StageReference({ game, session }) {
  const { state, actions } = useStore();
  const [selected, setSelected] = React.useState(new Set());
  const [view, setView] = React.useState('grid'); // grid | list

  // Use store-seeded assets if present; fall back to a seeded snapshot for demo density.
  const fallback = [
    { id: 'a1', name: 'hero_character.png', tag: 'must-use', kind: 'Character', size: '1024×1024', hue: 330 },
    { id: 'a2', name: 'logo_primary.svg', tag: 'must-use', kind: 'Logo', size: '512×180', hue: 45 },
    { id: 'a3', name: 'screen_meta_01.png', tag: 'reference', kind: 'Screenshot', size: '2778×1284', hue: 280 },
    { id: 'a4', name: 'screen_meta_02.png', tag: 'reference', kind: 'Screenshot', size: '2778×1284', hue: 200 },
    { id: 'a5', name: 'ui_buttons.png', tag: 'reference', kind: 'UI kit', size: '512×768', hue: 150 },
    { id: 'a6', name: 'bg_level.png', tag: 'reference', kind: 'Background', size: '1920×1080', hue: 20 },
    { id: 'a7', name: 'app_icon.png', tag: 'must-use', kind: 'App icon', size: '1024×1024', hue: 330 },
    { id: 'a8', name: 'gem_sprites.png', tag: 'reference', kind: 'Sprites', size: '256×1024', hue: 280 },
  ];
  const assets = (session.referencePack?.assets?.length ? session.referencePack.assets : fallback);

  const toggle = (id) => setSelected(prev => {
    const s = new Set(prev); s.has(id) ? s.delete(id) : s.add(id); return s;
  });

  const openUpload = () => {
    actions.openModal({ kind: 'confirm', data: {
      title: 'Upload additional assets',
      body: 'Pick files from your disk. Co-pilot will auto-detect asset types and suggest must-use vs reference tags.',
      cta: 'Open file picker',
      onConfirm: () => {
        // simulate 2 new assets
        const next = [
          { id: 'ax1_' + Date.now(), name: 'win_anim_loop.gif', tag: 'must-use', kind: 'Animation', size: '512×512', hue: 60 },
          { id: 'ax2_' + Date.now(), name: 'cta_localized_ja.png', tag: 'must-use', kind: 'CTA string', size: '400×120', hue: 10 },
        ];
        actions.addAssets(session.id, next);
        actions.pushToast({ kind: 'success', title: 'Uploaded 2 assets', text: 'win_anim_loop.gif, cta_localized_ja.png' });
      },
    }});
  };

  const approveGeneration = () => {
    actions.openModal({ kind: 'confirm', data: {
      title: 'Approve paid generation · $0.18',
      body: 'Co-pilot will generate a winning animation loop and a Japanese CTA string, using your brand assets as reference. Billed to your session budget.',
      cta: 'Generate',
      onConfirm: () => {
        const next = [
          { id: 'gen1_' + Date.now(), name: 'win_anim_loop_v1.webm', tag: 'must-use', kind: 'Animation', size: '512×512', hue: 60 },
          { id: 'gen2_' + Date.now(), name: 'cta_ja_v1.txt', tag: 'must-use', kind: 'CTA string', size: '—', hue: 10 },
        ];
        actions.addAssets(session.id, next);
        actions.updateSession(session.id, { budget: { ...session.budget, spent: session.budget.spent + 0.18 } });
        actions.pushToast({ kind: 'success', title: 'Generation queued', text: 'You\'ll see artifacts here in ~30s. $0.18 billed.' });
      },
    }});
  };

  const removeSelected = () => {
    if (!selected.size) return;
    actions.removeAssets(session.id, Array.from(selected));
    actions.pushToast({ kind: 'info', title: `Removed ${selected.size} asset${selected.size > 1 ? 's' : ''}` });
    setSelected(new Set());
  };

  const retagAsset = (id, tag) => actions.updateAsset(session.id, id, { tag });

  return (
    <div>
      <div className="banner warn">
        <Icon name="alert" size={16} />
        <div>
          <div className="title">2 missing asset types detected</div>
          <div style={{ fontSize: 12, marginTop: 2 }}>
            Co-pilot needs a <b>winning animation loop</b> and a <b>localized CTA string</b>. You can upload or approve paid generation.
          </div>
        </div>
        <div className="actions">
          <button className="btn sm" onClick={openUpload}>Upload</button>
          <button className="btn primary sm" onClick={approveGeneration}>Approve generation · $0.18</button>
        </div>
      </div>

      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 14 }}>
        <div>
          <div className="section-label">Reference pack · {assets.length} assets{selected.size ? ` · ${selected.size} selected` : ''}</div>
          <div style={{ fontSize: 13, color: 'var(--ink-muted)' }}>
            Tagged <b style={{ color: 'var(--ink)' }}>must-use</b> assets will appear in every variant verbatim. <b style={{ color: 'var(--ink)' }}>Reference</b> assets inform style only.
          </div>
        </div>
        <div style={{ display: 'flex', gap: 6 }}>
          {selected.size > 0 && (
            <button className="btn sm danger" onClick={removeSelected}><Icon name="trash" size={13} /> Remove ({selected.size})</button>
          )}
          <button className="btn sm" onClick={() => setView(v => v === 'grid' ? 'list' : 'grid')}>
            <Icon name={view === 'grid' ? 'list' : 'grid'} size={13} /> {view === 'grid' ? 'List' : 'Grid'}
          </button>
          <button className="btn sm" onClick={openUpload}><Icon name="upload" size={13} /> Upload more</button>
        </div>
      </div>

      <div className="ref-grid">
        {assets.map(a => (
          <div
            key={a.id}
            className={`ref-card ${selected.has(a.id) ? 'selected' : ''}`}
            onClick={() => toggle(a.id)}
          >
            <div
              className="ref-thumb"
              style={{ background: window.placeholderUrl(a.kind, 200, 200, a.hue || 200), backgroundSize: 'cover' }}
            >
              <div style={{ position: 'absolute', top: 8, left: 8 }}>
                <span
                  className={`chip ${a.tag === 'must-use' ? 'lime' : ''}`}
                  style={{ fontSize: 9, cursor: 'pointer' }}
                  onClick={(e) => { e.stopPropagation(); retagAsset(a.id, a.tag === 'must-use' ? 'reference' : 'must-use'); }}
                  title="Click to toggle must-use / reference"
                >
                  {a.tag === 'must-use' ? '★ must-use' : 'reference'}
                </span>
              </div>
            </div>
            <div className="ref-meta">
              <div>
                <div className="ref-name">{a.name}</div>
                <div className="ref-size">{a.kind} · {a.size}</div>
              </div>
            </div>
          </div>
        ))}
      </div>

      <div className="hr" />

      <div className="section-label">Provenance log</div>
      <table className="table">
        <thead>
          <tr>
            <th>Asset</th><th>Source</th><th>Detected type</th><th>Added</th><th style={{ textAlign: 'right' }}>Confidence</th>
          </tr>
        </thead>
        <tbody>
          <tr><td className="mono" style={{ fontSize: 12 }}>hero_character.png</td><td>uploaded · maya@northbeam.co</td><td>Character sprite</td><td className="mono" style={{ fontSize: 11, color: 'var(--ink-faint)' }}>14:22</td><td style={{ textAlign: 'right' }}><span className="chip lime">98%</span></td></tr>
          <tr><td className="mono" style={{ fontSize: 12 }}>logo_primary.svg</td><td>uploaded · maya@northbeam.co</td><td>Brand logo</td><td className="mono" style={{ fontSize: 11, color: 'var(--ink-faint)' }}>14:22</td><td style={{ textAlign: 'right' }}><span className="chip lime">99%</span></td></tr>
          <tr><td className="mono" style={{ fontSize: 12 }}>gem_sprites.png</td><td>fetched · app store screenshot</td><td>Sprite sheet</td><td className="mono" style={{ fontSize: 11, color: 'var(--ink-faint)' }}>14:23</td><td style={{ textAlign: 'right' }}><span className="chip cyan">74%</span></td></tr>
        </tbody>
      </table>
    </div>
  );
}

// ---------- Strategy ----------
// Authoring surface: user fills Concept / Hook / Playable Moment / CTA.
// Each field has an inline "✨ Suggest" affordance. Co-pilot chat is NOT the
// primary input here — it lives on Build only.
function StageStrategy({ game, session }) {
  const { actions } = useStore();

  const defaults = {
    concept: '',
    hook: '',
    playableMoment: '',
    cta: '',
  };
  const strategy = { ...defaults, ...(session.strategy || {}) };

  // AI suggestions — seeded, returned on "Suggest" click.
  const suggestions = {
    concept: 'A two-beat hook: near-miss match-3 board resolves in 2.4s, then unlocks a character-led "save the hero" moment. Emphasizes the reward loop, not the mechanic tutorial.',
    hook: 'Board in loss-imminent state. Single tap saves the character. High-contrast VFX on match. No voiceover — copy-driven.',
    playableMoment: 'One guided match (gem → gem → 4-chain), then one free tap, then CTA. Target playtime 14–18s.',
    cta: '"PLAY FREE" · pulsing · appears at 12s if user stalls, otherwise on win-state.',
  };

  const setField = (key, value) => actions.updateStrategy(session.id, { [key]: value });

  const suggest = (key, label) => {
    // simulate brief think
    actions.pushToast({ kind: 'info', title: `Suggesting ${label}…`, text: 'Using your reference pack + brief.', duration: 1200 });
    setTimeout(() => setField(key, suggestions[key]), 700);
  };

  const regenerateAll = () => {
    actions.openModal({ kind: 'confirm', data: {
      title: 'Suggest full strategy from your brief?',
      body: 'Fills every field using your reference pack + brief. You can still edit each one. Existing content will be replaced.',
      cta: 'Suggest',
      onConfirm: () => {
        actions.updateStrategy(session.id, suggestions);
        actions.pushToast({ kind: 'success', title: 'Strategy drafted', text: 'Review and edit before approving.' });
      },
    }});
  };

  const clearAll = () => {
    actions.openModal({ kind: 'confirm', data: {
      title: 'Clear all strategy fields?',
      body: 'Removes all current content. This is an immutable revision — you can restore from history.',
      cta: 'Clear',
      danger: true,
      onConfirm: () => {
        actions.updateStrategy(session.id, { concept: '', hook: '', playableMoment: '', cta: '' });
        actions.pushToast({ kind: 'info', title: 'Strategy cleared' });
      },
    }});
  };

  const requiredFilled = ['concept', 'hook', 'playableMoment', 'cta'].every(k => (strategy[k] || '').trim().length > 10);

  const approveAndBuild = () => {
    if (!requiredFilled) {
      actions.pushToast({ kind: 'warn', title: 'Fill all four fields first', text: 'Concept, hook, playable moment, and CTA — a few sentences each.' });
      return;
    }
    actions.openModal({ kind: 'confirm', data: {
      title: 'Approve strategy & start build',
      body: `Locks this strategy as revision r${(session.strategyRevision || 0) + 1}, commits to ${session.hypotheses?.length || 4} variants. You can still pause or stop the run.`,
      cta: 'Approve & build',
      onConfirm: () => {
        actions.updateSession(session.id, { stage: 2, stageName: 'build', strategyApproved: true });
        actions.pushToast({ kind: 'success', title: 'Strategy approved', text: 'Build starting — head to the Build stage.' });
      },
    }});
  };

  // Hypothesis editing
  const updateHyp = (id, patch) => actions.updateHypothesis(session.id, id, patch);
  const removeHyp = (id) => {
    actions.removeHypothesis(session.id, id);
    actions.pushToast({ kind: 'info', title: 'Hypothesis removed' });
  };
  const addHyp = () => {
    const hyps = session.hypotheses || [];
    const keys = ['A', 'B', 'C', 'D', 'E', 'F'];
    const nextKey = keys[hyps.length] || `V${hyps.length + 1}`;
    const colors = ['magenta', 'cyan', 'lime', '', 'magenta', 'cyan'];
    actions.addHypothesis(session.id, {
      id: 'h_' + Date.now(),
      key: nextKey,
      color: colors[hyps.length] || '',
      name: 'New hypothesis',
      hook: '',
      ipm: 7.0,
    });
  };

  return (
    <div className="strategy-stage">
      {/* Page header */}
      <div className="strat-hero">
        <div>
          <div className="section-label">Creative strategy · r{session.strategyRevision || 0}</div>
          <h2 className="strat-title">Shape the brief, then branch into variants.</h2>
          <div className="strat-subtitle">
            Write the shared creative brief once. Then define 2–4 variants that each differ by one hypothesis.
            Build runs all variants in parallel against a small test budget. <b>Tap ✨ Suggest</b> on anything to get a draft.
          </div>
        </div>
        <div className="strat-hero-actions">
          <button className="btn ghost sm" onClick={clearAll}><Icon name="x" size={12} /> Clear all</button>
          <button className="btn sm" onClick={regenerateAll}><Icon name="sparkle" size={12} /> Draft with AI</button>
        </div>
      </div>

      {/* Step 1: Shared brief */}
      <section className="strat-step">
        <div className="strat-step-head">
          <div className="strat-step-num">1</div>
          <div>
            <div className="strat-step-title">Shared brief</div>
            <div className="strat-step-desc">Applies to every variant. These are the creative constants — pitch, hook, interaction, CTA.</div>
          </div>
          <div className="strat-step-status">
            {['concept','hook','playableMoment','cta'].filter(k => (strategy[k] || '').trim().length > 10).length}/4 filled
          </div>
        </div>

        <div className="strat-brief-card">
          <StrategyField
            label="Concept"
            hint="The one-line pitch. Why does someone tap?"
            value={strategy.concept}
            onChange={(v) => setField('concept', v)}
            onSuggest={() => suggest('concept', 'concept')}
            placeholder="e.g. A near-miss board resolves in the first 2 seconds, then the hero rescues the player."
          />
          <StrategyField
            label="Primary hook"
            hint="What happens in the first 3 seconds — the scroll-stopper."
            value={strategy.hook}
            onChange={(v) => setField('hook', v)}
            onSuggest={() => suggest('hook', 'hook')}
            placeholder="e.g. Board in loss-imminent state. Single tap saves the character."
          />
          <StrategyField
            label="Playable moment"
            hint="The interactive beat — what the user does, and for how long."
            value={strategy.playableMoment}
            onChange={(v) => setField('playableMoment', v)}
            onSuggest={() => suggest('playableMoment', 'playable moment')}
            placeholder="e.g. One guided match, then one free tap, then CTA. Target playtime 14–18s."
          />
          <StrategyField
            label="CTA"
            hint="The call to action — copy, placement, timing."
            value={strategy.cta}
            onChange={(v) => setField('cta', v)}
            onSuggest={() => suggest('cta', 'CTA')}
            placeholder='e.g. "PLAY FREE" · pulsing · appears at 12s if user stalls.'
          />
        </div>
      </section>

      {/* Connector */}
      <div className="strat-connector">
        <div className="strat-connector-line" />
        <div className="strat-connector-label">
          Branch the brief into <b>{(session.hypotheses || []).length} variants</b> — each one tests a different hypothesis
        </div>
        <div className="strat-connector-line" />
      </div>

      {/* Step 2: Variants */}
      <section className="strat-step">
        <div className="strat-step-head">
          <div className="strat-step-num">2</div>
          <div>
            <div className="strat-step-title">Variants to test</div>
            <div className="strat-step-desc">
              Each variant inherits the shared brief, then changes <i>one thing</i> — a hook archetype, a pacing choice, a character treatment.
              Co-pilot will build and run all of them in parallel.
            </div>
          </div>
          <button className="btn ghost sm" onClick={addHyp}><Icon name="plus" size={12} /> Add variant</button>
        </div>

        <div className="strat-variants-grid">
          {(session.hypotheses || []).map((h, i) => (
            <HypothesisEditor
              key={h.id}
              h={h}
              index={i}
              onChange={(patch) => updateHyp(h.id, patch)}
              onRemove={() => removeHyp(h.id)}
            />
          ))}
          <button className="strat-variant-add" onClick={addHyp}>
            <Icon name="plus" size={14} />
            <span>Add another variant</span>
            <span className="hint">Up to 6 · each uses a small share of the test budget</span>
          </button>
        </div>

        <div className="strat-tip">
          <Icon name="info" size={12} />
          <div>
            <b>A good variant set</b> covers at least two hook archetypes (e.g. loss-aversion vs reward) and one expansion dimension (character, pacing, difficulty).
            2–4 variants is the sweet spot to read signal at a 500-impression budget.
          </div>
        </div>
      </section>

      {/* Step 3: Approve */}
      <section className="strat-step">
        <div className="strat-step-head">
          <div className="strat-step-num">3</div>
          <div>
            <div className="strat-step-title">Approve & build</div>
            <div className="strat-step-desc">
              Locks this revision and starts the parallel build run. You can still pause, stop, or revise during build.
            </div>
          </div>
        </div>

        <div className="strat-approve-card" data-ready={requiredFilled ? 'yes' : 'no'}>
          <div>
            <div className="strat-approve-title">
              {requiredFilled ? 'Ready to build' : 'Fill the shared brief first'}
            </div>
            <div className="strat-approve-sub">
              {requiredFilled
                ? `Commits to ${(session.hypotheses || []).length} variants · est. $${((session.hypotheses || []).length * 0.55).toFixed(2)} · ~${(session.hypotheses || []).length * 2 + 4}m build time.`
                : `${4 - ['concept','hook','playableMoment','cta'].filter(k => (strategy[k] || '').trim().length > 10).length} of 4 brief fields still need content.`}
            </div>
          </div>
          <button className="btn accent lg" onClick={approveAndBuild} disabled={!requiredFilled}>
            <Icon name="check" size={14} /> Approve &amp; build {(session.hypotheses || []).length} variants
          </button>
        </div>
      </section>
    </div>
  );
}

function StrategyField({ label, hint, value, onChange, onSuggest, placeholder }) {
  const [focused, setFocused] = React.useState(false);
  const filled = (value || '').trim().length > 10;
  return (
    <div className="strategy-field" data-filled={filled ? 'yes' : 'no'} data-focused={focused ? 'yes' : 'no'}>
      <div className="sf-gutter">
        <div className="sf-dot" data-filled={filled ? 'yes' : 'no'}>
          {filled ? <Icon name="check" size={10} /> : null}
        </div>
      </div>
      <div className="sf-body">
        <div className="sf-head">
          <div className="sf-label">{label}</div>
          <button className="btn ghost sm sf-suggest" onClick={onSuggest} title={`Suggest ${label}`}>
            <Icon name="sparkle" size={10} /> Suggest
          </button>
        </div>
        <div className="sf-hint">{hint}</div>
        <textarea
          className="sf-input"
          value={value || ''}
          placeholder={placeholder}
          onChange={e => onChange(e.target.value)}
          onFocus={() => setFocused(true)}
          onBlur={() => setFocused(false)}
          rows={2}
        />
      </div>
    </div>
  );
}

function HypothesisEditor({ h, index, onChange, onRemove }) {
  const [ipmOpen, setIpmOpen] = React.useState(false);
  return (
    <div className="hyp-card">
      <div className="vc-card-head">
        <div className="vc-card-id">
          <span className={`chip ${h.color || ''}`}>{h.key}</span>
          <span className="vc-card-ordinal">Variant {index + 1}</span>
        </div>
        <button className="btn ghost sm" onClick={onRemove} title="Remove variant">
          <Icon name="trash" size={11} />
        </button>
      </div>

      <input
        className="vc-card-name"
        value={h.name || ''}
        onChange={e => onChange({ name: e.target.value })}
        placeholder="Name this variant (e.g. Near-miss hook)"
      />

      <div className="vc-card-field">
        <label>What's the hypothesis?</label>
        <textarea
          className="vc-card-hook"
          value={h.hook || ''}
          onChange={e => onChange({ hook: e.target.value })}
          placeholder="What's different about this variant vs the shared brief? What audience does it hook? What signal are you testing?"
          rows={3}
        />
      </div>

      <div className="vc-card-foot">
        <div className="vc-ipm">
          <div className="vc-ipm-row">
            <label className="vc-ipm-label">
              Target IPM
              <button
                className="vc-ipm-info"
                onClick={() => setIpmOpen(o => !o)}
                aria-label="What is IPM?"
                type="button"
              >
                <Icon name="info" size={11} />
              </button>
            </label>
            <input
              type="number"
              step="0.1"
              min="0"
              max="15"
              className="hyp-ipm-input"
              value={h.ipm ?? ''}
              onChange={e => onChange({ ipm: parseFloat(e.target.value) || 0 })}
            />
          </div>
          {ipmOpen && (
            <div className="vc-ipm-explain">
              <b>IPM = Installs Per Mille</b> (installs per 1,000 impressions). It's the core efficiency metric for UA creative — a variant with IPM 7.0 drives 7 installs per 1,000 views.
              This is your <i>target</i>; Review will compare actuals against it.
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

// ---------- Build ----------
// Focused baseline-first workspace. Co-pilot chat is the primary input
// surface (docked left). Here the content is the live preview, the run
// log, and a "Commit baseline" milestone. Variants are an OPT-IN
// affordance that only appears after commit — users who just want one
// creative can advance to Review directly.
function StageBuild({ game, session }) {
  const { actions } = useStore();

  // Baseline state: 'running' | 'ready' | 'committed'.
  // Ready means smoke tests pass, Commit is available; user can still chat-iterate.
  // Committed is a soft milestone — tagged as baseline v1; further chat still works.
  // Variants only fan out from the LAST committed version.
  const savedBaseline = session._baselineState || 'ready';
  const [baseline, setBaseline] = React.useState(savedBaseline);
  const [variantsMode, setVariantsMode] = React.useState(session._variantsFanned || 'none'); // none | fanning | done
  const [committedRev, setCommittedRev] = React.useState(session._committedRev || 0);

  const [elapsed, setElapsed] = React.useState(0);
  const [paused, setPaused] = React.useState(false);
  const [runLogOpen, setRunLogOpen] = React.useState(true);

  React.useEffect(() => {
    actions.updateSession(session.id, { _baselineState: baseline, _variantsFanned: variantsMode, _committedRev: committedRev });
  }, [baseline, variantsMode, committedRev]);

  React.useEffect(() => {
    if (paused) return;
    const t = setInterval(() => setElapsed(e => e + 1), 1000);
    return () => clearInterval(t);
  }, [paused]);
  const fmt = (s) => `${Math.floor(s / 60)}:${String(s % 60).padStart(2, '0')}`;

  const togglePause = () => {
    setPaused(p => !p);
    actions.pushToast({ kind: 'info', title: paused ? 'Build resumed' : 'Build paused', text: paused ? 'Continuing from last checkpoint.' : 'No bill accrues while paused.' });
  };

  const commit = () => {
    setCommittedRev(r => r + 1);
    actions.pushToast({ kind: 'success', title: `Baseline v${committedRev + 1} committed`, text: 'Tagged as the current baseline. You can keep iterating via chat — variants will use this tag.' });
  };

  const uncommit = () => {
    actions.openModal({ kind: 'confirm', data: {
      title: 'Revert baseline commit?',
      body: 'Un-tags the current baseline. If variants have fanned out, they remain — but no baseline is marked as current.',
      cta: 'Revert',
      onConfirm: () => {
        setCommittedRev(0);
        actions.pushToast({ kind: 'info', title: 'Baseline un-committed' });
      },
    }});
  };

  const fanOutVariants = () => {
    setVariantsMode('fanning');
    actions.pushToast({ kind: 'success', title: 'Fanning out 4 variants', text: 'Each variant is a diff on the committed baseline — reusing assets, compiled code, physics.' });
    // Simulate completion after a beat
    setTimeout(() => setVariantsMode('done'), 1400);
  };

  // If variants exist, baseline-changes must invalidate them with explicit confirm.
  const onBaselineChange = (label, note) => {
    if (variantsMode === 'none') {
      actions.pushToast({ kind: 'info', title: label, text: note });
      return;
    }
    actions.openModal({ kind: 'confirm', data: {
      title: 'This changes the baseline — variants will need to rebuild',
      body: `The 4 variants currently exist as diffs on baseline v${committedRev}. Applying this change will invalidate them and re-run the fan-out (~$0.96, 2–3 min).`,
      cta: 'Apply & rebuild variants',
      danger: true,
      onConfirm: () => {
        setVariantsMode('fanning');
        actions.pushToast({ kind: 'info', title: 'Baseline updated · variants rebuilding' });
        setTimeout(() => setVariantsMode('done'), 1400);
      },
    }});
  };

  return (
    <div className="build-v2">
      {/* Status strip above the preview */}
      <div className="bv-status">
        <div className="bv-status-left">
          <span className={`dot ${paused ? '' : 'live'}`} />
          <div>
            <div className="bv-status-title">
              {variantsMode === 'fanning' ? 'Fanning out variants' :
                paused ? 'Build paused' : `Baseline ${committedRev > 0 ? `v${committedRev} · committed` : 'ready to commit'}`}
            </div>
            <div className="spec">
              {paused ? 'resume to continue' :
                variantsMode === 'fanning' ? 'reusing baseline · est 2m remaining · ~$0.96' :
                committedRev > 0 ? 'chat to iterate · or fan out variants below' :
                'smoke tests 12/12 passing · chat with co-pilot to request changes'}
            </div>
          </div>
        </div>
        <div className="bv-status-right">
          <div className="bv-cost">
            <span className="spec">spent</span>
            <span className="bv-cost-v">${session.budget.spent.toFixed(2)}</span>
            <span className="spec">/ ${session.budget.maxUsd.toFixed(2)}</span>
          </div>
          <button className="btn sm" onClick={togglePause}>
            <Icon name={paused ? 'sparkle' : 'pause'} size={12} /> {paused ? 'Resume' : 'Pause'}
          </button>
          <button className="btn ghost sm" onClick={() => setRunLogOpen(o => !o)} title="Toggle run log">
            <Icon name="terminal" size={12} /> Run log
          </button>
        </div>
      </div>

      <div className={`bv-main ${runLogOpen ? '' : 'log-closed'}`}>
        {/* Center: big portrait preview */}
        <div className="bv-preview-col">
          <div className="bv-preview-frame">
            <div className="bv-browser-chrome">
              <div className="bv-dots"><span /><span /><span /></div>
              <div className="bv-url">preview.7play.ai/session/{session.id}/baseline{committedRev > 0 ? `@v${committedRev}` : ''}</div>
              <div style={{ display: 'flex', gap: 4 }}>
                <button className="bv-chrome-btn" title="Refresh"><Icon name="refresh" size={11} /></button>
                <button className="bv-chrome-btn" title="Open in new tab"><Icon name="external" size={11} /></button>
              </div>
            </div>
            <div className="bv-preview-stage">
              <div className="bv-phone">
                <PlayablePreview game={game} variant="baseline" state={paused ? 'paused' : 'playing'} />
              </div>
              {committedRev > 0 && (
                <div className="bv-committed-badge">
                  <Icon name="check" size={10} /> Baseline v{committedRev}
                </div>
              )}
            </div>
          </div>

          {/* Milestone / actions row */}
          <div className="bv-milestone">
            <div>
              <div className="spec">smoke tests</div>
              <div className="bv-milestone-v"><Icon name="check" size={12} /> 12 / 12 passing</div>
            </div>
            <div>
              <div className="spec">bundle · fps · mem</div>
              <div className="bv-milestone-v">1.82mb · 60fps · 48mb</div>
            </div>
            <div>
              <div className="spec">last run</div>
              <div className="bv-milestone-v">{fmt(elapsed + 42)} ago</div>
            </div>
            <div className="bv-milestone-actions">
              {committedRev === 0 ? (
                <button className="btn primary" onClick={commit}>
                  <Icon name="check" size={13} /> Commit as baseline
                </button>
              ) : (
                <>
                  <button className="btn ghost sm" onClick={uncommit}><Icon name="revert" size={12} /> Revert commit</button>
                  <button className="btn sm" onClick={commit}><Icon name="check" size={12} /> Re-commit v{committedRev + 1}</button>
                </>
              )}
            </div>
          </div>

          {/* Variants affordance — appears after commit. OPTIONAL. */}
          {committedRev > 0 && (
            <VariantsAffordance
              game={game} session={session}
              mode={variantsMode}
              onFanOut={fanOutVariants}
              onSkip={() => actions.pushToast({ kind: 'info', title: 'Skipping variants', text: 'This session will ship with the baseline as the only creative. Click Next stage to continue.' })}
            />
          )}
        </div>

        {/* Right: collapsible run log (chat thinking events + timeline) */}
        {runLogOpen && (
          <BuildRunLog
            committedRev={committedRev}
            variantsMode={variantsMode}
            paused={paused}
            elapsed={elapsed}
            fmt={fmt}
          />
        )}
      </div>
    </div>
  );
}

// Variants — appears as an opt-in card after baseline commit.
function VariantsAffordance({ game, session, mode, onFanOut, onSkip }) {
  if (mode === 'none') {
    return (
      <div className="bv-variants-offer">
        <div className="bv-offer-icon"><Icon name="layers" size={18} /></div>
        <div className="bv-offer-body">
          <div className="bv-offer-title">Want to A/B-test 4 hook variants?</div>
          <div className="bv-offer-sub">
            Each variant is a <b>diff</b> on your committed baseline — different hook copy, first-moment mechanic, or CTA timing.
            Reuses all your assets and compiled code. Est <b>$0.96</b>, 2–3 min.
          </div>
        </div>
        <div className="bv-offer-actions">
          <button className="btn ghost sm" onClick={onSkip}>Skip · ship baseline only</button>
          <button className="btn primary" onClick={onFanOut}>
            <Icon name="sparkle" size={13} /> Fan out 4 variants
          </button>
        </div>
      </div>
    );
  }

  const variants = [
    { key: 'A', color: 'teal',   name: 'Near-miss hook',    hypothesis: 'Loss-aversion open; board resolves at t=2.4s.', state: mode === 'done' ? 'done' : 'current' },
    { key: 'B', color: 'cyan',   name: 'Reward-first',       hypothesis: 'Coin-rain open at t=0. Dopamine-forward.',      state: mode === 'done' ? 'done' : 'current' },
    { key: 'C', color: 'violet', name: 'Character-led',      hypothesis: 'Mascot pre-roll; 1.2s intro to game.',          state: mode === 'done' ? 'done' : 'queued' },
    { key: 'D', color: 'amber',  name: 'Difficulty ramp',    hypothesis: 'Opens on level 3; skill-gate framing.',         state: mode === 'done' ? 'done' : 'queued' },
  ];

  return (
    <div className="bv-variants-fanned">
      <div className="bv-variants-head">
        <div>
          <div className="bv-variants-title">
            {mode === 'fanning' ? 'Fanning out 4 variants…' : '4 variants ready'}
          </div>
          <div className="spec">all diff'd from baseline v1 · reusing assets, scaffold, physics</div>
        </div>
        {mode === 'done' && (
          <button className="btn sm"><Icon name="arrowRight" size={12} /> Review & approve →</button>
        )}
      </div>

      <div className="bv-variant-cards">
        {variants.map(v => (
          <div key={v.key} className={`bv-variant-mini ${v.state}`}>
            <div className="bv-variant-mini-preview">
              <PlayablePreview game={game} variant={v.key} state={v.state === 'queued' ? 'paused' : 'playing'} />
            </div>
            <div className="bv-variant-mini-body">
              <div className="bv-variant-mini-head">
                <span className={`chip ${v.color}`}>VAR {v.key}</span>
                {v.state === 'done' && <span className="chip lime"><Icon name="check" size={10} /> ready</span>}
                {v.state === 'current' && <span className="chip cyan"><span className="dot live" /> building</span>}
                {v.state === 'queued' && <span className="chip">queued</span>}
              </div>
              <div className="bv-variant-mini-name">{v.name}</div>
              <div className="bv-variant-mini-hyp">{v.hypothesis}</div>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

// Right-column run log — collapsible "thinking events" + timeline + smoke tests.
function BuildRunLog({ committedRev, variantsMode, paused, elapsed, fmt }) {
  const [thinkingOpen, setThinkingOpen] = React.useState(false);

  const thinking = [
    { t: '10:32:14', line: 'Loading reference pack · 8 assets hashed' },
    { t: '10:32:18', line: 'Matched 7 assets to canonical atlas slots' },
    { t: '10:32:22', line: 'Flagged 1 asset for upscale (bg-tile · 512→1024)' },
    { t: '10:32:40', line: 'Baking sprite atlas · 412kb' },
    { t: '10:33:04', line: 'Scene graph: 18 objects · 3 VFX layers · grid 6×7' },
    { t: '10:33:48', line: 'Wiring CTA triggers at t=2.4s, t=6s' },
    { t: '10:34:12', line: 'Compiling core loop · 18.2kb runtime' },
    { t: '10:34:40', line: 'Smoke tests: 12/12 passing' },
  ];

  const steps = [
    { state: 'done', title: 'Reference pack locked', meta: '+4m' },
    { state: 'done', title: 'Asset pipeline · atlases baked', meta: '+3m' },
    { state: 'done', title: 'Scene graph built', meta: '+2m' },
    { state: 'done', title: 'Core loop compiled', meta: '+1m' },
    { state: 'done', title: 'Smoke tests · 12/12', meta: 'now' },
    ...(committedRev > 0 ? [{ state: 'done', title: `Baseline v${committedRev} committed`, meta: 'now' }] : []),
    ...(variantsMode === 'fanning' ? [{ state: 'current', title: 'Fanning out variants · diff compile', meta: 'now' }] : []),
    ...(variantsMode === 'done' ? [{ state: 'done', title: 'Variants A/B/C/D ready', meta: 'now' }] : []),
  ];

  return (
    <div className="bv-runlog">
      <div className="bv-runlog-head">
        <div className="section-label" style={{ margin: 0 }}>Run log</div>
        <div className="spec">elapsed · {fmt(elapsed + 632)}</div>
      </div>

      {/* Collapsible thinking events */}
      <button className="bv-thinking-toggle" onClick={() => setThinkingOpen(o => !o)}>
        <Icon name={thinkingOpen ? 'chevronDown' : 'chevronRight'} size={12} />
        <span>Agent thinking · {thinking.length} events</span>
        <span className="spec" style={{ marginLeft: 'auto' }}>hidden by default</span>
      </button>
      {thinkingOpen && (
        <div className="bv-thinking-list">
          {thinking.map((e, i) => (
            <div key={i} className="bv-thinking-item">
              <span className="spec">{e.t}</span>
              <span>{e.line}</span>
            </div>
          ))}
        </div>
      )}

      <div className="hr" style={{ margin: '10px 0' }} />

      {/* Timeline */}
      <div className="section-label" style={{ margin: '0 0 4px' }}>Timeline</div>
      <div className="timeline" style={{ padding: 0 }}>
        {steps.map((s, i) => (
          <div key={i} className={`tl-item ${s.state}`}>
            <div className="tl-icon">{s.state === 'done' ? '✓' : s.state === 'current' ? '•' : i + 1}</div>
            <div><div className="tl-title">{s.title}</div></div>
            <div className="tl-meta">{s.meta}</div>
          </div>
        ))}
      </div>

      <div className="hr" style={{ margin: '10px 0' }} />

      {/* Smoke tests as a compact list */}
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 6 }}>
        <div className="section-label" style={{ margin: 0 }}>Smoke tests</div>
        <span className="chip lime"><Icon name="check" size={9} /> 12 / 12</span>
      </div>
      <div className="smoke-grid compact">
        {['Launches', 'Assets · 0 missing', 'First frame ≤ 800ms', 'FPS ≥ 58', 'Memory ≤ 64mb', 'No console errors', 'Tap-through', 'CTA fires', 'Win reachable', 'Loss reachable', 'Bundle ≤ 2mb', 'AppLovin spec'].map((label, i) => (
          <div key={i} className="smoke-item done">
            <span className="smoke-dot"><Icon name="check" size={9} /></span>
            <span>{label}</span>
          </div>
        ))}
      </div>
    </div>
  );
}

// ---------- Review ----------
function StageReview({ game, session, onOpenVariant }) {
  const { actions } = useStore();

  // Variants always come from the store so the preview modal can look them up by id.
  const variants = session.variants || [];

  const approve = (v, e) => {
    e.stopPropagation();
    actions.updateVariant(session.id, v.id, { state: 'approved' });
    actions.pushToast({ kind: 'success', title: `Variant ${v.key} approved` });
  };
  const revert = (v, e) => {
    e.stopPropagation();
    actions.updateVariant(session.id, v.id, { state: 'ready' });
    actions.pushToast({ kind: 'info', title: `Variant ${v.key} un-approved` });
  };
  const revise = (v, e) => {
    e.stopPropagation();
    actions.updateVariant(session.id, v.id, { state: 'needs_revision' });
    actions.pushToast({ kind: 'info', title: 'Revision requested', text: `Type your notes in the co-pilot for Variant ${v.key}.` });
  };

  return (
    <div>
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 14 }}>
        <div>
          <div className="section-label">
            {variants.length} variants · {variants.filter(v => v.state === 'approved').length} approved · {variants.filter(v => v.state === 'needs_revision').length} need revision
          </div>
          <div style={{ fontSize: 13, color: 'var(--ink-muted)' }}>
            Click a variant to preview fullscreen. Request revisions via chat — each spawns an immutable new revision.
          </div>
        </div>
        <div style={{ display: 'flex', gap: 6 }}>
          <button className="btn sm" onClick={() => actions.openModal({ kind: 'history', data: { sessionId: session.id } })}>
            <Icon name="history" size={13} /> Revision history
          </button>
          <button className="btn sm" onClick={() => actions.openModal({ kind: 'share', data: { sessionId: session.id } })}>
            <Icon name="share" size={13} /> Share preview link
          </button>
        </div>
      </div>

      <div className="review-grid">
        {variants.map((v) => (
          <div
            key={v.id}
            className={`variant-card ${v.state === 'approved' ? 'approved' : ''}`}
            onClick={() => onOpenVariant && onOpenVariant(v)}
          >
            <div className="vc-head">
              <div className="vc-name">
                <span className={`chip ${v.color || ''}`}>VAR {v.key}</span>
                {v.name}
              </div>
              {v.state === 'approved' && <span className="chip lime"><Icon name="check" size={10} /> approved</span>}
              {v.state === 'needs_revision' && <span className="chip warn">revision requested</span>}
              {v.state === 'ready' && <span className="chip">ready for review</span>}
              {v.state === 'building' && <span className="chip cyan"><span className="dot live" /> building</span>}
              {v.state === 'queued' && <span className="chip">queued</span>}
            </div>

            <div className="vc-preview">
              <PlayablePreview game={game} variant={v.key} state="playing" />
            </div>

            <div className="vc-foot">
              <div className="vc-hypothesis">{v.hypothesis}</div>
              <div className="vc-actions">
                <button className="btn sm" onClick={(e) => { e.stopPropagation(); onOpenVariant && onOpenVariant(v); }}>
                  <Icon name="eye" size={12} /> Open
                </button>
                {v.state !== 'approved'
                  ? <button className="btn sm accent" onClick={(e) => approve(v, e)}><Icon name="check" size={12} /> Approve</button>
                  : <button className="btn sm" onClick={(e) => revert(v, e)}><Icon name="revert" size={12} /> Revert</button>}
                <button className="btn ghost sm" onClick={(e) => revise(v, e)}><Icon name="chat" size={12} /> Revise</button>
                <div className="vc-hist">
                  <Icon name="history" size={11} /> r{v.currentRev || 1} · {v.runtime}
                </div>
              </div>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

// ---------- Export ----------
function StageExport({ game, session }) {
  const { actions } = useStore();

  const variants = session.variants || [];
  const checksFor = (v, i) => {
    if (v.state === 'approved') return [
      { ok: true, text: 'AppLovin MRAID v3.0' },
      { ok: true, text: `Bundle · ${v.runtime} / 500kb` },
      { ok: true, text: 'All CTAs → store URL' },
      { ok: true, text: 'No external network' },
    ];
    if (v.state === 'ready') return [
      { ok: true, text: 'AppLovin MRAID v3.0' },
      { ok: true, text: `Bundle · ${v.runtime} / 500kb` },
      { ok: false, level: 'warn', text: 'Awaiting your approval' },
    ];
    if (v.state === 'needs_revision') return [
      { ok: true, text: 'AppLovin MRAID v3.0' },
      { ok: false, level: 'fail', text: 'Revision pending build' },
      { ok: false, level: 'warn', text: 'Audio asset needs remastering' },
    ];
    return [
      { ok: false, level: 'warn', text: `State: ${v.state}` },
    ];
  };
  const isValid = (v) => v.state === 'approved';
  const approvedCount = variants.filter(isValid).length;

  const pushOne = (v) => {
    actions.openModal({ kind: 'confirm', data: {
      title: `Push Variant ${v.key} to AppLovin`,
      body: 'Uploads to AppLovin ad-review API. Nothing goes live until a human reviews in AppLovin dashboard.',
      cta: 'Push to ad-review',
      onConfirm: () => actions.pushToast({ kind: 'success', title: `Variant ${v.key} pushed`, text: 'Will appear in AppLovin within 2 min.' }),
    }});
  };

  const pushBatch = () => {
    if (!approvedCount) {
      actions.pushToast({ kind: 'warn', title: 'Nothing to export', text: 'Approve variants in Review first.' });
      return;
    }
    actions.openModal({ kind: 'confirm', data: {
      title: `Export ${approvedCount} variant${approvedCount > 1 ? 's' : ''} to AppLovin`,
      body: 'Batch-uploads all approved & valid variants to AppLovin ad-review. Immutable bundles; each traceable to its strategy revision.',
      cta: `Export ${approvedCount} now`,
      onConfirm: () => actions.pushToast({ kind: 'success', title: `Pushed ${approvedCount} variants`, text: 'AppLovin review typically completes within 2 hours.' }),
    }});
  };

  const downloadZip = () => {
    if (!approvedCount) {
      actions.pushToast({ kind: 'warn', title: 'Nothing to download', text: 'Approve variants in Review first.' });
      return;
    }
    actions.pushToast({ kind: 'info', title: `Downloaded ${approvedCount}-variant bundle.zip`, text: 'Manifest, playables, and revision hashes included.' });
  };

  const downloadOne = (v, e) => {
    e.stopPropagation();
    if (!isValid(v)) { actions.pushToast({ kind: 'warn', title: `Variant ${v.key} not ready`, text: 'Approve in Review first.' }); return; }
    actions.pushToast({ kind: 'info', title: `Downloaded variant_${v.key}.zip`, text: `r${v.currentRev || 1} · ${v.runtime}` });
  };

  const rowMenu = (v, e) => {
    e.stopPropagation();
    actions.openModal({ kind: 'confirm', data: {
      title: `Variant ${v.key} · actions`,
      body: 'Archive keeps the bundle in history but removes from this export batch. You can always restore.',
      cta: 'Archive',
      danger: true,
      onConfirm: () => {
        actions.updateVariant(session.id, v.id, { archived: true });
        actions.pushToast({ kind: 'info', title: `Variant ${v.key} archived` });
      },
    }});
  };

  return (
    <div>
      <div className="banner lime">
        <Icon name="check" size={16} />
        <div>
          <div className="title">{approvedCount} of {variants.length} variants ready for AppLovin</div>
          <div style={{ fontSize: 12, marginTop: 2 }}>
            Validation is gated — unapproved or failing variants won't export. Every bundle is immutable and traceable to its strategy run.
          </div>
        </div>
        <div className="actions">
          <button className="btn" onClick={downloadZip}><Icon name="download" size={13} /> Download ZIP ({approvedCount})</button>
          <button className="btn primary" onClick={pushBatch}>
            <Icon name="upload" size={13} /> Export to AppLovin
          </button>
        </div>
      </div>

      <div className="card">
        {variants.map((v, i) => {
          const checks = checksFor(v, i);
          const ready = isValid(v);
          return (
            <div className="export-row" key={v.id}>
              <div>
                <div className="name"><span className={`chip ${v.color || ''}`}>VAR {v.key}</span> {v.name}</div>
                <div className="spec" style={{ marginTop: 4 }}>
                  runtime · {v.runtime} · 9:16 · mraid v3
                </div>
              </div>
              <div className="check-list">
                {checks.map((c, j) => (
                  <div key={j} className={c.ok ? 'ok' : (c.level || 'warn')} style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                    {c.ok ? <Icon name="check" size={12} /> : <Icon name="alert" size={12} />}
                    <span>{c.text}</span>
                  </div>
                ))}
              </div>
              <div style={{ display: 'flex', gap: 6, justifyContent: 'flex-end', flexWrap: 'wrap' }}>
                {ready
                  ? <button className="btn accent sm" onClick={() => pushOne(v)}><Icon name="upload" size={12} /> Push to AppLovin</button>
                  : <button className="btn sm" disabled>Cannot export</button>}
                <button className="btn ghost sm" onClick={(e) => downloadOne(v, e)} title="Download single variant"><Icon name="download" size={12} /></button>
                <button className="btn ghost sm" onClick={(e) => rowMenu(v, e)} title="More actions"><Icon name="more" size={12} /></button>
              </div>
            </div>
          );
        })}
      </div>

      <div className="hr" />

      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 14 }}>
        <div className="card">
          <div className="card-header"><div className="card-title">Bundle destination</div></div>
          <div className="card-body">
            <dl className="kv">
              <dt>Network</dt><dd>AppLovin · ad-review API</dd>
              <dt>Campaign</dt><dd>Q2 · US · Casual LAL</dd>
              <dt>Format</dt><dd>Playable (MRAID v3.0) · 9:16</dd>
              <dt>Budget</dt><dd>500 impressions per variant</dd>
              <dt>Publishing</dt><dd><span className="chip">manual review first</span></dd>
            </dl>
          </div>
        </div>
        <div className="card">
          <div className="card-header"><div className="card-title">Run provenance</div></div>
          <div className="card-body">
            <dl className="kv">
              <dt>Session</dt><dd className="mono" style={{ fontSize: 11 }}>7p_{session.id}_{(session.gameId || 'g').slice(0,2)}_2026-04-18</dd>
              <dt>Reference</dt><dd>{(session.referencePack?.assets?.length || 8)} assets · hash a19f…</dd>
              <dt>Strategy</dt><dd>locked r1 · approved by maya@</dd>
              <dt>Runtime</dt><dd>2D canvas · no Three.js</dd>
              <dt>Model</dt><dd className="mono" style={{ fontSize: 11 }}>claude-haiku-4-5</dd>
            </dl>
          </div>
        </div>
      </div>
    </div>
  );
}

window.StageReference = StageReference;
window.StageStrategy = StageStrategy;
window.StageBuild = StageBuild;
window.StageReview = StageReview;
window.StageExport = StageExport;
