/* Rooms 5-6: generate/preview/edit, export/share */

const { useState: r5UseState, useEffect: r5UseEffect, useMemo: r5UseMemo, useRef: r5UseRef, useCallback: r5UseCallback } = React;

// Scales a full artifact down to exactly fit its container width (never up), and
// collapses the layout box to the scaled height so there's no dead space below.
// Used in the variant compare cards so each preview fills its column cleanly.
function FitToWidth({ children }) {
  const outer = r5UseRef(null);
  const inner = r5UseRef(null);
  const [d, setD] = r5UseState({ s: 1, h: undefined });
  React.useLayoutEffect(() => {
    const measure = () => {
      const cw = outer.current ? outer.current.clientWidth : 0;
      const nw = inner.current ? inner.current.scrollWidth : 0;
      const nh = inner.current ? inner.current.scrollHeight : 0;
      if (cw && nw) {
        const s = Math.min(1, cw / nw);
        setD({ s, h: Math.round(nh * s) });
      }
    };
    measure();
    const ro = new ResizeObserver(measure);
    if (outer.current) ro.observe(outer.current);
    if (inner.current) ro.observe(inner.current);
    if (document.fonts && document.fonts.ready) document.fonts.ready.then(measure);
    return () => ro.disconnect();
  }, []);
  return (
    <div ref={outer} style={{ width: '100%', height: d.h, overflow: 'hidden' }}>
      <div ref={inner} style={{ transform: 'scale(' + d.s + ')', transformOrigin: 'top center', width: 'fit-content', margin: '0 auto' }}>
        {children}
      </div>
    </div>
  );
}

// ============================================================
// ROOM 5 — Generate, preview, edit
// ============================================================
function Room5_Generate({ intake, setIntake = () => {}, strategy, artifacts, setArtifacts, agentStatus, agentErrors = {}, runAgents, regenerateOne, regenerateCampaignPost, runVariants, runRefine, openDeepEdit, artifactStaleKeys = [], strategyStale = false, streamProgress = {} }) {
  const formats = intake.formats || [];
  const [activeFormat, setActiveFormat] = r5UseState(formats[0] || null);
  const [refineInput, setRefineInput] = r5UseState('');
  const [refineField, setRefineField] = r5UseState('headline');
  const [variantState, setVariantState] = r5UseState({}); // {format: {variants, activeIdx}}
  const [compareFormat, setCompareFormat] = r5UseState(null); // when set: side-by-side mode
  const [fullVariants, setFullVariants] = r5UseState(null); // when set: 3-up variant compare modal { format, loading, results }
  const [deviceFrame, setDeviceFrame] = r5UseState('none');

  // Reset device frame when switching to a format that doesn't support it
  r5UseEffect(() => {
    const allowed = window.VENTUM_DEVICE_FRAMES?.[activeFormat] || ['none'];
    if (!allowed.includes(deviceFrame)) setDeviceFrame('none');
  }, [activeFormat]);

  // Keyboard tab navigation via [ and ]
  r5UseEffect(() => {
    const onNav = (e) => {
      const dir = e?.detail?.dir || 1;
      if (formats.length <= 1) return;
      const idx = formats.indexOf(activeFormat);
      const next = (idx + dir + formats.length) % formats.length;
      setActiveFormat(formats[next]);
    };
    window.addEventListener('__ventum_tab_nav', onNav);
    return () => window.removeEventListener('__ventum_tab_nav', onNav);
  }, [activeFormat, formats]);

  r5UseEffect(() => {
    if (!activeFormat && formats[0]) setActiveFormat(formats[0]);
    if (activeFormat && !formats.includes(activeFormat) && formats.length) setActiveFormat(formats[0]);
  }, [formats.join(',')]);

  const artifact = activeFormat ? artifacts[activeFormat] : null;
  const status   = activeFormat ? agentStatus[activeFormat] : null;

  const fieldOptions = artifact ? Object.keys(artifact).filter(k => typeof artifact[k] === 'string' || Array.isArray(artifact[k])) : [];

  const doRefine = async () => {
    if (!artifact || !refineInput.trim()) return;
    const cur = artifact[refineField];
    try {
      const newVal = await runRefine(activeFormat, refineField, cur, refineInput.trim());
      setArtifacts({ ...artifacts, [activeFormat]: { ...artifact, [refineField]: newVal } });
      setRefineInput('');
    } catch (e) {
      console.error(e);
      window.__toast && window.__toast('Refine failed: ' + e.message);
    }
  };

  const doVariants = async () => {
    if (!artifact) return;
    try {
      const variants = await runVariants(activeFormat, artifact);
      const headlineKey = artifact.hook !== undefined ? 'hook' :
                          artifact.headline !== undefined ? 'headline' :
                          artifact.subject !== undefined ? 'subject' : null;
      setVariantState({
        ...variantState,
        [activeFormat]: { key: headlineKey, originals: artifact[headlineKey], variants, activeIdx: -1 }
      });
    } catch (e) {
      window.__toast && window.__toast('Variants failed: ' + e.message);
    }
  };

  const applyVariant = (text) => {
    const v = variantState[activeFormat];
    if (!v || !v.key) return;
    setArtifacts({ ...artifacts, [activeFormat]: { ...artifact, [v.key]: text } });
  };

  const patchArtifact = (next) => {
    setArtifacts({ ...artifacts, [activeFormat]: next });
  };

  // Sync core messaging from the active artifact across the others. Pulls headline /
  // subhead / stat / CTA from the active source and writes them to each other format
  // using that format's preferred field name.
  const syncMessaging = () => {
    if (!artifact) return;
    const src = artifact;
    // Pluck a generic "headline-like" string from the source
    const pluckHeadline = (a) => a.headline || a.hook || a.subject || a.eyebrow || '';
    const pluckSubhead  = (a) => a.subhead || a.preheader || a.body?.[0] || '';
    const pluckStat     = (a) => a.stat_block || a.stat || a.outcome_stat || a.proof_stat || (a.slides?.find(s => s.stat)?.stat) || null;
    const pluckQuote    = (a) => a.proof_quote || a.proof_block || a.quote || (a.slides?.find(s => s.quote) && { quote: a.slides.find(s => s.quote).quote, attribution: a.slides.find(s => s.quote).attribution }) || null;
    const pluckCta      = (a) => a.cta || a.cta_button || a.cta_line || a.primary_cta || (a.slides?.find(s => s.cta)?.cta) || '';

    const h = pluckHeadline(src);
    const sh = pluckSubhead(src);
    const stat = pluckStat(src);
    const quote = pluckQuote(src);
    const cta = pluckCta(src);

    // Apply to a target artifact based on its format's known fields.
    const applyToFormat = (target, targetFormat) => {
      const out = JSON.parse(JSON.stringify(target));
      // Headline mapping
      if (h) {
        if ('headline' in out) out.headline = h;
        else if ('hook' in out) out.hook = h;
        else if ('subject' in out) out.subject = h.slice(0, 60);
      }
      // Subhead mapping
      if (sh) {
        if ('subhead' in out) out.subhead = sh;
        else if ('preheader' in out) out.preheader = sh.slice(0, 80);
        else if ('overview' in out) out.overview = sh;
      }
      // CTA mapping
      if (cta) {
        if ('cta' in out) out.cta = cta;
        else if ('cta_button' in out) out.cta_button = cta;
        else if ('cta_line' in out) out.cta_line = cta;
        else if ('primary_cta' in out) out.primary_cta = cta;
      }
      // Stat mapping
      if (stat && stat.value) {
        if ('stat_block' in out) out.stat_block = { ...out.stat_block, value: stat.value, unit: stat.unit || out.stat_block?.unit };
        else if ('stat' in out && out.stat) out.stat = { ...out.stat, value: stat.value, unit: stat.unit || out.stat?.unit };
        else if ('outcome_stat' in out) out.outcome_stat = { ...out.outcome_stat, value: stat.value, unit: stat.unit || out.outcome_stat?.unit };
        else if ('stat_overlay' in out && out.stat_overlay) out.stat_overlay = { value: stat.value, unit: stat.unit || out.stat_overlay?.unit };
        else if ('proof_stat' in out) out.proof_stat = { ...out.proof_stat, value: stat.value, unit: stat.unit || out.proof_stat?.unit };
        // Decks — push to the proof slide's stat if present
        if (Array.isArray(out.slides)) {
          out.slides = out.slides.map(s => s.kind === 'proof' && s.stat ? { ...s, stat: { value: stat.value, unit: stat.unit || s.stat.unit } } : s);
        }
      }
      // Quote mapping
      if (quote && (quote.quote || quote.text)) {
        const q = quote.quote || quote.text;
        const attr = quote.attribution;
        if ('proof_quote' in out) out.proof_quote = { quote: q, attribution: attr || out.proof_quote?.attribution };
        else if ('proof_block' in out) out.proof_block = { quote: q, attribution: attr || out.proof_block?.attribution };
        else if ('quote' in out && typeof out.quote === 'object') out.quote = { text: q, attribution: attr || out.quote?.attribution };
        if (Array.isArray(out.slides)) {
          out.slides = out.slides.map(s => s.kind === 'proof' && s.quote ? { ...s, quote: q, attribution: attr || s.attribution } : s);
        }
      }
      return out;
    };

    const targets = formats.filter(f => f !== activeFormat && artifacts[f]);
    if (!targets.length) {
      window.__toast && window.__toast('No other artifacts to sync to');
      return;
    }
    const updated = { ...artifacts };
    targets.forEach(f => {
      updated[f] = applyToFormat(updated[f], f);
    });
    setArtifacts(updated);
    window.__toast && window.__toast(`Synced messaging to ${targets.length} artifact${targets.length === 1 ? '' : 's'}`);
  };

  // Open the full 3-up variant compare modal and kick off generation.
  const doFullVariants = async (pickFrom = null) => {
    if (!activeFormat || !artifact) return;
    setFullVariants({ format: activeFormat, loading: true, results: [], usedSeeds: pickFrom });
    try {
      const results = await window.VentumAgents.runFullVariants(activeFormat, intake, strategy, artifact, pickFrom);
      setFullVariants({ format: activeFormat, loading: false, results, usedSeeds: pickFrom });
    } catch (e) {
      console.error(e);
      window.__toast && window.__toast('Variant generation failed — see console');
      setFullVariants(null);
    }
  };

  // Cycle to the next 2 seed flavors. If the current run used [workflow,outcome],
  // next pass tries [proof,timepressed], etc.
  const tryDifferentAngles = () => {
    const options = window.VentumAgents.VARIANT_SEED_OPTIONS || [];
    const allSeeds = options.map(o => o.seed);
    const used = fullVariants?.usedSeeds || allSeeds.slice(0, 2);
    const unused = allSeeds.filter(s => !used.includes(s));
    const next = (unused.length >= 2) ? unused.slice(0, 2) : [...unused, ...allSeeds].slice(0, 2);
    doFullVariants(next);
  };

  // Apply a chosen variant as the new active artifact for this format.
  // Carry the previous artifact's sigs forward so the staleness banner
  // doesn't fire just because we replaced the content under the same intake.
  const applyFullVariant = (artifactToApply) => {
    if (!fullVariants?.format) return;
    setArtifacts(prev => {
      const prevArt = prev[fullVariants.format];
      const sigs = prevArt
        ? { _intake_sig: prevArt._intake_sig, _strategy_sig: prevArt._strategy_sig }
        : {};
      return { ...prev, [fullVariants.format]: { ...artifactToApply, ...sigs } };
    });
    setFullVariants(null);
    window.__toast && window.__toast('Variant applied');
  };

  return (
    <div>
      <div className="room-head">
        <div className="room-num">Step 05 of 06</div>
        <div>
          <h1>Generate · preview · edit</h1>
          <p className="lede">Each deliverable runs through the Copy Agent, the QA pass, and the Vertical-fit check. Click any text to edit it directly, or use the refine bar to nudge a field with natural language.</p>
        </div>
      </div>

      <BriefPills intake={intake} />

      {formats.length === 0 && (
        <div className="empty">
          <h2>No deliverables selected</h2>
          <p>Go back to Step 01 and pick one or more formats.</p>
        </div>
      )}

      {(artifactStaleKeys.length > 0 || strategyStale) && Object.values(agentStatus).some(s => s) && (
        <div style={{ marginBottom: 18, padding: '12px 14px', background: 'rgba(255,176,40,0.08)', borderLeft: '3px solid #d99020', borderRadius: 3, display: 'flex', alignItems: 'center', gap: 12 }}>
          <div style={{ flex: 1, fontSize: 13, color: 'var(--ink-80)' }}>
            <strong style={{ color: 'var(--ink-100)' }}>
              {strategyStale ? 'Strategy + content are based on a previous brief.' : `${artifactStaleKeys.length} of these were generated from an older intake.`}
            </strong> Re-run to refresh against your current brief.
          </div>
          <button className="btn-primary" style={{ padding: '8px 14px', fontSize: 12 }} onClick={runAgents}>
            <span>Regenerate all</span>
          </button>
        </div>
      )}

      {formats.length > 0 && Object.values(agentStatus).every(s => !s) && (
        <div className="empty">
          <h2>Ready to generate.</h2>
          <p>The Copy Agent will draft {formats.length} deliverable{formats.length > 1 ? 's' : ''} in parallel using your strategy brief.</p>
          <button className="btn-primary" style={{ marginTop: 18 }} onClick={runAgents}>
            <span>Run Copy Agent — {formats.length} piece{formats.length > 1 ? 's' : ''}</span>
            <span className="arrow" />
          </button>
        </div>
      )}

      {formats.length > 0 && Object.values(agentStatus).some(s => s) && (
        <div>
          <div className="artifact-tabs">
            {/* Format tabs — scroll horizontally if many formats */}
            <div className="artifact-tabs__tabs">
              {formats.map(f => {
                const st = agentStatus[f];
                const streamed = streamProgress[f];
                return (
                  <button
                    key={f}
                    className={activeFormat === f ? 'active' : ''}
                    onClick={() => setActiveFormat(f)}
                  >
                    {window.VENTUM_BRAND.formats[f]?.name || f}
                    {st === 'live' && streamed > 0 && (
                      <span className="badge mono" style={{ fontSize: 9, letterSpacing: '0.06em' }}>{streamed.toLocaleString()}c</span>
                    )}
                    {st === 'live' && !streamed && <span className="badge">…</span>}
                    {st === 'done' && <span className="badge">✓</span>}
                    {st === 'error' && <span className="badge" style={{ background: '#C0392B', color: 'white' }}>!</span>}
                  </button>
                );
              })}
            </div>
            {/* Action buttons — wrap as a group to the next row when needed */}
            <div className="artifact-tabs__actions">
              {/* Logo color treatment toggle — visible whenever an account
                  logo is in play. Switches between auto (white on dark, color
                  on light), always-mono, and always-color across all artifacts. */}
              {(intake.account_logo_url || intake.account_domain) && (
                <div
                  className="logo-mode-toggle"
                  data-editor-only="true"
                  style={{ display: 'inline-flex', alignItems: 'center', gap: 0, border: '1px solid var(--ink-10)', borderRadius: 3, overflow: 'hidden' }}
                  title="How the account logo renders across artifacts"
                >
                  {[
                    { id: 'auto',  label: 'Auto',  hint: 'White silhouette on dark slides, full color on light' },
                    { id: 'color', label: 'Color', hint: 'Always full color, even on dark backgrounds' },
                    { id: 'mono',  label: 'White', hint: 'Always white silhouette' }
                  ].map(opt => {
                    const active = (intake.logo_color_mode || 'auto') === opt.id;
                    return (
                      <button
                        key={opt.id}
                        onClick={() => setIntake({ ...intake, logo_color_mode: opt.id })}
                        title={opt.hint}
                        style={{
                          background: active ? 'var(--navy)' : 'transparent',
                          color: active ? 'white' : 'var(--ink-60)',
                          border: 'none',
                          padding: '6px 10px',
                          fontFamily: 'var(--mono)',
                          fontSize: 10,
                          letterSpacing: '0.12em',
                          textTransform: 'uppercase',
                          cursor: 'pointer',
                          lineHeight: 1
                        }}
                      >{opt.label}</button>
                    );
                  })}
                </div>
              )}
              {activeFormat && regenerateOne && (
                <button onClick={() => regenerateOne(activeFormat)} title={`Regenerate only ${window.VENTUM_BRAND.formats[activeFormat]?.name || activeFormat}`}>↻ Regenerate this</button>
              )}
              <button onClick={runAgents}>↻ Regenerate all</button>
              {formats.length > 1 && artifact && Object.values(artifacts).filter(Boolean).length > 1 && (
                <button
                  onClick={syncMessaging}
                  title="Push this artifact's headline, stat, quote, and CTA to all other artifacts"
                  style={{ fontSize: 11, padding: '6px 10px' }}
                >⇆ Sync messaging</button>
              )}
              {formats.length > 1 && (
                <select
                  className="select compare-select"
                  value={compareFormat || ''}
                  onChange={(e) => setCompareFormat(e.target.value || null)}
                  title="Show another artifact side-by-side"
                  style={{ fontSize: 11, padding: '5px 8px' }}
                >
                  <option value="">⇆ Compare with…</option>
                  {formats.filter(f => f !== activeFormat).map(f => (
                    <option key={f} value={f}>{window.VENTUM_BRAND.formats[f]?.name || f}</option>
                  ))}
                </select>
              )}
              {compareFormat && (
                <button className="deep-edit-btn" onClick={() => setCompareFormat(null)} title="Close compare view">× Exit compare</button>
              )}
              {artifact && (
                <button className="deep-edit-btn" onClick={() => openDeepEdit(activeFormat)}>⛭ Deep edit</button>
              )}
              {artifact && (
                <select
                  value={artifact._logo_variant || 'auto'}
                  onChange={(e) => patchArtifact({ ...artifact, _logo_variant: e.target.value })}
                  title="Logo variant for this artifact — Pride is opt-in for pride-themed posts"
                  style={{ fontSize: 11, padding: '5px 8px' }}
                >
                  <option value="auto">⬡ Logo: Auto (light/dark)</option>
                  <option value="color">Logo: Color lockup</option>
                  <option value="white">Logo: White lockup</option>
                  <option value="black">Logo: Black mark</option>
                  <option value="grayscale">Logo: Grayscale mark</option>
                  <option value="pride">Logo: Pride mark</option>
                </select>
              )}
            </div>
          </div>

          <div className="gen-layout">
            <div className="gen-sidebar">
              <div className="gen-side-card">
                <h4>Status</h4>
                <div style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 13 }}>
                  {status === 'live' && <><span className="spinner" /><span>Drafting…</span></>}
                  {status === 'done' && <><span style={{ width: 8, height: 8, background: 'var(--efficacy)', borderRadius: '50%' }} /><span>Generated & QA'd</span></>}
                  {status === 'error' && <><span style={{ width: 8, height: 8, background: '#C0392B', borderRadius: '50%' }} /><span>Failed — retry</span></>}
                </div>
              </div>

              {artifact && (
                <div className="gen-side-card">
                  <h4>Variants</h4>
                  <p style={{ fontSize: 12, color: 'var(--ink-60)', lineHeight: 1.5 }}>Generate two full alternate takes (a workflow-led and an outcome-led angle) and pick the one that fits.</p>
                  <button
                    className="btn-primary"
                    style={{ marginTop: 8, fontSize: 12, padding: '8px 12px' }}
                    onClick={doFullVariants}
                    title="Generate two full alternates and choose one"
                  >Generate variants</button>
                </div>
              )}

              {artifact && (
                <div className="gen-side-card">
                  <h4>Image Direction</h4>
                  <p style={{ fontSize: 11.5, color: 'var(--ink-60)', lineHeight: 1.5, fontFamily: 'var(--mono)' }}>
                    {window.VentumAgents.runImageDirectionAgent(artifact, intake)}
                  </p>
                  <button className="btn-ghost" style={{ marginTop: 8, fontSize: 12, padding: '8px 12px' }} onClick={() => {
                    const prompt = window.VentumAgents.runImageDirectionAgent(artifact, intake);
                    navigator.clipboard.writeText(prompt);
                    window.__toast && window.__toast('Image prompt copied');
                  }}>Copy prompt</button>
                </div>
              )}
            </div>

            <div>
              {/* Layout / preview switchers — a normal-flow row ABOVE the stage
                  so they never overlap the artwork (they used to float over the
                  top-right of the artifact). */}
              {artifact && !compareFormat && (
                (window.VENTUM_LAYOUTS && window.VENTUM_LAYOUTS[activeFormat]?.length > 1) ||
                (window.VENTUM_DEVICE_FRAMES?.[activeFormat]?.length > 1)
              ) && (
                <div className="artifact-controls">
                  {window.VENTUM_LAYOUTS && window.VENTUM_LAYOUTS[activeFormat]?.length > 1 && (
                    <div className="layout-switcher">
                      <span className="mono" style={{ fontSize: 9, color: 'var(--ink-40)', letterSpacing: '0.16em', textTransform: 'uppercase', marginRight: 4 }}>Layout</span>
                      {window.VENTUM_LAYOUTS[activeFormat].map((opt) => {
                        const current = artifact._layout || window.VENTUM_LAYOUTS[activeFormat][0].key;
                        const active = current === opt.key;
                        return (
                          <button
                            key={opt.key}
                            className={"layout-pill" + (active ? ' is-active' : '')}
                            onClick={() => patchArtifact({ ...artifact, _layout: opt.key })}
                          >
                            {opt.name}
                          </button>
                        );
                      })}
                    </div>
                  )}
                  {window.VENTUM_DEVICE_FRAMES?.[activeFormat]?.length > 1 && (
                    <div className="layout-switcher device-switcher">
                      <span className="mono" style={{ fontSize: 9, color: 'var(--ink-40)', letterSpacing: '0.16em', textTransform: 'uppercase', marginRight: 4 }}>Preview</span>
                      {window.VENTUM_DEVICE_FRAMES[activeFormat].map(kind => (
                        <button
                          key={kind}
                          className={"layout-pill" + (deviceFrame === kind ? ' is-active' : '')}
                          onClick={() => setDeviceFrame(kind)}
                        >
                          {kind === 'none' ? 'Bare' : kind === 'iphone' ? 'iPhone' : kind === 'email' ? 'Inbox' : 'Browser'}
                        </button>
                      ))}
                    </div>
                  )}
                </div>
              )}
              <div className="artifact-stage">
                <div className="stage-label">PREVIEW · {activeFormat}{compareFormat ? ' ⇆ ' + compareFormat : ''}</div>
                {status === 'live' && (
                  <div style={{ padding: 60, textAlign: 'center', color: 'var(--ink-60)' }}>
                    <span className="spinner" /> <span style={{ marginLeft: 10 }}>Drafting copy…</span>
                  </div>
                )}
                {status === 'error' && (
                  <div className="error-card" style={{ margin: 24 }}>
                    <div className="error-card__head">
                      <span className="error-card__dot" />
                      <span className="error-card__title">Couldn't generate {window.VENTUM_BRAND.formats[activeFormat]?.name || activeFormat}</span>
                    </div>
                    <p className="error-card__body">{agentErrors[activeFormat]?.message || 'Generation failed for this format. Retry — if it keeps failing, the AI might be temporarily unavailable.'}</p>
                    <div className="error-card__actions">
                      <button className="btn-primary" onClick={() => regenerateOne && regenerateOne(activeFormat)}>↻ Retry</button>
                      <button className="btn-ghost" onClick={runAgents}>Retry all</button>
                    </div>
                    {agentErrors[activeFormat]?.technical && (
                      <details className="error-card__details">
                        <summary>Technical details</summary>
                        <code>{agentErrors[activeFormat].technical}</code>
                      </details>
                    )}
                  </div>
                )}
                {artifact && status !== 'live' && !compareFormat && (
                  <AutoFitArtifact
                    format={activeFormat}
                    artifact={artifact}
                    onPatch={patchArtifact}
                    intake={intake}
                    strategy={strategy}
                    onRegeneratePost={regenerateCampaignPost}
                    editableLogo
                    deviceFrame={deviceFrame}
                  />
                )}
                {artifact && compareFormat && artifacts[compareFormat] && (
                  <div className="artifact-compare">
                    <div className="artifact-compare__col">
                      <div className="mono compare-col-label">{window.VENTUM_BRAND.formats[activeFormat]?.name || activeFormat}</div>
                      <div className="artifact-compare__inner">
                        <window.ArtifactRender format={activeFormat} artifact={artifact} onPatch={patchArtifact} intake={intake} strategy={strategy} onRegeneratePost={regenerateCampaignPost} editableLogo />
                      </div>
                    </div>
                    <div className="artifact-compare__col">
                      <div className="mono compare-col-label">{window.VENTUM_BRAND.formats[compareFormat]?.name || compareFormat}</div>
                      <div className="artifact-compare__inner">
                        <window.ArtifactRender
                          format={compareFormat}
                          artifact={artifacts[compareFormat]}
                          onPatch={(updated) => setArtifacts(prev => ({ ...prev, [compareFormat]: updated }))}
                          intake={intake}
                          strategy={strategy}
                          onRegeneratePost={regenerateCampaignPost}
                        />
                      </div>
                    </div>
                  </div>
                )}
              </div>

              {artifact && (
                <div className="refine-toolbar">
                  <span className="label">Refine</span>
                  <select className="select" style={{ width: 160 }} value={refineField} onChange={(e) => setRefineField(e.target.value)}>
                    {fieldOptions.map(f => <option key={f} value={f}>{f}</option>)}
                  </select>
                  <input
                    type="text"
                    placeholder='e.g. "make it shorter and lead with the stat"'
                    value={refineInput}
                    onChange={(e) => setRefineInput(e.target.value)}
                    onKeyDown={(e) => { if (e.key === 'Enter') doRefine(); }}
                  />
                  <button className="btn-primary" onClick={doRefine} style={{ padding: '9px 14px', fontSize: 12 }}>
                    Refine field
                  </button>
                </div>
              )}
            </div>
          </div>
        </div>
      )}

      {/* FULL VARIANTS — 3-up compare modal — portaled to body so no
          ancestor overflow/transform context can trap the scroll. */}
      {fullVariants && ReactDOM.createPortal(
        <div
          className="variants-mask"
          onClick={(e) => { if (e.target === e.currentTarget) setFullVariants(null); }}
        >
          <div className="variants-modal" style={{ display: 'flex', flexDirection: 'column' }}>
            <div className="variants-head">
              <div>
                <h3>Choose a variant · {window.VENTUM_BRAND.formats[fullVariants.format]?.name || fullVariants.format}</h3>
                <p className="mono">Each variant is built from the same brief with a different emphasis. Click "Use this one" to keep the version you want.</p>
              </div>
              <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
                <button className="drawer-close" onClick={() => setFullVariants(null)} aria-label="Close">×</button>
              </div>
            </div>
            <div className="variants-grid" style={{ height: 'calc(100vh - 140px)', overflowY: 'scroll', WebkitOverflowScrolling: 'touch' }}>
              {/* Card 0: original (current artifact) */}
              <div className="variants-card" data-format={fullVariants.format}>
                <div className="variants-card__head">
                  <span className="mono">Original</span>
                  <span className="variants-card__seed">your current draft</span>
                </div>
                <div className="variants-card__preview">
                  <FitToWidth>
                    <window.ArtifactRender format={fullVariants.format} artifact={artifact} onPatch={() => {}} intake={intake} />
                  </FitToWidth>
                </div>
                <div className="variants-card__foot">
                  <p className="variants-card__blurb">The draft you have now — keep it or pick an alternate.</p>
                  <button className="btn-ghost" disabled style={{ opacity: 0.5 }}>Currently applied</button>
                </div>
              </div>
              {/* Cards 1..3: agent-generated variants */}
              {fullVariants.loading
                ? [1, 2].map((i) => (
                    <div key={i} className="variants-card is-loading" data-format={fullVariants.format}>
                      <div className="variants-card__head">
                        <span className="mono">V{i}</span>
                        <span className="variants-card__seed">drafting…</span>
                      </div>
                      <div className="variants-card__preview">
                        <div style={{ padding: 40, textAlign: 'center', color: 'var(--ink-60)' }}>
                          <span className="spinner" />
                          <span style={{ marginLeft: 10, fontSize: 12 }}>Generating variant…</span>
                        </div>
                      </div>
                      <div className="variants-card__foot">
                        <button className="btn-ghost" disabled>Waiting…</button>
                      </div>
                    </div>
                  ))
                : fullVariants.results.map((r, i) => (
                    <div key={r.seed} className={"variants-card" + (!r.ok ? ' is-error' : '')} data-format={fullVariants.format}>
                      <div className="variants-card__head">
                        <span className="mono">V{i+1} · {r.label}</span>
                        <span className="variants-card__seed">{r.seed}</span>
                      </div>
                      <div className="variants-card__preview">
                        {r.ok ? (
                          <FitToWidth>
                            <window.ArtifactRender format={fullVariants.format} artifact={r.artifact} onPatch={() => {}} intake={intake} />
                          </FitToWidth>
                        ) : (
                          <div style={{ padding: 30, color: '#C0392B', fontSize: 12, textAlign: 'center' }}>
                            Generation failed for this variant.
                            <button className="btn-ghost" style={{ marginTop: 10, fontSize: 11 }} onClick={doFullVariants}>Retry all</button>
                          </div>
                        )}
                      </div>
                      <div className="variants-card__foot">
                        {r.blurb && <p className="variants-card__blurb">{r.blurb}</p>}
                        <button
                          className="btn-primary"
                          onClick={() => applyFullVariant(r.artifact)}
                          disabled={!r.ok}
                        >Use this one →</button>
                      </div>
                    </div>
                  ))
              }
            </div>
          </div>
        </div>,
        document.body
      )}
    </div>
  );
}
// ============================================================
// ROOM 6 — QA report + export
// ============================================================
function Room6_Export({ intake, setIntake = () => {}, strategy, artifacts, setArtifacts, library = [], briefId = null, authUser = null, onSaveLibrary, onSaveAsCopy, onNew, onSaveAsTemplate }) {
  // User-tunable preview zoom (A− / A+). Visual only — doesn't affect
  // generation. Persisted to localStorage so it carries across sessions.
  const [previewZoom, _setPreviewZoom] = r5UseState(() => {
    try { const z = parseFloat(localStorage.getItem('ventum-preview-zoom') || '1'); return isNaN(z) ? 1 : z; }
    catch (_) { return 1; }
  });
  const setPreviewZoom = (next) => {
    const z = typeof next === 'function' ? next(previewZoom) : next;
    _setPreviewZoom(z);
    try { localStorage.setItem('ventum-preview-zoom', String(z)); } catch (_) {}
  };
  const formats = intake.formats || [];
  const [activeFormat, setActiveFormat] = r5UseState(formats[0] || null);
  const [shareUrl, setShareUrl] = r5UseState(null);
  const [printScope, setPrintScope] = r5UseState(null); // null = no print; array = formats to print
  // QA autofix: per-row "suggesting" state + pending diff preview modal
  const [fixSuggesting, setFixSuggesting] = r5UseState({}); // {`${format}:${fixId}`: true}
  const [fixPreview, setFixPreview] = r5UseState(null); // { format, fixId, summary, patches }
  // Cross-format consistency check
  const [consistencyState, setConsistencyState] = r5UseState({ status: 'idle', findings: null, error: null });
  const runConsistency = async () => {
    setConsistencyState({ status: 'running', findings: null, error: null });
    try {
      const result = await window.VentumAgents.runConsistencyCheck(artifacts, intake, strategy);
      setConsistencyState({ status: 'done', findings: result.findings || [], error: null });
      const n = (result.findings || []).length;
      window.__toast && window.__toast(n ? `${n} consistency finding${n === 1 ? '' : 's'}` : 'All artifacts consistent');
    } catch (e) {
      console.error('consistency check failed', e);
      setConsistencyState({ status: 'error', findings: null, error: e.message || 'failed' });
      window.__toast && window.__toast('Consistency check failed — see console');
    }
  };
  const requestFix = async (format, fixId, fixDetail) => {
    const key = `${format}:${fixId}`;
    setFixSuggesting(prev => ({ ...prev, [key]: true }));
    try {
      const result = await window.VentumAgents.runAutoFix(artifacts[format], fixId, fixDetail, format, intake, strategy);
      if (!result.patches || result.patches.length === 0) {
        window.__toast && window.__toast(result.summary || 'No change suggested');
      } else {
        setFixPreview({ format, fixId, summary: result.summary, patches: result.patches });
      }
    } catch (e) {
      console.error('autofix failed', e);
      window.__toast && window.__toast('Fix suggestion failed — ' + (e.message || 'unknown'));
    } finally {
      setFixSuggesting(prev => { const n = { ...prev }; delete n[key]; return n; });
    }
  };
  const applyFixPreview = () => {
    if (!fixPreview || !setArtifacts) { setFixPreview(null); return; }
    const { format, patches } = fixPreview;
    const next = window.VentumAgents.applyPatches(artifacts[format], patches);
    const sigs = artifacts[format]
      ? { _intake_sig: artifacts[format]._intake_sig, _strategy_sig: artifacts[format]._strategy_sig }
      : {};
    setArtifacts(prev => ({ ...prev, [format]: { ...next, ...sigs } }));
    window.__toast && window.__toast('Fix applied');
    setFixPreview(null);
  };
  const dismissFixPreview = () => setFixPreview(null);

  // Print as soon as React has actually rendered the new scope into the
  // print-stage DOM. Using requestAnimationFrame ensures layout+paint
  // complete before window.print() captures the page.
  r5UseEffect(() => {
    if (!printScope) return;
    let cancelled = false;
    let raf1, raf2, raf3;
    raf1 = requestAnimationFrame(() => {
      raf2 = requestAnimationFrame(() => {
        raf3 = requestAnimationFrame(() => {
          if (cancelled) return;
          window.print();
          setTimeout(() => setPrintScope(null), 800);
        });
      });
    });
    return () => {
      cancelled = true;
      cancelAnimationFrame(raf1);
      cancelAnimationFrame(raf2);
      cancelAnimationFrame(raf3);
    };
  }, [printScope]);

  // Keyboard tab navigation via [ and ]
  r5UseEffect(() => {
    const onNav = (e) => {
      const dir = e?.detail?.dir || 1;
      if (formats.length <= 1) return;
      const idx = formats.indexOf(activeFormat);
      const next = (idx + dir + formats.length) % formats.length;
      setActiveFormat(formats[next]);
    };
    window.addEventListener('__ventum_tab_nav', onNav);
    return () => window.removeEventListener('__ventum_tab_nav', onNav);
  }, [activeFormat, formats]);

  r5UseEffect(() => {
    if (!activeFormat && formats[0]) setActiveFormat(formats[0]);
  }, [formats.join(',')]);

  const artifact = activeFormat ? artifacts[activeFormat] : null;

  // Aggregate QA across all artifacts
  const qaByFormat = r5UseMemo(() => {
    const out = {};
    formats.forEach(f => {
      if (artifacts[f]) out[f] = window.VentumAgents.runQAChecks(artifacts[f], f, intake);
    });
    return out;
  }, [artifacts, formats.join(','), intake.vertical]);

  const overallStats = r5UseMemo(() => {
    let pass = 0, total = 0;
    Object.values(qaByFormat).forEach(checks => {
      checks.forEach(c => { total++; if (c.pass) pass++; });
    });
    return { pass, total };
  }, [qaByFormat]);

  // Sort by severity so the eye lands on what needs attention: fail → warn → pass.
  // Stable order is preserved within each bucket.
  //
  // Filter out advisory-only warnings (kind === 'warn' with no fixId).
  // These come from the claims library's "topic mentioned without
  // approved wording" pass — they used to render in the QA panel with
  // a broken Fix button. Dropping the fixId in claims-library.js made
  // the button disappear, but the orange row was still showing — which
  // the user reads as "broken QA." Hiding them here gives a clean
  // pass/fail panel; the underlying claims-discipline guidance is
  // already in the brand guide drawer where it belongs.
  const SEVERITY_RANK = { fail: 0, warn: 1, pass: 2 };
  const qa = activeFormat
    ? [...(qaByFormat[activeFormat] || [])]
        .filter(c => !(c.kind === 'warn' && !c.fixId))
        .sort((a, b) => (SEVERITY_RANK[a.kind] ?? 3) - (SEVERITY_RANK[b.kind] ?? 3))
    : [];

  // Performance calibration — closed-loop predicted-vs-actual.
  // teamLogs is fetched from Supabase performance_logs (Phase 4) so
  // calibration aggregates across every operator's logged actuals, not
  // just whatever's been touched in this browser session.
  const [teamLogs, setTeamLogs] = r5UseState([]);
  const [perfModalOpen, setPerfModalOpen] = r5UseState(false);
  const reloadTeamLogs = r5UseCallback(async () => {
    if (!window.VentumStorage?.listAllPerformanceLogs) return;
    try { const logs = await window.VentumStorage.listAllPerformanceLogs(); setTeamLogs(logs); }
    catch (e) { console.warn('listAllPerformanceLogs failed', e); }
  }, []);
  r5UseEffect(() => { reloadTeamLogs(); }, [reloadTeamLogs]);
  const calibration = r5UseMemo(() => {
    try { return window.VentumAgents.runPerformanceCalibration(library, artifacts, teamLogs); }
    catch (_) { return { samples: 0, byPlatform: {} }; }
  }, [library, artifacts, teamLogs]);

  // ============================================================
  // PHASE 3 — Approvals + comments live in Supabase, with realtime
  // sync so changes propagate across all browsers viewing this brief.
  //
  //   approvalsByFormat — map of {format: approval_row} (Supabase shape)
  //   commentsByFormat  — map of {format: [comment_row, ...]}
  //
  // Both load whenever briefId changes; both subscribe to realtime so
  // a teammate's approval or comment appears live for everyone.
  // ============================================================
  const [approvalsByFormat, setApprovalsByFormat] = r5UseState({});
  const [commentsByFormat, setCommentsByFormat]   = r5UseState({});
  const [approvalsLoading, setApprovalsLoading]   = r5UseState(false);

  const reviewerName = authUser?.email ? authUser.email.split('@')[0] : '(you)';
  const reviewerEmail = authUser?.email || null;

  // Load + subscribe on briefId change
  r5UseEffect(() => {
    if (!briefId || !window.VentumStorage) {
      setApprovalsByFormat({});
      setCommentsByFormat({});
      return;
    }
    let cancelled = false;
    setApprovalsLoading(true);

    const reload = async () => {
      try {
        const [appr, allCom] = await Promise.all([
          window.VentumStorage.listApprovals(briefId),
          window.VentumStorage.listAllComments(briefId)
        ]);
        if (cancelled) return;
        setApprovalsByFormat(appr);
        const byFmt = {};
        (allCom || []).forEach(c => {
          if (!byFmt[c.format]) byFmt[c.format] = [];
          byFmt[c.format].push(c);
        });
        setCommentsByFormat(byFmt);
      } catch (e) {
        console.error('approvals load failed', e);
      } finally {
        if (!cancelled) setApprovalsLoading(false);
      }
    };
    reload();

    // Realtime subscription — push updates from anyone editing this brief.
    const unsub = window.VentumStorage.subscribeBrief(briefId, {
      onApproval: (payload) => {
        const row = payload.new || payload.old;
        if (!row) return;
        setApprovalsByFormat(prev => {
          const next = { ...prev };
          if (payload.eventType === 'DELETE') delete next[row.format];
          else next[row.format] = payload.new;
          return next;
        });
      },
      onComment: (payload) => {
        const row = payload.new || payload.old;
        if (!row) return;
        setCommentsByFormat(prev => {
          const next = { ...prev };
          const list = (next[row.format] || []).slice();
          if (payload.eventType === 'DELETE') {
            next[row.format] = list.filter(c => c.id !== row.id);
          } else if (payload.eventType === 'INSERT') {
            if (!list.find(c => c.id === row.id)) list.push(payload.new);
            next[row.format] = list;
          } else {
            const idx = list.findIndex(c => c.id === row.id);
            if (idx >= 0) list[idx] = payload.new;
            next[row.format] = list;
          }
          return next;
        });
      }
    });
    return () => { cancelled = true; unsub(); };
  }, [briefId]);

  const approval = activeFormat ? approvalsByFormat[activeFormat] : null;
  const activeComments = activeFormat ? (commentsByFormat[activeFormat] || []) : [];

  const [commentDraft, setCommentDraft] = r5UseState('');

  // Helper that wraps storage calls + surfaces errors via toast.
  const callStorage = async (fn, errMsg) => {
    if (!briefId) {
      window.__toast && window.__toast('Save the brief first — approvals need a brief ID');
      return null;
    }
    try { return await fn(); }
    catch (e) {
      console.error(errMsg, e);
      window.__toast && window.__toast(e.message || errMsg);
      return null;
    }
  };

  const requestReview = () => callStorage(async () => {
    await window.VentumStorage.upsertApproval(briefId, activeFormat, {
      status: 'in_review',
      requested_by: authUser?.id || null,
      requested_at: new Date().toISOString()
    });
    window.__toast && window.__toast('Marked in review');
  }, 'Mark in-review failed');

  const markApproved = () => callStorage(async () => {
    await window.VentumStorage.upsertApproval(briefId, activeFormat, {
      status: 'approved',
      approved_by: authUser?.id || null,
      approved_at: new Date().toISOString()
    });
    window.__toast && window.__toast('Approved');
  }, 'Approve failed');

  const requestChanges = () => callStorage(async () => {
    await window.VentumStorage.upsertApproval(briefId, activeFormat, {
      status: 'changes_requested',
      changes_requested_at: new Date().toISOString()
    });
    window.__toast && window.__toast('Changes requested');
  }, 'Request changes failed');

  const resetApproval = () => callStorage(async () => {
    await window.VentumStorage.deleteApproval(briefId, activeFormat);
    window.__toast && window.__toast('Approval reset');
  }, 'Reset failed');

  const addComment = () => callStorage(async () => {
    if (!commentDraft.trim()) return;
    await window.VentumStorage.addComment(briefId, activeFormat, commentDraft.trim(), reviewerEmail);
    setCommentDraft('');
  }, 'Comment failed');
  const copyReviewLink = () => {
    // Build a #share= URL that opens directly into Room 6 with the current
    // artifacts (including approval state). Reviewer pastes back the JSON
    // briefcase OR clicks "Approve" + sends a screenshot. Future: Supabase
    // will sync the approval state in real time across reviewers.
    const payload = { intake, strategy, artifacts };
    const compressed = btoa(unescape(encodeURIComponent(JSON.stringify(payload))));
    const url = `${location.origin}${location.pathname}#share=${compressed}&review=${activeFormat || ''}`;
    navigator.clipboard.writeText(url).then(() => {
      window.__toast && window.__toast('Review link copied · share with reviewer');
    }).catch(() => {
      // Some browsers block clipboard without secure context — fallback prompt
      prompt('Copy this review link:', url);
    });
  };
  const approvalColor = (status) => ({
    draft: 'var(--ink-40)',
    in_review: '#C77800',
    changes_requested: '#A33027',
    approved: '#1F5A48'
  })[status] || 'var(--ink-40)';
  const approvalLabel = (status) => ({
    draft: 'Not reviewed',
    in_review: 'In review',
    changes_requested: 'Needs a look',
    approved: 'Ready'
  })[status] || 'Not reviewed';

  // Exports
  const downloadJSON = () => {
    const payload = { intake, strategy, artifacts, generated_at: new Date().toISOString() };
    const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' });
    const a = document.createElement('a');
    a.href = URL.createObjectURL(blob);
    a.download = `ventum-${(intake.vertical || 'piece')}-${Date.now()}.json`;
    a.click();
  };

  // CSV scheduling-calendar export — one row per post, designed to drop
  // into Buffer / Hootsuite / Sprout's bulk-import flow.
  const downloadCalendarCSV = () => {
    const rows = [['platform', 'campaign', 'post_num', 'role', 'suggested_post_time', 'body', 'hashtags', 'visual_concept', 'alt_text', 'cta']];
    const esc = (v) => {
      const s = String(v == null ? '' : v).replace(/"/g, '""');
      return `"${s}"`;
    };
    Object.entries(artifacts).forEach(([format, art]) => {
      if (!art) return;
      const platform =
        format.startsWith('linkedin') ? 'linkedin' :
        format.startsWith('twitter') ? 'twitter' :
        format.startsWith('instagram') ? 'instagram' : format;
      const campaignName = art.campaign_title || window.VENTUM_BRAND.formats[format]?.name || format;
      if (format === 'linkedin_campaign' && Array.isArray(art.posts)) {
        art.posts.forEach((p, i) => {
          const body = [p.hook, '', ...(p.body || []), '', p.cta_line].filter(Boolean).join('\n');
          rows.push([platform, campaignName, p.post_num || (i + 1), p.role || '', p.suggested_post_time || '', body, (p.hashtags || []).join(' '), p.image_direction || '', '', p.cta_line || '']);
        });
      } else if (format === 'instagram_caption_set' && Array.isArray(art.posts)) {
        art.posts.forEach((p, i) => {
          const body = [p.caption_hook, '', ...(p.caption_body || []), '', p.cta_line].filter(Boolean).join('\n');
          rows.push([platform, campaignName, p.post_num || (i + 1), '', '', body, (p.hashtags || []).join(' '), p.visual_concept || '', p.alt_text || '', p.cta_line || '']);
        });
      } else if (format === 'twitter_thread' && Array.isArray(art.thread_tweets)) {
        rows.push([platform, campaignName, 1, 'hook', '', art.hook_tweet || '', '', '', '', '']);
        art.thread_tweets.forEach((t, i) => {
          rows.push([platform, campaignName, t.tweet_num || (i + 2), '', '', t.body || '', '', '', '', '']);
        });
        rows.push([platform, campaignName, (art.thread_tweets.length + 2), 'cta', '', art.cta_tweet || '', (art.hashtags || []).join(' '), '', '', art.cta_tweet || '']);
      } else if (format === 'linkedin_post') {
        const body = [art.hook, '', ...(art.body || []), '', art.cta_line].filter(Boolean).join('\n');
        rows.push([platform, campaignName, 1, '', '', body, (art.hashtags || []).join(' '), art.image_direction || '', '', art.cta_line || '']);
      } else if (format === 'twitter_post') {
        rows.push([platform, campaignName, 1, '', '', art.body || '', (art.hashtags || []).join(' '), art.image_direction || '', '', '']);
      } else if (format === 'instagram_post') {
        const body = [art.caption_hook, '', ...(art.caption_body || []), '', art.cta_line].filter(Boolean).join('\n');
        rows.push([platform, campaignName, 1, '', '', body, (art.hashtags || []).join(' '), art.visual_concept || '', art.alt_text || '', art.cta_line || '']);
      }
    });
    if (rows.length === 1) {
      window.__toast && window.__toast('No social posts to export — generate a post or campaign first');
      return;
    }
    const csv = rows.map(r => r.map(esc).join(',')).join('\n');
    const blob = new Blob([csv], { type: 'text/csv' });
    const a = document.createElement('a');
    a.href = URL.createObjectURL(blob);
    a.download = `ventum-calendar-${Date.now()}.csv`;
    a.click();
    window.__toast && window.__toast(`Calendar exported · ${rows.length - 1} posts`);
  };

  const copyAllText = () => {
    const lines = [];
    formats.forEach(f => {
      if (!artifacts[f]) return;
      lines.push(`========== ${window.VENTUM_BRAND.formats[f]?.name || f} ==========`);
      lines.push(plainText(artifacts[f]));
      lines.push('');
    });
    navigator.clipboard.writeText(lines.join('\n'));
    window.__toast && window.__toast('All copy copied as plain text');
  };

  // Truncate any thrown error message so the toast stays readable but still
  // carries the failure reason. Full stack lives in the console.
  const exportErrText = (e, fallback) => {
    const msg = String(e?.message || e || '').split('\n')[0];
    if (!msg) return fallback;
    return msg.length > 140 ? msg.slice(0, 137) + '…' : msg;
  };

  // Page-size config for the server-side headless renderer. Mirrors
  // the html2canvas FORMAT_PAGES table in pdf-export.js — when those
  // change, change these too. (Kept here rather than imported because
  // pdf-export.js's table lives inside an IIFE.)
  //
  // Numbers are inches. margin: 0 means full-bleed (e.g. decks).
  // perSlide: true triggers per-[data-slide-idx] page-break-after CSS
  // injection on the server side, giving each deck slide its own page.
  const HEADLESS_PDF_PAGE_CONFIG = {
    deck:                          { size: [13.333, 7.5], orientation: 'landscape', margin: 0,    perSlide: true },
    deck_slide:                    { size: [13.333, 7.5], orientation: 'landscape', margin: 0 },
    one_pager:                     { size: [8.5, 11],     orientation: 'portrait',  margin: 0.17 },
    web_hero:                      { size: [8.5, 11],     orientation: 'landscape', margin: 0.26 },
    print_ad:                      { size: [8.5, 11],     orientation: 'portrait',  margin: 0.34 },
    brochure:                      { size: [8.5, 11],     orientation: 'landscape', margin: 0.34 },
    account_brief:                 { size: [8.5, 11],     orientation: 'landscape', margin: 0.34 },
    info_pack:                     { size: [8.5, 11],     orientation: 'portrait',  margin: 0,    multiPage: true },
    case_study_summary:            { size: [8.5, 11],     orientation: 'portrait',  margin: 0.51 },
    linkedin_post:                 { size: [8.5, 11],     orientation: 'portrait',  margin: 0.68 },
    linkedin_carousel:             { size: [8.5, 11],     orientation: 'portrait',  margin: 0.43 },
    email:                         { size: [8.5, 11],     orientation: 'portrait',  margin: 0.43, multiPage: true },
    trade_show_banner:             { size: [8.5, 11],     orientation: 'portrait',  margin: 1.02 },
    linkedin_campaign:             { size: [8.5, 11],     orientation: 'portrait',  margin: 0.34, multiPage: true },
    twitter_thread:                { size: [8.5, 11],     orientation: 'portrait',  margin: 0.34, multiPage: true },
    instagram_caption_set:         { size: [8.5, 11],     orientation: 'portrait',  margin: 0.34, multiPage: true },
    twitter_post:                  { size: [8.5, 11],     orientation: 'portrait',  margin: 0.85 },
    instagram_post:                { size: [8.5, 11],     orientation: 'portrait',  margin: 0.68 },
    partner_announcement_email:    { size: [8.5, 11],     orientation: 'portrait',  margin: 0.34, multiPage: true },
    partner_announcement_slack:    { size: [8.5, 11],     orientation: 'portrait',  margin: 0.85 },
    partner_announcement_linkedin: { size: [8.5, 11],     orientation: 'portrait',  margin: 0.51 },
    partner_announcement_press:    { size: [8.5, 11],     orientation: 'portrait',  margin: 0.51, multiPage: true },
    partner_announcement_allhands: { size: [8.5, 11],     orientation: 'portrait',  margin: 0.34, multiPage: true },
    blog_post:                     { size: [8.5, 11],     orientation: 'portrait',  margin: 0,    multiPage: true },
    white_paper:                   { size: [8.5, 11],     orientation: 'portrait',  margin: 0,    multiPage: true },
    infographic:                   { size: [8.5, 11],     orientation: 'portrait',  margin: 0.17 }
  };

  // Trigger a browser download of a Blob with the given filename.
  // Used by the headless-PDF path; html2canvas saves via jsPDF directly.
  function triggerBlobDownload(blob, filename) {
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = filename;
    document.body.appendChild(a);
    a.click();
    a.remove();
    setTimeout(() => URL.revokeObjectURL(url), 2000);
  }

  // Server-side PDF render. Returns true on success, null when the
  // feature flag is off (caller should skip to fallback silently), or
  // throws on any failure (caller should fall back to html2canvas and
  // surface the error in the console — not as a user-visible failure
  // unless the fallback also fails).
  async function tryHeadlessPdfRender(format, artifact) {
    const flags = window.__featureFlags;
    if (!flags?.flags?.headlessPdf) return null;
    const pageConfig = HEADLESS_PDF_PAGE_CONFIG[format] || null;
    const body = {
      email: authUser?.email || '',
      format,
      artifact,
      intake,
      pageConfig
    };
    window.__pdfRender?.start();
    try {
      const r = await fetch('/api/render-pdf', {
        method: 'POST',
        credentials: 'include',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify(body)
      });
      if (!r.ok) {
        let msg = 'HTTP ' + r.status;
        try { const j = await r.json(); msg = j.error || msg; } catch (_) {}
        throw new Error(msg);
      }
      const blob = await r.blob();
      const elapsedMs = parseInt(r.headers.get('X-Render-Ms') || '0', 10);
      const filename = 'ventum-' + format.replace(/_/g, '-') + '-' + Date.now() + '.pdf';
      triggerBlobDownload(blob, filename);
      return { elapsedMs };
    } finally {
      window.__pdfRender?.stop();
    }
  }

  const downloadPDF = async () => {
    if (!activeFormat || !artifacts[activeFormat]) return;

    // Try the headless-Chromium path first when the feature flag is on
    // for this user. Any failure (network, 4xx, 5xx, render error) falls
    // through silently to html2canvas so the floor stays at today's
    // known-good behavior.
    //
    // EXCEPTION: the carousel renders as a horizontal scroll strip, whose
    // bounding box the headless renderer measures as the clipped (maxWidth)
    // width — producing a one-page PDF that cuts off every frame past the
    // first couple. The html2canvas path captures each [data-carousel-frame]
    // as its own page, so route the carousel straight there.
    const useHeadless = window.__featureFlags?.flags?.headlessPdf && activeFormat !== 'linkedin_carousel';
    if (useHeadless) {
      try {
        const result = await tryHeadlessPdfRender(activeFormat, artifacts[activeFormat]);
        if (result) {
          const secs = (result.elapsedMs / 1000).toFixed(1);
          window.__toast && window.__toast('PDF downloaded · ' + secs + 's (high-quality)');
          return;
        }
      } catch (e) {
        console.warn('[VENTUM_PDF] headless render failed, falling back to html2canvas:', e?.message || e);
        // fall through to html2canvas below
      }
    }

    // html2canvas fallback (today's behavior — unchanged)
    if (window.VENTUM_PDF?.available) {
      window.__toast && window.__toast('Building PDF…');
      try {
        await window.VENTUM_PDF.exportArtifact(activeFormat, artifacts[activeFormat], intake);
        window.__toast && window.__toast('PDF downloaded');
      } catch (e) {
        console.error(e);
        window.__toast && window.__toast('PDF export failed: ' + exportErrText(e, 'falling back to print'));
        setPrintScope([activeFormat]);
      }
    } else {
      setPrintScope([activeFormat]);
    }
  };

  // downloadAllPDF deliberately does NOT use the headless path yet —
  // combining multiple format-specific PDFs server-side would require
  // a pdf-lib merge step. For now, "All PDF" stays on html2canvas
  // (which already produces a combined PDF). The per-format Download
  // button is where the high-quality path matters most.
  const downloadAllPDF = async () => {
    const have = formats.filter(f => artifacts[f]);
    if (!have.length) return;
    if (window.VENTUM_PDF?.available) {
      window.__toast && window.__toast('Building combined PDF…');
      try {
        await window.VENTUM_PDF.exportArtifacts(
          have.map(f => ({ format: f, artifact: artifacts[f] })),
          intake
        );
        window.__toast && window.__toast('PDF downloaded');
      } catch (e) {
        console.error(e);
        window.__toast && window.__toast('PDF export failed: ' + exportErrText(e, 'falling back to print'));
        setPrintScope(have);
      }
    } else {
      setPrintScope(have);
    }
  };

  const downloadPPTX = async () => {
    if (!window.VENTUM_PPTX?.available) {
      window.__toast && window.__toast('PPTX export not loaded — refresh and try again');
      return;
    }
    const f = activeFormat;
    const art = artifacts[f];
    if (!art) return;
    if (f === 'deck') {
      window.__toast && window.__toast('Capturing slides for PPTX…');
      try { await window.VENTUM_PPTX.exportDeckAsScreenshots(art, intake); window.__toast && window.__toast('PPTX downloaded'); }
      catch (e) { console.error(e); window.__toast && window.__toast('PPTX export failed: ' + exportErrText(e, 'see console')); }
    } else if (f === 'deck_slide') {
      window.__toast && window.__toast('Capturing slide for PPTX…');
      try { await window.VENTUM_PPTX.exportDeckSlideAsScreenshot(art, intake); window.__toast && window.__toast('PPTX downloaded'); }
      catch (e) { console.error(e); window.__toast && window.__toast('PPTX export failed: ' + exportErrText(e, 'see console')); }
    } else {
      window.__toast && window.__toast('PPTX export is available for deck formats only');
    }
  };

  const downloadPPTXEditable = async () => {
    if (!window.VENTUM_PPTX?.available) return;
    const f = activeFormat;
    const art = artifacts[f];
    if (!art) return;
    window.__toast && window.__toast('Building PPTX · embedding images…');
    try {
      if (f === 'deck') await window.VENTUM_PPTX.exportDeck(art, intake);
      else if (f === 'deck_slide') await window.VENTUM_PPTX.exportDeckSlide(art, intake);
      window.__toast && window.__toast('PPTX downloaded');
    } catch (e) {
      console.error('[pptx] export failed', e);
      window.__toast && window.__toast('PPTX export failed — see console');
    }
  };

  const makeShareLink = () => {
    // Encode the payload in URL fragment (shareable read-only preview)
    const payload = { intake, strategy, artifacts };
    const compressed = btoa(unescape(encodeURIComponent(JSON.stringify(payload))));
    const url = `${location.origin}${location.pathname}#share=${compressed}`;
    setShareUrl(url);
    navigator.clipboard.writeText(url).then(() => {
      window.__toast && window.__toast('Share link copied');
    });
  };

  // Room-6 keyboard shortcuts: ⌘P / ⌘⇧P / ⌘⇧C / ⌘⇧N.
  // Registered after the action handlers so the closures see the latest state.
  r5UseEffect(() => {
    const onKey = (e) => {
      if (!(e.metaKey || e.ctrlKey)) return;
      // Skip when typing in inputs/textareas
      const tag = (e.target?.tagName || '').toLowerCase();
      if (tag === 'input' || tag === 'textarea' || e.target?.isContentEditable) return;
      const k = e.key.toLowerCase();
      if (k === 'p' && !e.shiftKey) { e.preventDefault(); downloadPDF(); }
      else if (k === 'p' && e.shiftKey) { e.preventDefault(); downloadAllPDF(); }
      else if (k === 'c' && e.shiftKey) { e.preventDefault(); copyAllText(); }
      else if (k === 'n' && e.shiftKey) { e.preventDefault(); onNew && onNew(); }
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  });

  // Approve all formats in the current brief at once. Requires a saved brief id.
  const approveAll = async () => {
    if (!briefId) {
      window.__toast && window.__toast('Save the brief first — approvals need a brief ID');
      return;
    }
    const now = new Date().toISOString();
    try {
      await Promise.all(formats.filter(f => artifacts[f]).map(f =>
        window.VentumStorage.upsertApproval(briefId, f, {
          status: 'approved',
          approved_by: authUser?.id || null,
          approved_at: now
        })
      ));
      window.__toast && window.__toast(`Approved ${formats.length} artifact${formats.length === 1 ? '' : 's'}`);
    } catch (e) {
      console.error('approveAll failed', e);
      window.__toast && window.__toast('Approve all failed — see console');
    }
  };

  return (
    <div>
      <div className="room-head">
        <div className="room-num">Step 06 of 06</div>
        <div>
          <h1>Review · QA · export</h1>
          <p className="lede">Brand QA, claims review, vertical-fit score — then export to PDF, copy, JSON, or a shareable link. Save to library to reuse the brief later.</p>
        </div>
      </div>

      {/* ---- Edit audit line: who last touched this saved brief ---- */}
      {briefId && (() => {
        const entry = library.find(e => e.id === briefId);
        if (!entry || !entry._editor_email || !entry.updated_at) return null;
        const editorIsAuthor = entry._last_edited_by === entry._created_by;
        const editorIsYou = authUser?.id && entry._last_edited_by === authUser.id;
        const editorName = editorIsYou ? 'you' : entry._editor_email.split('@')[0];
        const ago = (() => {
          const ms = Date.now() - new Date(entry.updated_at).getTime();
          const m = Math.floor(ms / 60000);
          if (m < 1) return 'just now';
          if (m < 60) return m + 'm ago';
          const h = Math.floor(m / 60);
          if (h < 24) return h + 'h ago';
          const d = Math.floor(h / 24);
          return d + 'd ago';
        })();
        return (
          <div className="mono" data-editor-only="true" style={{
            fontSize: 10, letterSpacing: '0.1em', color: 'var(--ink-40)',
            marginBottom: 12, display: 'flex', alignItems: 'center', gap: 6,
            textTransform: 'uppercase'
          }}>
            <span style={{ width: 6, height: 6, borderRadius: 3, background: 'var(--teal)', display: 'inline-block' }} />
            {editorIsAuthor ? 'Saved' : 'Edited'} by {editorName} · {ago}
            {!editorIsAuthor && entry._author_email && (
              <span style={{ marginLeft: 6, opacity: 0.7 }}>· originally saved by {entry._author_email.split('@')[0]}</span>
            )}
          </div>
        );
      })()}

      {/* ---- Format tabs (shared across preview / actions / reviews) ---- */}
      {formats.length > 1 && (
        <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 18, flexWrap: 'wrap' }}>
          <div className="chip-row" style={{ margin: 0 }}>
            {formats.map(f => {
              const checks = qaByFormat[f] || [];
              const fails = checks.filter(c => !c.pass).length;
              const aStatus = approvalsByFormat[f]?.status;
              const aDot = aStatus === 'approved' ? '●' : aStatus === 'changes_requested' ? '●' : aStatus === 'in_review' ? '○' : '';
              const aColor = aStatus === 'approved' ? '#1F5A48' : aStatus === 'changes_requested' ? '#A33027' : aStatus === 'in_review' ? '#C77800' : 'var(--ink-40)';
              const commentCount = (commentsByFormat[f] || []).length;
              return (
                <button key={f} className={"chip" + (activeFormat === f ? " selected" : "")} onClick={() => setActiveFormat(f)}>
                  {window.VENTUM_BRAND.formats[f]?.name || f}
                  {fails > 0 ? ` · ${fails}!` : ' ✓'}
                  {aDot && <span style={{ marginLeft: 6, color: aColor }}>{aDot}</span>}
                  {commentCount > 0 && <span style={{ marginLeft: 4, fontSize: 9, color: 'var(--ink-40)' }}>{commentCount}c</span>}
                </button>
              );
            })}
          </div>
          {briefId && (() => {
            const unapproved = formats.filter(f => artifacts[f] && approvalsByFormat[f]?.status !== 'approved').length;
            if (unapproved === 0) return null;
            return (
              <button
                onClick={approveAll}
                className="r6-action r6-action--accent"
                title={`Mark all ${unapproved} artifact${unapproved === 1 ? '' : 's'} ready to share`}
                style={{ marginLeft: 'auto' }}
              >
                <span className="r6-action-icon">★</span>
                <span>Mark all ready · {unapproved}</span>
              </button>
            );
          })()}
        </div>
      )}

      {/* ---- 1. PREVIEW + REVIEW SIDE-BY-SIDE ---- */}
      {/* Preview takes ~60%, compact review cards stack on the right ~40%.
          Preview area scrolls internally so the action bar stays visible.
          We measure the artifact's native width and pick a scale that fits
          the available column width — no more compress-and-clip. */}
      {(() => null)()}
      <div className="r6-split">
        <div className="r6-preview" data-editor-only="true">
          <div className="r6-preview__head">
            <span className="mono r6-preview__eyebrow">
              {window.VENTUM_BRAND.formats[activeFormat]?.name || activeFormat || 'No artifact'} · preview
            </span>
            {artifact && (
              <span className="mono r6-preview__title" title={artifact.headline || artifact.hook || artifact.subject || ''}>
                {(artifact.headline || artifact.hook || artifact.subject || artifact.intro_line || artifact.campaign_title || '').slice(0, 80)}
              </span>
            )}
            {artifact && approval?.status && (
              <span className="mono" style={{ marginLeft: 'auto', fontSize: 10, padding: '3px 8px', borderRadius: 2, color: 'white', background: approvalColor(approval.status), letterSpacing: '0.14em', textTransform: 'uppercase' }}>{approvalLabel(approval.status)}</span>
            )}
          </div>
          <div className="r6-preview__scroll">
            {artifact ? (
              <AutoFitArtifact
                format={activeFormat}
                artifact={artifact}
                intake={intake}
                zoom={previewZoom}
                onPatch={(updated) => setArtifacts(prev => ({ ...prev, [activeFormat]: { ...updated, _intake_sig: prev[activeFormat]?._intake_sig, _strategy_sig: prev[activeFormat]?._strategy_sig } }))}
              />
            ) : (
              <div className="r6-preview__empty">
                <div className="mono" style={{ fontSize: 10, letterSpacing: '0.16em', color: 'var(--ink-40)', textTransform: 'uppercase', marginBottom: 8 }}>No artifact selected</div>
                <h3 style={{ fontSize: 17, margin: 0, color: 'var(--ink-80)', letterSpacing: '-0.01em' }}>Pick a deliverable above to preview it here</h3>
                <p style={{ fontSize: 13, color: 'var(--ink-60)', lineHeight: 1.5, marginTop: 8, maxWidth: 420 }}>
                  {formats.length === 0
                    ? 'Go back to Step 1 and choose a format, then run Generate.'
                    : 'Click any tab above. Each preview is a final-state mock you can copy, export to PDF, or share.'}
                </p>
              </div>
            )}
          </div>
        </div>

        {/* RIGHT — compact review cards (collapsible, dense) */}
        <div className="r6-reviews" data-editor-only="true">
          {/* Rep-facing "Ready to share" signal — plain-language verdict on the
              things that actually affect whether a deck is safe to send: sourced
              figures, approved claim wording, on-brand (no emoji). Advisory
              polish checks (length, discovery) are intentionally excluded. */}
          {activeFormat && (() => {
            const share = (qa || []).filter(q => q.kind !== 'pass' && (q.kind === 'fail' || /fact check|claims|emoji/i.test(q.label)));
            const ready = share.length === 0;
            return (
              <div className={"r6-share " + (ready ? "r6-share--ready" : "r6-share--check")}>
                <div className="r6-share-head">
                  <span className="r6-share-badge">{ready ? '✓' : share.length}</span>
                  <div className="r6-share-copy">
                    <div className="r6-share-title">{ready ? 'Ready to share' : `${share.length} thing${share.length > 1 ? 's' : ''} to check before sharing`}</div>
                    <div className="r6-share-sub">{ready
                      ? 'Figures are sourced, claims use approved wording, and the tone is on-brand.'
                      : 'Resolve these so the deck is safe to send to a client.'}</div>
                  </div>
                </div>
                {!ready && (
                  <div className="r6-share-items">
                    {share.map((q, i) => (
                      <div key={i} className="r6-share-item">
                        <div style={{ minWidth: 0, flex: 1 }}>
                          <div className="r6-share-item-title">{q.label.replace(/^Fact check — /, '').replace(/^Claims /i, 'Claim: ')}</div>
                          <div className="r6-share-item-detail">{q.detail}</div>
                        </div>
                        {q.fixId && q.kind !== 'pass' && (
                          <button className="r6-qa-fix" disabled={!!fixSuggesting[activeFormat + ':' + q.fixId]}
                            onClick={() => requestFix(activeFormat, q.fixId, q.fixDetail || q.detail)}>
                            {fixSuggesting[activeFormat + ':' + q.fixId] ? '…' : 'Fix'}</button>
                        )}
                      </div>
                    ))}
                  </div>
                )}
              </div>
            );
          })()}
          <ReviewCard
            label="QA"
            stat={(() => {
              // Count only kind === 'pass' as actually passing. Warnings
              // (severity === 'warn') used to be counted as pass because
              // their `pass: true` flag indicates "not a blocker" — but
              // that produced the "13/13 pass" header next to visible
              // orange warning rows, which read like a UI bug. Counting
              // warnings as not-pass for the header keeps the math
              // honest. (The tone calc below already used kind, so the
              // tile color was already right; only the number was off.)
              const truePass = qa.filter(q => q.kind === 'pass').length;
              const warnings = qa.filter(q => q.kind === 'warn').length;
              return warnings
                ? `${truePass}/${qa.length} pass · ${warnings} advisory`
                : `${truePass}/${qa.length} pass`;
            })()}
            tone={qa.some(q => q.kind === 'fail') ? 'fail' : qa.some(q => q.kind === 'warn') ? 'warn' : 'pass'}
            defaultOpen
          >
            <div className="r6-qa-list">
              {qa.slice(0, 60).map((q, i) => (
                <div key={i} className={"r6-qa-row r6-qa-row--" + q.kind}>
                  <span className={"r6-qa-dot " + q.kind}>{q.kind === 'pass' ? '✓' : q.kind === 'fail' ? '✗' : '!'}</span>
                  <div style={{ minWidth: 0, flex: 1 }}>
                    <div className="r6-qa-label">{q.label}</div>
                    <div className="r6-qa-detail" title={q.detail}>{q.detail}</div>
                  </div>
                  {q.fixId && q.kind !== 'pass' && (
                    <button
                      onClick={() => requestFix(activeFormat, q.fixId, q.fixDetail || q.detail)}
                      disabled={!!fixSuggesting[activeFormat + ':' + q.fixId]}
                      className="r6-qa-fix"
                    >{fixSuggesting[activeFormat + ':' + q.fixId] ? '…' : 'Fix'}</button>
                  )}
                </div>
              ))}
            </div>
          </ReviewCard>

          {activeFormat && artifacts[activeFormat] && (
            <ReviewCard
              label="Review status"
              stat={briefId ? approvalLabel(approval?.status || 'draft') : 'unsaved'}
              tone={!briefId ? 'idle' : approval?.status === 'approved' ? 'pass' : approval?.status === 'changes_requested' ? 'warn' : 'idle'}
              defaultOpen
            >
              {!briefId ? (
                <div style={{ padding: '4px 0' }}>
                  <p style={{ fontSize: 12, color: 'var(--ink-60)', lineHeight: 1.5, margin: '0 0 8px' }}>
                    Save this brief to the shared library so the team can weigh in. Status and comments sync live across everyone signed in.
                  </p>
                  <button onClick={onSaveLibrary} className="r6-mini-btn r6-mini-btn--ok">★ Save & enable review</button>
                </div>
              ) : (
                <>
                  <div className="mono" style={{ fontSize: 9, color: 'var(--ink-40)', letterSpacing: '0.1em', textTransform: 'uppercase', marginBottom: 6 }}>
                    Signed in as {reviewerName}
                  </div>
                  <div style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
                    <button onClick={markApproved} className={"r6-mini-btn r6-mini-btn--ok" + (approval?.status === 'approved' ? ' is-active' : '')} title="Mark this ready to share">Mark ready</button>
                    <button onClick={requestChanges} className={"r6-mini-btn r6-mini-btn--warn" + (approval?.status === 'changes_requested' ? ' is-active' : '')} title="Flag for another look">Needs a look</button>
                    <button onClick={copyReviewLink} className="r6-mini-btn" title="Copy review link">⧉</button>
                    {approval && <button onClick={resetApproval} className="r6-mini-btn" title="Clear status">Reset</button>}
                  </div>
                  {activeComments.length > 0 && (
                    <div style={{ marginTop: 8, maxHeight: 180, overflowY: 'auto' }}>
                      {activeComments.map((c) => (
                        <div key={c.id} style={{ paddingBottom: 6, marginBottom: 6, borderBottom: '1px solid var(--ink-05)' }}>
                          <div className="mono" style={{ fontSize: 9, color: 'var(--ink-60)', letterSpacing: '0.1em', textTransform: 'uppercase', display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: 6 }}>
                            <span>{(c.author_name || '').split('@')[0] || 'anon'} · {new Date(c.created_at).toLocaleString(undefined, { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' })}</span>
                            {c.author_id && authUser?.id === c.author_id && (
                              <button
                                onClick={async () => {
                                  try { await window.VentumStorage.deleteComment(c.id); } catch (e) { console.error(e); }
                                }}
                                style={{ background: 'transparent', border: 'none', color: 'var(--ink-40)', cursor: 'pointer', padding: 0, fontSize: 11 }}
                                title="Delete your comment"
                              >×</button>
                            )}
                          </div>
                          <div style={{ fontSize: 11.5, color: 'var(--ink-100)', marginTop: 2, lineHeight: 1.4 }}>{c.body}</div>
                        </div>
                      ))}
                    </div>
                  )}
                  <div style={{ display: 'flex', gap: 4, marginTop: 6 }}>
                    <textarea
                      value={commentDraft}
                      onChange={(e) => setCommentDraft(e.target.value)}
                      placeholder="Add comment…"
                      rows={2}
                      style={{ flex: 1, padding: '5px 8px', border: '1px solid var(--ink-10)', borderRadius: 2, fontSize: 11.5, fontFamily: 'inherit', resize: 'vertical' }}
                    />
                    <button onClick={addComment} disabled={!commentDraft.trim()} className="r6-mini-btn" style={{ alignSelf: 'flex-end' }}>Post</button>
                  </div>
                  <div className="mono" style={{ marginTop: 8, fontSize: 9, color: 'var(--teal)', letterSpacing: '0.14em', textTransform: 'uppercase', display: 'flex', alignItems: 'center', gap: 6 }}>
                    <span style={{ width: 6, height: 6, borderRadius: 3, background: 'var(--teal)', display: 'inline-block', animation: 'pulse 2s infinite' }} />
                    Live · everyone signed in sees updates instantly
                  </div>
                </>
              )}
            </ReviewCard>
          )}

        </div>
      </div>

      {/* ---- 2. COMPACT ACTION BAR (export · save) ---- */}
      <div className="r6-actionbar" data-editor-only="true">
        <div className="r6-actionbar__group">
          <button className="r6-action" onClick={downloadPDF} title={`Save ${window.VENTUM_BRAND.formats[activeFormat]?.name || 'this'} as PDF (⌘P)`}>
            <span className="r6-action-icon">↓</span><span>PDF</span>
            <span className="r6-action-kbd">⌘P</span>
          </button>
          {formats.length > 1 && (
            <button className="r6-action" onClick={downloadAllPDF} title={`All ${formats.length} deliverables, one per page (⌘⇧P)`}>
              <span className="r6-action-icon">↓</span><span>All PDF</span>
              <span className="r6-action-kbd">⌘⇧P</span>
            </button>
          )}
          {(activeFormat === 'deck' || activeFormat === 'deck_slide') && (
            <button className="r6-action r6-action--accent" onClick={downloadPPTXEditable} title="PowerPoint — fully editable text, charts, and images. Opens in PowerPoint / Keynote / Google Slides.">
              <span className="r6-action-icon">↓</span><span>PPTX</span>
            </button>
          )}
          <button className="r6-action" onClick={copyAllText} title="All artifacts as plain text (⌘⇧C)">
            <span className="r6-action-icon">⧉</span><span>Copy all</span>
            <span className="r6-action-kbd">⌘⇧C</span>
          </button>
        </div>
        <span className="r6-actionbar__rule" />
        <div className="r6-actionbar__group">
          {/* Preview zoom — visual-only A− / A+ that scales the artifact
              preview without re-generating. Stored locally so a teammate's
              preference doesn't override yours. */}
          <button className="r6-action" onClick={() => setPreviewZoom(z => Math.max(0.6, Number((z - 0.1).toFixed(2))))} title="Smaller preview (A−)">
            <span className="r6-action-icon" style={{ fontSize: 11 }}>A−</span>
          </button>
          <span className="mono" style={{ fontSize: 10, padding: '0 4px', color: 'var(--ink-60)', letterSpacing: '0.08em' }}>{Math.round(previewZoom * 100)}%</span>
          <button className="r6-action" onClick={() => setPreviewZoom(z => Math.min(1.6, Number((z + 0.1).toFixed(2))))} title="Larger preview (A+)">
            <span className="r6-action-icon" style={{ fontSize: 13 }}>A+</span>
          </button>
        </div>
        <span className="r6-actionbar__rule" />
        <div className="r6-actionbar__group">
          <button
            className="r6-action r6-action--accent"
            onClick={onSaveLibrary}
            title={briefId
              ? 'Update the saved brief (same library entry) — ⌘S'
              : 'Save brief to shared library — ⌘S'}
          >
            <span className="r6-action-icon">★</span>
            <span>{briefId ? 'Update' : 'Save'}</span>
            <span className="r6-action-kbd">⌘S</span>
          </button>
          {briefId && onSaveAsCopy && (
            <button
              className="r6-action"
              onClick={onSaveAsCopy}
              title="Save as a new copy without affecting the original"
            >
              <span className="r6-action-icon">⎘</span>
              <span>Save as copy</span>
            </button>
          )}
          {onSaveAsTemplate && (
            <button
              className="r6-action"
              onClick={onSaveAsTemplate}
              title="Save this brief as a starting template — your team can spin up new briefs from it"
            >
              <span className="r6-action-icon">◫</span>
              <span>Save as template</span>
            </button>
          )}
          <button className="r6-action" onClick={onNew} title="Reset wizard (⌘⇧N)">
            <span className="r6-action-icon">+</span><span>New</span>
            <span className="r6-action-kbd">⌘⇧N</span>
          </button>
        </div>
      </div>


      {/* Print-only stage: portaled to body so it lives outside .studio-app's
          fixed-positioning grid context — the only reliable way to keep print
          isolation clean. Only the formats in printScope render here. */}
      {ReactDOM.createPortal(
        <div className="print-stage" aria-hidden="true">
          {(printScope || []).map(f => artifacts[f] ? (
            <div key={f} className="print-page" data-format={f}>
              <window.ArtifactRender format={f} artifact={artifacts[f]} onPatch={() => {}} intake={intake} />
            </div>
          ) : null)}
        </div>,
        document.body
      )}

      {/* PERFORMANCE LOG modal — log actual reach/engagement per post */}
      {perfModalOpen && ReactDOM.createPortal(
        <PerformanceLogModal
          briefId={briefId}
          artifacts={artifacts}
          onClose={() => setPerfModalOpen(false)}
          onSaved={async () => { await reloadTeamLogs(); }}
        />,
        document.body
      )}

      {/* QA AUTOFIX — diff preview modal */}
      {fixPreview && ReactDOM.createPortal(
        <div
          className="variants-mask"
          onClick={(e) => { if (e.target === e.currentTarget) dismissFixPreview(); }}
        >
          <div className="variants-modal" style={{ display: 'flex', flexDirection: 'column', maxWidth: 760 }}>
            <div className="variants-head">
              <div>
                <h3>Suggested fix</h3>
                <p className="mono">{fixPreview.summary}</p>
              </div>
              <button className="btn-ghost" style={{ fontSize: 11, padding: '8px 12px' }} onClick={dismissFixPreview}>Dismiss</button>
            </div>
            <div style={{ padding: '0 24px 20px', overflowY: 'auto', flex: 1 }}>
              {fixPreview.patches.map((p, i) => (
                <div key={i} style={{ marginTop: 18, paddingTop: 14, borderTop: i > 0 ? '1px solid var(--ink-05)' : 'none' }}>
                  <div className="mono" style={{ fontSize: 9.5, letterSpacing: '0.14em', color: 'var(--ink-40)', textTransform: 'uppercase', marginBottom: 8 }}>
                    {p.path.join(' · ')}
                  </div>
                  <div style={{ background: 'rgba(192,57,43,0.06)', border: '1px solid rgba(192,57,43,0.2)', borderRadius: 3, padding: '10px 12px', fontSize: 13, lineHeight: 1.5, color: 'var(--ink-80)', whiteSpace: 'pre-wrap', marginBottom: 6 }}>
                    <div className="mono" style={{ fontSize: 9, letterSpacing: '0.14em', color: '#C0392B', marginBottom: 4 }}>BEFORE</div>
                    {Array.isArray(p.oldValue) ? p.oldValue.join(' ') : String(p.oldValue || '')}
                  </div>
                  <div style={{ background: 'rgba(6,118,71,0.06)', border: '1px solid rgba(6,118,71,0.2)', borderRadius: 3, padding: '10px 12px', fontSize: 13, lineHeight: 1.5, color: 'var(--ink-100)', whiteSpace: 'pre-wrap' }}>
                    <div className="mono" style={{ fontSize: 9, letterSpacing: '0.14em', color: '#067647', marginBottom: 4 }}>AFTER</div>
                    {Array.isArray(p.newValue) ? p.newValue.join(' ') : String(p.newValue || '')}
                  </div>
                </div>
              ))}
            </div>
            <div style={{ padding: '14px 24px', borderTop: '1px solid var(--ink-05)', display: 'flex', alignItems: 'center', gap: 12 }}>
              <span className="mono" style={{ fontSize: 10, color: 'var(--ink-40)', letterSpacing: '0.12em', textTransform: 'uppercase' }}>{fixPreview.patches.length} change{fixPreview.patches.length === 1 ? '' : 's'}</span>
              <button className="btn-ghost" style={{ marginLeft: 'auto', fontSize: 12, padding: '8px 14px' }} onClick={dismissFixPreview}>Cancel</button>
              <button className="btn-primary" style={{ fontSize: 12, padding: '8px 16px' }} onClick={applyFixPreview}>Apply fix</button>
            </div>
          </div>
        </div>,
        document.body
      )}
    </div>
  );
}

// ---- helpers ----
// Auto-scale the artifact to fit its container's width. Measures the
// natural width of the rendered artifact (which may be 480-800px depending
// on format) and picks a scale factor that fills the column without
// horizontal overflow — and stays at 1.0 if the natural size already fits.
//
// Two refs:
// ============================================================
// PERFORMANCE LOG MODAL — operator enters actual metrics per post.
// Saves to Supabase performance_logs. Each post in the current brief
// gets its own row of fields; submit writes all non-empty rows.
// ============================================================
function PerformanceLogModal({ briefId, artifacts, onClose, onSaved }) {
  const SOCIAL = ['linkedin_post','twitter_post','instagram_post','linkedin_campaign','twitter_thread','instagram_caption_set'];

  // Build a flat list of every post that can be logged.
  const posts = r5UseMemo(() => {
    const out = [];
    SOCIAL.forEach(format => {
      const art = artifacts[format];
      if (!art) return;
      const label = window.VENTUM_BRAND.formats[format]?.name || format;
      if (Array.isArray(art.posts)) {
        art.posts.forEach((p, i) => {
          const preview = (p.hook || p.caption_hook || p.body || '').slice(0, 60);
          out.push({ format, postIndex: i, label: `${label} · post ${i + 1}`, preview });
        });
      } else if (Array.isArray(art.thread_tweets)) {
        const preview = (art.hook_tweet || '').slice(0, 60);
        out.push({ format, postIndex: null, label, preview });
      } else {
        const preview = (art.hook || art.body || art.caption_hook || '').slice(0, 60);
        out.push({ format, postIndex: null, label, preview });
      }
    });
    return out;
  }, [artifacts]);

  const [rows, setRows] = r5UseState(() =>
    posts.map(p => ({ ...p, reach: '', likes: '', comments: '', shares: '', clicks: '', postedAt: '', notes: '' }))
  );
  const [saving, setSaving] = r5UseState(false);

  const updateRow = (i, patch) => setRows(rs => rs.map((r, idx) => idx === i ? { ...r, ...patch } : r));

  const saveAll = async () => {
    if (!briefId) {
      window.__toast && window.__toast('Save the brief first — performance logs need a brief ID');
      return;
    }
    if (!window.VentumStorage?.addPerformanceLog) return;
    setSaving(true);
    try {
      const writes = rows
        .map(r => {
          const hasAny = ['reach','likes','comments','shares','clicks'].some(k => r[k] !== '');
          if (!hasAny) return null;
          const platform = r.format.startsWith('linkedin') ? 'linkedin'
                        : r.format.startsWith('twitter') ? 'twitter'
                        : r.format.startsWith('instagram') ? 'instagram'
                        : null;
          return window.VentumStorage.addPerformanceLog({
            briefId,
            format: r.format,
            postIndex: r.postIndex,
            platform,
            reach: r.reach, likes: r.likes, comments: r.comments,
            shares: r.shares, clicks: r.clicks,
            postedAt: r.postedAt || null,
            notes: r.notes || null
          });
        })
        .filter(Boolean);
      if (!writes.length) {
        window.__toast && window.__toast('Add at least one metric on at least one row');
        setSaving(false);
        return;
      }
      await Promise.all(writes);
      window.__toast && window.__toast(`Logged ${writes.length} post${writes.length === 1 ? '' : 's'} — calibration updating`);
      onSaved && (await onSaved());
      onClose();
    } catch (e) {
      console.error('addPerformanceLog failed', e);
      window.__toast && window.__toast(e.message || 'Save failed — see console');
    } finally {
      setSaving(false);
    }
  };

  return (
    <div className="variants-mask" onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}>
      <div className="variants-modal" style={{ display: 'flex', flexDirection: 'column', width: 'min(960px, 96vw)', maxHeight: 'calc(100vh - 60px)' }}>
        <div className="variants-head">
          <div>
            <h3>Log performance · {posts.length} post{posts.length === 1 ? '' : 's'}</h3>
            <p className="mono">Reach + engagement actuals per post. Cross-team calibration uses these to learn what lifts engagement.</p>
          </div>
          <button className="btn-ghost" style={{ fontSize: 11, padding: '8px 12px' }} onClick={onClose}>Close</button>
        </div>
        <div className="modal-body" style={{ padding: '14px 18px', overflowY: 'auto' }}>
          {posts.length === 0 && (
            <div style={{ padding: 20, color: 'var(--ink-60)' }}>No social posts in this brief. Generate a LinkedIn, Twitter, or Instagram post / campaign first.</div>
          )}
          {rows.map((r, i) => (
            <div key={i} style={{ padding: '12px 0', borderBottom: '1px solid var(--ink-05)' }}>
              <div className="mono" style={{ fontSize: 10, color: 'var(--ink-60)', letterSpacing: '0.14em', textTransform: 'uppercase', marginBottom: 4 }}>{r.label}</div>
              {r.preview && (
                <div style={{ fontSize: 12, color: 'var(--ink-80)', marginBottom: 8, fontStyle: 'italic' }}>"{r.preview}…"</div>
              )}
              <div style={{ display: 'grid', gridTemplateColumns: 'repeat(6, 1fr) 1.4fr', gap: 6 }}>
                {['reach','likes','comments','shares','clicks'].map(field => (
                  <label key={field} style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
                    <span className="mono" style={{ fontSize: 9, color: 'var(--ink-40)', letterSpacing: '0.12em', textTransform: 'uppercase' }}>{field}</span>
                    <input
                      type="number"
                      min="0"
                      value={r[field]}
                      onChange={(e) => updateRow(i, { [field]: e.target.value })}
                      style={{ padding: '5px 7px', border: '1px solid var(--ink-10)', borderRadius: 2, fontSize: 12, fontFamily: 'var(--mono)' }}
                    />
                  </label>
                ))}
                <label style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
                  <span className="mono" style={{ fontSize: 9, color: 'var(--ink-40)', letterSpacing: '0.12em', textTransform: 'uppercase' }}>Posted</span>
                  <input
                    type="date"
                    value={r.postedAt}
                    onChange={(e) => updateRow(i, { postedAt: e.target.value })}
                    style={{ padding: '5px 7px', border: '1px solid var(--ink-10)', borderRadius: 2, fontSize: 12, fontFamily: 'var(--mono)' }}
                  />
                </label>
                <label style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
                  <span className="mono" style={{ fontSize: 9, color: 'var(--ink-40)', letterSpacing: '0.12em', textTransform: 'uppercase' }}>Notes</span>
                  <input
                    type="text"
                    placeholder="optional"
                    value={r.notes}
                    onChange={(e) => updateRow(i, { notes: e.target.value })}
                    style={{ padding: '5px 7px', border: '1px solid var(--ink-10)', borderRadius: 2, fontSize: 12 }}
                  />
                </label>
              </div>
            </div>
          ))}
        </div>
        <div style={{ padding: '12px 18px', borderTop: '1px solid var(--ink-10)', display: 'flex', justifyContent: 'flex-end', gap: 8 }}>
          <button className="btn-ghost" onClick={onClose} disabled={saving}>Cancel</button>
          <button className="btn-primary" onClick={saveAll} disabled={saving || posts.length === 0}>{saving ? 'Saving…' : 'Save all'}</button>
        </div>
      </div>
    </div>
  );
}

//   wrapRef  → the scaled wrapper div (we read parent width from it)
//   innerRef → the un-scaled inner div (we measure its natural width)
//
// A ResizeObserver watches both so the scale recomputes on window resize
// or when the artifact swaps to a different format.
function AutoFitArtifact({ format, artifact, intake, zoom = 1, onPatch = () => {}, strategy, onRegeneratePost, editableLogo = false, deviceFrame = null }) {
  const wrapRef = r5UseRef(null);
  const innerRef = r5UseRef(null);
  const rafRef = r5UseRef(0);
  const lastScaleRef = r5UseRef(0.7);
  const [scale, setScale] = r5UseState(0.7);

  r5UseEffect(() => {
    let cancelled = false;

    // Measure runs inside requestAnimationFrame and only flips state when
    // the new scale differs from the last applied value by > 0.01. Without
    // this, the ResizeObserver → setState → re-render → measure cycle can
    // ping-pong on heavy artifacts like the slide deck (5 stacked slides
    // with images/charts), freezing the tab.
    const measure = () => {
      if (cancelled) return;
      const wrap = wrapRef.current;
      const inner = innerRef.current;
      if (!wrap || !inner) return;
      const naturalW = inner.firstElementChild?.offsetWidth || inner.offsetWidth;
      const availableW = wrap.parentElement?.clientWidth || wrap.clientWidth;
      if (!naturalW || !availableW) return;
      const next = Math.min(1, (availableW - 32) / naturalW);
      if (Math.abs(next - lastScaleRef.current) > 0.01) {
        lastScaleRef.current = next;
        setScale(next);
      }
    };

    const schedule = () => {
      if (rafRef.current) cancelAnimationFrame(rafRef.current);
      rafRef.current = requestAnimationFrame(measure);
    };

    schedule();
    const ro = new ResizeObserver(schedule);
    if (wrapRef.current) ro.observe(wrapRef.current);
    if (wrapRef.current?.parentElement) ro.observe(wrapRef.current.parentElement);
    return () => {
      cancelled = true;
      if (rafRef.current) cancelAnimationFrame(rafRef.current);
      ro.disconnect();
    };
  }, [format, artifact, deviceFrame]);

  return (
    <div
      ref={wrapRef}
      className="artifact-wrap"
      style={{
        transform: `scale(${scale})`,
        transformOrigin: 'top center',
        width: '100%',
        display: 'flex',
        justifyContent: 'center',
        padding: '16px 0',
        // Compensate for the visual shrink so the scaled element's
        // post-transform height takes the right amount of layout space.
        // (Otherwise scale(0.6) on a 1000px-tall element still reserves 1000px.)
        marginBottom: scale < 1 ? `calc(${(scale - 1) * 100}% )` : 0
      }}
    >
      <div ref={innerRef} style={zoom !== 1 ? { zoom } : undefined}>
        {deviceFrame && deviceFrame !== 'none'
          ? <window.DeviceFrame kind={deviceFrame}><window.ArtifactRender format={format} artifact={artifact} onPatch={onPatch} intake={intake} strategy={strategy} onRegeneratePost={onRegeneratePost} editableLogo={editableLogo} /></window.DeviceFrame>
          : <window.ArtifactRender format={format} artifact={artifact} onPatch={onPatch} intake={intake} strategy={strategy} onRegeneratePost={onRegeneratePost} editableLogo={editableLogo} />}
      </div>
    </div>
  );
}

// Compact review card used in Room 6's right column.
// Collapsible header (label + stat + tone dot) + body children.
function ReviewCard({ label, stat, tone = 'idle', defaultOpen = true, children }) {
  const [open, setOpen] = r5UseState(!!defaultOpen);
  const toneColor = tone === 'pass' ? '#1F5A48' : tone === 'fail' ? '#A33027' : tone === 'warn' ? '#C77800' : 'var(--ink-40)';
  return (
    <div className="r6-review-card">
      <button onClick={() => setOpen(v => !v)} className="r6-review-card__head" type="button">
        <span className="r6-review-card__chevron">{open ? '▾' : '▸'}</span>
        <span className="r6-review-card__label mono">{label}</span>
        <span className="r6-review-card__stat mono" style={{ color: toneColor }}>{stat}</span>
        <span className="r6-review-card__dot" style={{ background: toneColor }} />
      </button>
      {open && <div className="r6-review-card__body">{children}</div>}
    </div>
  );
}

function plainText(artifact) {
  const lines = [];
  function walk(o, prefix = '') {
    if (typeof o === 'string') lines.push(prefix + o);
    else if (Array.isArray(o)) o.forEach((x, i) => walk(x, prefix));
    else if (o && typeof o === 'object') {
      for (const [k,v] of Object.entries(o)) {
        if (typeof v === 'string') lines.push(`${k}: ${v}`);
        else if (Array.isArray(v) || typeof v === 'object') {
          lines.push(`${k}:`);
          walk(v, '  ');
        }
      }
    }
  }
  walk(artifact);
  return lines.join('\n');
}

Object.assign(window, { Room5_Generate, Room6_Export });
