/* ===========================================================================
   iRaven App Store Studio — App Preview editor (scene timeline).
   A preview = a strip of scenes; each scene = one media item with its own
   duration + enter/exit animation. One soundtrack spans the preview. Previews
   can be stitched into one longer video from the Combine panel.
   =========================================================================== */

/* hide the ugly native number-spinner on the duration field (live + standalone) */
(function () {
  if (typeof document === 'undefined' || document.getElementById('s360-ui-style')) return;
  const s = document.createElement('style');
  s.id = 's360-ui-style';
  s.textContent =
    'input.s360-num::-webkit-outer-spin-button,input.s360-num::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}' +
    'input.s360-num{-moz-appearance:textfield;appearance:textfield}';
  document.head.appendChild(s);
})();

function PreviewThumb({ preview, active, index, onClick }) {
  const dims = (window.VIDEO_DEVICES[preview.device] || window.VIDEO_DEVICES.iphone).canvas;
  const W = 116;
  const scale = W / dims.w;
  const D = window.previewDuration(preview);
  return (
    <button onClick={onClick} style={{
      position: 'relative', flex: '0 0 auto', padding: 0, cursor: 'pointer', borderRadius: 14, overflow: 'hidden',
      width: W, height: dims.h * scale, background: '#05070f',
      border: active ? '2px solid #2f6df6' : '2px solid rgba(255,255,255,0.08)',
      boxShadow: active ? '0 0 0 3px rgba(47,109,246,0.25)' : 'none', transition: 'all .15s' }}>
      <div style={{ width: dims.w, height: dims.h, transform: `scale(${scale})`, transformOrigin: 'top left' }}>
        <SceneComposition preview={preview} sceneIdx={0} mode="display" />
      </div>
      <span style={{ position: 'absolute', top: 6, left: 6, fontFamily: "'JetBrains Mono',monospace", fontSize: 11,
        fontWeight: 700, color: '#fff', background: 'rgba(0,0,0,0.55)', borderRadius: 6, padding: '1px 6px' }}>
        {String(index + 1).padStart(2, '0')}
      </span>
      <span style={{ position: 'absolute', bottom: 6, right: 6, fontFamily: "'JetBrains Mono',monospace", fontSize: 10,
        color: '#fff', background: 'rgba(0,0,0,0.55)', borderRadius: 6, padding: '1px 6px' }}>
        {(preview.scenes || []).length}◦ {Math.round(D)}s
      </span>
    </button>
  );
}

/* a single scene tile in the horizontal strip */
function SceneTile({ preview, scene, sceneIdx, active, dur, onClick }) {
  const dims = (window.VIDEO_DEVICES[preview.device] || window.VIDEO_DEVICES.iphone).canvas;
  const H = 116;
  const scale = H / dims.h;
  const isVid = window.isVideoMedia(scene.media);
  return (
    <button onClick={onClick} style={{ position: 'relative', flex: '0 0 auto', padding: 0, cursor: 'pointer',
      borderRadius: 12, overflow: 'hidden', width: dims.w * scale, height: H, background: '#05070f',
      border: active ? '2px solid #2f6df6' : '2px solid rgba(255,255,255,0.10)',
      boxShadow: active ? '0 0 0 3px rgba(47,109,246,0.22)' : 'none' }}>
      <div style={{ width: dims.w, height: dims.h, transform: `scale(${scale})`, transformOrigin: 'top left' }}>
        <SceneComposition preview={preview} sceneIdx={sceneIdx} mode="display" />
      </div>
      <span style={{ position: 'absolute', top: 4, left: 4, fontFamily: "'JetBrains Mono',monospace", fontSize: 9,
        fontWeight: 700, color: '#fff', background: 'rgba(0,0,0,0.6)', borderRadius: 5, padding: '0 5px' }}>
        {String(sceneIdx + 1).padStart(2, '0')}
      </span>
      <span style={{ position: 'absolute', bottom: 4, right: 4, fontFamily: "'JetBrains Mono',monospace", fontSize: 9,
        color: '#fff', background: 'rgba(0,0,0,0.6)', borderRadius: 5, padding: '0 5px' }}>
        {isVid ? '▶ ' : ''}{(dur || window.sceneDuration(scene)).toFixed(1)}s
      </span>
    </button>
  );
}

const durStepStyle = { width: 26, height: 32, border: 'none', background: 'transparent',
  color: 'rgba(255,255,255,0.72)', cursor: 'pointer', fontSize: 15, lineHeight: 1,
  display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 0 };

/* styled native select */
function Pick({ value, onChange, options }) {
  return (
    <select value={value} onChange={e => onChange(e.target.value)} style={{
      width: '100%', boxSizing: 'border-box', padding: '8px 10px', borderRadius: 9, cursor: 'pointer',
      background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.12)', color: '#fff',
      fontFamily: BODY, fontSize: 13, outline: 'none', appearance: 'none' }}>
      {options.map(o => <option key={String(o.id)} value={o.id} style={{ background: '#0c1222' }}>{o.label}</option>)}
    </select>
  );
}

function AnimEditor({ label, anim, onChange }) {
  const a = anim || { type: 'fade', dur: 0.6, ease: 'out', dist: 1 };
  const hasMotion = a.type && a.type !== 'fade' && a.type !== 'none';
  const distLabel = (a.type === 'zoomIn' || a.type === 'zoomOut' || a.type === 'pop') ? 'Scale amount'
    : (a.type === 'tilt') ? 'Tilt amount' : 'Travel distance';
  return (
    <div style={{ marginBottom: 12 }}>
      <div style={{ fontSize: 11.5, color: 'rgba(255,255,255,0.5)', marginBottom: 6, fontWeight: 600 }}>{label}</div>
      <div style={{ display: 'grid', gridTemplateColumns: '1.3fr 1fr', gap: 6, marginBottom: 6 }}>
        <Pick value={a.type} onChange={v => onChange({ ...a, type: v })} options={window.SCENE_ANIMS} />
        <Pick value={a.ease} onChange={v => onChange({ ...a, ease: v })} options={window.SCENE_EASES} />
      </div>
      <Seg options={window.ANIM_DURS} value={a.dur} onChange={v => onChange({ ...a, dur: v })} />
      {hasMotion ? (
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginTop: 8 }}>
          <span style={{ fontSize: 11, color: 'rgba(255,255,255,0.45)', minWidth: 84, fontFamily: BODY }}>{distLabel}</span>
          <input type="range" min={0.1} max={4} step={0.05} value={a.dist == null ? 1 : a.dist}
            onChange={e => onChange({ ...a, dist: +e.target.value })} style={{ flex: 1, accentColor: '#3b78ff' }} />
        </div>
      ) : null}
    </div>
  );
}

function VideoEditor({ videos, setVideos, current, setCurrent, stageRef, onStatus,
  assets, setAssets, pickMedia, genMedia, assist }) {
  const preview = videos[Math.min(current, videos.length - 1)];
  const dims = (window.VIDEO_DEVICES[preview.device] || window.VIDEO_DEVICES.iphone).canvas;
  const wrapRef = useRef(null);
  const [scale, setScale] = useState(0.22);
  const [sel, setSel] = useState(0);
  const [measured, setMeasured] = useState(null);   // timeline from the stage (video lengths)

  useEffect(() => { setSel(0); setMeasured(null); }, [current]);

  useEffect(() => {
    const fit = () => {
      const el = wrapRef.current; if (!el) return;
      const pad = 48, controlsH = 70;
      const s = Math.min((el.clientWidth - pad) / dims.w, (el.clientHeight - pad - controlsH) / dims.h);
      setScale(Math.max(0.05, s));
    };
    fit();
    const ro = new ResizeObserver(fit);
    if (wrapRef.current) ro.observe(wrapRef.current);
    return () => ro.disconnect();
  }, [dims.w, dims.h]);

  const patch = (ch) => setVideos(arr => arr.map((v, i) => i === current ? { ...v, ...ch } : v));
  const scenes = preview.scenes || [];
  const scene = scenes[Math.min(sel, scenes.length - 1)] || scenes[0];

  const patchScene = (ch) => patch({ scenes: scenes.map((s, i) => i === sel ? { ...s, ...ch } : s) });

  const addPreview = () => {
    if (videos.length >= 10) return;
    setVideos(arr => [...arr, { device: 'iphone', bg: 'stadium', accent: 'brand1', outro: true, audio: null,
      name: 'Preview ' + String(arr.length + 1).padStart(2, '0'),
      scenes: [window.makeScene({ headline: 'New preview', subtitle: '' })] }]);
    setCurrent(videos.length);
  };
  const delPreview = () => {
    if (videos.length <= 1) return;
    for (const sc of scenes) { const m = sc.media; if (m && typeof m === 'object' && m.key && !(assets || []).find(a => a.key === m.key)) window.vidMediaDel(m.key); }
    if (preview.savedExport && preview.savedExport.key) { try { window.vidMediaDel(preview.savedExport.key); } catch (e) {} }
    setVideos(arr => arr.filter((_, i) => i !== current));
    setCurrent(c => Math.max(0, c - 1));
  };

  const addScene = () => {
    const ns = window.makeScene({ headline: '', subtitle: '', captionPos: 'none' });
    patch({ scenes: [...scenes.slice(0, sel + 1), ns, ...scenes.slice(sel + 1)] });
    setSel(sel + 1);
  };
  const dupScene = () => { const ns = { ...window.makeScene({}), ...scene, id: window.sceneUid() };
    patch({ scenes: [...scenes.slice(0, sel + 1), ns, ...scenes.slice(sel + 1)] }); setSel(sel + 1); };
  const delScene = () => { if (scenes.length <= 1) return;
    patch({ scenes: scenes.filter((_, i) => i !== sel) }); setSel(s => Math.max(0, Math.min(s, scenes.length - 2))); };
  const moveScene = (dir) => { const j = sel + dir; if (j < 0 || j >= scenes.length) return;
    const a = [...scenes]; const t = a[sel]; a[sel] = a[j]; a[j] = t; patch({ scenes: a }); setSel(j); };

  const setSceneMedia = (ref, mediaDur) => patchScene({ media: ref, mediaDur: mediaDur || null });

  /* Save the rendered preview mp4 to the server and attach it to the preview
     record so it shows up in the Saved Previews rail across reloads. */
  const handleSavePreview = async (blob, ext) => {
    if (!blob) return;
    const key = window.assetUid('v') + '_saved.' + (ext || 'mp4');
    try {
      const prevKey = preview.savedExport && preview.savedExport.key;
      await window.vidMediaPut(key, blob);
      const dur = totalDur || window.previewDuration(preview) || null;
      const rec = { key, name: (preview.name || 'preview') + '.' + (ext || 'mp4'),
        ext: ext || 'mp4', mime: blob.type || 'video/mp4', size: blob.size, dur, ts: Date.now() };
      patch({ savedExport: rec });
      if (prevKey && prevKey !== key) { try { window.vidMediaDel(prevKey); } catch (e) {} }
    } catch (e) { console.error('save preview failed', e); onStatus && onStatus('Save failed'); }
  };

  const removeSavedExport = (i) => {
    const v = videos[i]; if (!v || !v.savedExport) return;
    try { window.vidMediaDel(v.savedExport.key); } catch (e) {}
    setVideos(arr => arr.map((x, j) => j === i ? { ...x, savedExport: null } : x));
  };

  const [playingSaved, setPlayingSaved] = useState(null);   // {key, name}

  const chooseSceneMedia = () => pickMedia({ accept: 'image+video', title: 'Scene media',
    aiKind: 'image',
    onPick: (asset) => setSceneMedia(window.assetToRef(asset), asset.dur) });
  const chooseAudio = () => pickMedia({ accept: 'audio', title: 'Soundtrack', aiKind: 'music',
    onPick: (asset) => patch({ audio: window.assetToRef(asset) }) });

  const sceneDur = (i) => (measured && measured[i] && measured[i].dur) || window.sceneDuration(scenes[i]);
  const totalDur = measured
    ? measured.reduce((a, t) => a + (t.dur || 0), 0) + (preview.outro !== false ? 2.4 : 0)
    : window.previewDuration(preview);
  const isSceneVideo = window.isVideoMedia(scene.media);

  /* AI builds the WHOLE timeline from one prompt — picks media from storage,
     sets durations, captions, animations, bg/accent and a soundtrack. */
  const aiTimeline = () => assist({ kind: 'preview', preview, current, sel,
    applyTimeline: (plan) => {
      const findAsset = (name) => {
        if (!name) return null;
        const n = String(name).toLowerCase().trim();
        return (assets || []).find(a => a.name && a.name.toLowerCase() === n)
          || (assets || []).find(a => a.name && a.name.toLowerCase().includes(n))
          || null;
      };
      const animOk = (t) => (window.SCENE_ANIMS.find(a => a.id === t) ? t : null);
      const built = (plan.scenes || []).map(s => {
        const asset = findAsset(s.media);
        const ref = asset ? window.assetToRef(asset) : null;
        const isVid = asset && asset.kind === 'video';
        const hasCopy = (s.headline || s.subtitle);
        return window.makeScene({
          media: ref, mediaDur: isVid ? (asset.dur || null) : null,
          layout: s.layout === 'fullbleed' ? 'fullbleed' : 'device',
          dur: Math.max(0.5, Math.min(600, +s.dur || 2)),
          headline: (s.headline || '').replace(/\\n/g, '\n'), subtitle: s.subtitle || '', titleSize: 'md',
          captionPos: ['top', 'bottom', 'none'].includes(s.captionPos) ? s.captionPos : (hasCopy ? 'top' : 'none'),
          enter: { type: animOk(s.enter) || 'rise', dur: 0.7, ease: 'out' },
          exit: { type: animOk(s.exit) || 'fade', dur: 0.5, ease: 'in' },
        });
      });
      if (!built.length) return;
      const ch = { scenes: built };
      if (plan.bg && window.BACKGROUNDS.find(b => b.id === plan.bg)) ch.bg = plan.bg;
      if (plan.accent) ch.accent = plan.accent;
      if (plan.device && window.VIDEO_DEVICES[plan.device]) ch.device = plan.device;
      const music = findAsset(plan.music);
      if (music && music.kind === 'audio') ch.audio = window.assetToRef(music);
      patch(ch); setSel(0);
    },
  });

  const savedList = (videos || []).map((v, i) => v && v.savedExport ? { i, v } : null).filter(Boolean);

  return (
    <div style={{ flex: 1, display: 'flex', flexDirection: 'column', minHeight: 0 }}>
      {savedList.length ? (
        <SavedPreviewsRail items={savedList} onPlay={(it) => setPlayingSaved({ key: it.v.savedExport.key, name: it.v.savedExport.name || it.v.name })}
          onJump={(it) => setCurrent(it.i)} onDelete={(it) => removeSavedExport(it.i)} />
      ) : null}
      {playingSaved ? <SavedPreviewModal item={playingSaved} onClose={() => setPlayingSaved(null)} /> : null}
    <div style={{ flex: 1, display: 'flex', minHeight: 0 }}>
      {/* preview rail */}
      <aside style={{ width: 152, flex: '0 0 auto', borderRight: '1px solid rgba(255,255,255,0.08)',
        display: 'flex', flexDirection: 'column', minHeight: 0 }}>
        <div style={{ padding: '12px 14px 6px', fontFamily: "'JetBrains Mono',monospace", fontSize: 10.5,
          letterSpacing: '0.12em', textTransform: 'uppercase', color: 'rgba(255,255,255,0.4)' }}>Previews</div>
        <div style={{ flex: 1, overflowY: 'auto', padding: '4px 14px 14px', display: 'flex', flexDirection: 'column',
          gap: 12, alignItems: 'center' }}>
          {videos.map((v, i) => (
            <PreviewThumb key={i} preview={v} index={i} active={i === current} onClick={() => setCurrent(i)} />
          ))}
          <button onClick={addPreview} disabled={videos.length >= 10} style={{ width: 116, padding: '12px 0',
            borderRadius: 14, cursor: videos.length >= 10 ? 'not-allowed' : 'pointer',
            border: '1px dashed rgba(255,255,255,0.18)', background: 'transparent', color: 'rgba(255,255,255,0.5)',
            fontFamily: BODY, fontSize: 13, opacity: videos.length >= 10 ? 0.4 : 1 }}>+ Preview</button>
        </div>
      </aside>

      {/* stage + scene strip */}
      <main ref={wrapRef} style={{ flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column',
        background: 'radial-gradient(120% 120% at 50% 0%, #0d1426, #05070f)' }}>
        <div style={{ flex: 1, minHeight: 0, display: 'flex', alignItems: 'center', justifyContent: 'center',
          overflow: 'hidden', padding: 14 }}>
          <SceneStage ref={stageRef} preview={preview} idx={current} scale={scale} activeScene={sel}
            onMeasured={setMeasured} onStatus={onStatus} onSave={handleSavePreview}
            onExport={async (blob, ext) => {
              await window.storeAsset(setAssets, { blob, kind: 'video', source: 'export', sub: 'preview',
                name: `${(preview.name || 'preview')}.${ext}` });
            }} />
        </div>
        {/* scene strip */}
        <div style={{ flex: '0 0 auto', borderTop: '1px solid rgba(255,255,255,0.08)', padding: '12px 16px',
          background: 'rgba(0,0,0,0.25)' }}>
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 }}>
            <span style={{ fontFamily: "'JetBrains Mono',monospace", fontSize: 10.5, letterSpacing: '0.12em',
              textTransform: 'uppercase', color: 'rgba(255,255,255,0.42)' }}>
              Timeline · {scenes.length} scene{scenes.length === 1 ? '' : 's'} · {Math.round(totalDur)}s</span>
            <div style={{ display: 'flex', gap: 6 }}>
              <button style={btnStyle({ padding: '5px 10px', fontSize: 12 })} onClick={() => moveScene(-1)} title="Move left">←</button>
              <button style={btnStyle({ padding: '5px 10px', fontSize: 12 })} onClick={() => moveScene(1)} title="Move right">→</button>
              <button style={btnStyle({ padding: '5px 10px', fontSize: 12 })} onClick={dupScene} title="Duplicate">⧉</button>
              <button style={btnStyle({ padding: '5px 10px', fontSize: 12, color: '#ff8089' })} onClick={delScene} title="Delete scene">✕</button>
            </div>
          </div>
          <div style={{ display: 'flex', gap: 10, overflowX: 'auto', paddingBottom: 4, alignItems: 'center' }}>
            {scenes.map((s, i) => (
              <SceneTile key={s.id || i} preview={preview} scene={s} sceneIdx={i} active={i === sel}
                dur={sceneDur(i)} onClick={() => setSel(i)} />
            ))}
            <button onClick={addScene} style={{ flex: '0 0 auto', width: 66, height: 116, borderRadius: 12,
              cursor: 'pointer', border: '1px dashed rgba(255,255,255,0.2)', background: 'transparent',
              color: 'rgba(255,255,255,0.5)', fontSize: 22 }}>+</button>
          </div>
        </div>
      </main>

      {/* right panel: scene + preview controls */}
      <aside style={{ width: 348, flex: '0 0 auto', borderLeft: '1px solid rgba(255,255,255,0.08)',
        overflowY: 'auto', padding: 20 }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16, gap: 8 }}>
          <input value={preview.name || ''} placeholder={'Preview ' + String(current + 1).padStart(2, '0')}
            onChange={e => patch({ name: e.target.value })}
            style={{ flex: 1, minWidth: 0, background: 'transparent', border: 'none', color: '#fff',
              fontFamily: DISPLAY, fontWeight: 600, fontSize: 17, outline: 'none' }} />
          <button title="AI — build the whole timeline from one prompt"
            style={btnStyle({ padding: '6px 11px', fontSize: 12, flex: '0 0 auto',
              background: 'linear-gradient(180deg,#3b78ff,#1f54d6)', border: '1px solid #5b8cff', color: '#fff' })}
            onClick={aiTimeline}>✦ AI timeline</button>
          <button title="Delete preview" style={btnStyle({ padding: '6px 10px', fontSize: 13, flex: '0 0 auto', color: '#ff8089' })}
            onClick={delPreview}>✕</button>
        </div>

        {/* SCENE */}
        <div style={{ borderRadius: 14, border: '1px solid rgba(47,109,246,0.3)', background: 'rgba(47,109,246,0.06)',
          padding: 14, marginBottom: 20 }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }}>
            <span style={{ fontFamily: DISPLAY, fontWeight: 600, fontSize: 15, color: '#9dbcff' }}>
              Scene {String(sel + 1).padStart(2, '0')}</span>
            <button style={btnStyle({ padding: '5px 10px', fontSize: 11.5,
              background: 'rgba(47,109,246,0.18)', border: '1px solid rgba(47,109,246,0.4)', color: '#bcd2ff' })}
              onClick={() => assist({ kind: 'scene', preview, current, sel,
                apply: (ch) => patchScene(ch), applyMedia: (ref, dur) => setSceneMedia(ref, dur) })}>✦ AI scene</button>
          </div>

          <Section title="Media" hint={isSceneVideo ? 'video · uses its own length' : 'image'}>
            <div style={{ borderRadius: 10, overflow: 'hidden', border: '1px solid rgba(255,255,255,0.12)',
              aspectRatio: '16 / 9', marginBottom: 8, background: '#05070f', position: 'relative' }}>
              {scene.media ? <MediaThumb value={scene.media} /> : (
                <div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center',
                  color: 'rgba(255,255,255,0.35)', fontSize: 12, fontFamily: "'JetBrains Mono',monospace" }}>NO MEDIA</div>
              )}
            </div>
            <div style={{ display: 'flex', gap: 6 }}>
              <button style={btnStyle({ flex: 1, padding: '8px', fontSize: 12.5 })} onClick={chooseSceneMedia}>
                {scene.media ? 'Replace' : 'Choose / upload'}</button>
              {scene.media ? (
                <button style={btnStyle({ padding: '8px 10px', fontSize: 12.5, color: '#ff8089' })}
                  onClick={() => setSceneMedia(null)}>Clear</button>
              ) : null}
            </div>
          </Section>

          <Section title="Layout">
            <Seg options={[{ id: 'device', label: 'In device' }, { id: 'fullbleed', label: 'Full bleed' }]}
              value={scene.layout || 'device'} onChange={v => patchScene({ layout: v })} />
          </Section>

          {!isSceneVideo ? (
            <Section title="Duration" hint="seconds on screen">
              <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
                <input type="range" min={0.5} max={20} step={0.1} value={Math.min(scene.dur || 2, 20)}
                  onChange={e => patchScene({ dur: +e.target.value })} style={{ flex: 1, accentColor: '#3b78ff' }} />
                <div style={{ display: 'flex', alignItems: 'center', borderRadius: 9, overflow: 'hidden',
                  border: '1px solid rgba(255,255,255,0.14)', background: 'rgba(255,255,255,0.05)' }}>
                  <button title="−0.5s" onClick={() => patchScene({ dur: Math.max(0.2, +((scene.dur || 2) - 0.5).toFixed(1)) })}
                    style={durStepStyle}>−</button>
                  <input className="s360-num" type="number" min={0.2} max={600} step={0.1} value={scene.dur || 2}
                    onChange={e => { const v = parseFloat(e.target.value); patchScene({ dur: isFinite(v) ? Math.max(0.2, Math.min(600, v)) : 0.2 }); }}
                    style={{ width: 46, boxSizing: 'border-box', padding: '7px 4px', textAlign: 'center', border: 'none',
                      borderLeft: '1px solid rgba(255,255,255,0.10)', borderRight: '1px solid rgba(255,255,255,0.10)',
                      background: 'transparent', color: '#9dbcff', fontFamily: "'JetBrains Mono',monospace", fontSize: 13, outline: 'none' }} />
                  <button title="+0.5s" onClick={() => patchScene({ dur: Math.min(600, +((scene.dur || 2) + 0.5).toFixed(1)) })}
                    style={durStepStyle}>+</button>
                </div>
                <span style={{ fontFamily: "'JetBrains Mono',monospace", fontSize: 12, color: 'rgba(255,255,255,0.45)' }}>s</span>
              </div>
            </Section>
          ) : (
            <Section title="Duration">
              <div style={{ fontSize: 12, color: 'rgba(255,255,255,0.5)', fontFamily: BODY }}>
                Video clip — plays its full length ({(sceneDur(sel)).toFixed(1)}s).</div>
            </Section>
          )}

          <Section title="Caption">
            <Seg options={[{ id: 'none', label: 'None' }, { id: 'top', label: 'Top' }, { id: 'bottom', label: 'Bottom' }]}
              value={scene.captionPos || 'none'} onChange={v => patchScene({ captionPos: v })} />
            {scene.captionPos !== 'none' ? (
              <div style={{ marginTop: 8 }}>
                <Field label="Headline" rows={2} value={scene.headline} placeholder="Matchday, live."
                  onChange={v => patchScene({ headline: v })} />
                <Field label="Subtitle" rows={2} value={scene.subtitle} placeholder="One short line."
                  onChange={v => patchScene({ subtitle: v })} />
                <Seg options={window.TITLE_SIZES} value={scene.titleSize || 'md'} onChange={v => patchScene({ titleSize: v })} />
              </div>
            ) : null}
          </Section>

          <AnimEditor label="Enter animation" anim={scene.enter} onChange={v => patchScene({ enter: v })} />
          <AnimEditor label="Exit animation" anim={scene.exit} onChange={v => patchScene({ exit: v })} />
        </div>

        {/* PREVIEW SETTINGS */}
        <Section title="Device" hint="App Preview size">
          <Seg options={Object.values(window.VIDEO_DEVICES).map(d => ({ id: d.id, label: d.label }))}
            value={preview.device} onChange={v => patch({ device: v })} />
        </Section>

        <Section title="Background">
          <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
            {window.BACKGROUNDS.map(b => {
              const active = b.id === preview.bg;
              return (<button key={b.id} title={b.label} onClick={() => patch({ bg: b.id })} style={{
                width: 50, height: 50, borderRadius: 12, cursor: 'pointer', background: b.swatch,
                border: active ? '2px solid #2f6df6' : '2px solid rgba(255,255,255,0.12)',
                boxShadow: active ? '0 0 0 3px rgba(47,109,246,0.22)' : 'none' }} />);
            })}
          </div>
        </Section>

        <Section title="Accent">
          <Seg options={window.getAccentOptions()} value={preview.accent} onChange={v => patch({ accent: v })} cols={2} />
        </Section>

        <Section title="Outro" hint="logo end-card">
          <Seg options={[{ id: true, label: 'On' }, { id: false, label: 'Off' }]}
            value={preview.outro !== false} onChange={v => patch({ outro: v })} />
        </Section>

        <Section title="Soundtrack" hint="spans the whole preview">
          {preview.audio ? (
            <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
              <span style={{ flex: 1, fontSize: 12.5, color: 'rgba(255,255,255,0.75)', fontFamily: BODY,
                whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>♪ {preview.audio.name || 'soundtrack'}</span>
              <button style={btnStyle({ padding: '6px 10px', fontSize: 12 })} onClick={chooseAudio}>Change</button>
              <button style={btnStyle({ padding: '6px 10px', fontSize: 12, color: '#ff8089' })}
                onClick={() => patch({ audio: null })}>✕</button>
            </div>
          ) : (
            <button style={btnStyle({ width: '100%', padding: '9px', fontSize: 12.5 })} onClick={chooseAudio}>
              + Add music or voiceover</button>
          )}
        </Section>
      </aside>
    </div>
    </div>
  );
}

/* ---------- Saved Previews rail ---------- */
function SavedPreviewsRail({ items, onPlay, onJump, onDelete }) {
  return (
    <div style={{ flex: '0 0 auto', borderBottom: '1px solid rgba(255,255,255,0.08)',
      background: 'rgba(255,255,255,0.02)', padding: '10px 16px', display: 'flex',
      flexDirection: 'column', gap: 8 }}>
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
        <span style={{ fontFamily: "'JetBrains Mono',monospace", fontSize: 10.5, letterSpacing: '0.14em',
          textTransform: 'uppercase', color: 'rgba(255,255,255,0.5)' }}>
          Saved Previews · {items.length}</span>
        <span style={{ fontSize: 11, color: 'rgba(255,255,255,0.35)' }}>
          Saved on the server — survives reloads & sessions.</span>
      </div>
      <div style={{ display: 'flex', gap: 10, overflowX: 'auto', paddingBottom: 4 }}>
        {items.map(({ i, v }) => {
          const exp = v.savedExport || {};
          return (
            <div key={i} style={{ position: 'relative', flex: '0 0 auto', width: 168, borderRadius: 12,
              overflow: 'hidden', border: '1px solid rgba(255,255,255,0.10)', background: '#05070f' }}>
              <button onClick={() => onPlay({ i, v })} title="Play saved preview"
                style={{ display: 'block', width: '100%', padding: 0, border: 'none', background: 'transparent', cursor: 'pointer' }}>
                <SavedPreviewThumb mediaKey={exp.key} />
              </button>
              <div style={{ padding: '8px 10px 10px', display: 'flex', flexDirection: 'column', gap: 4 }}>
                <span style={{ fontSize: 12.5, color: '#fff', fontFamily: BODY, whiteSpace: 'nowrap',
                  overflow: 'hidden', textOverflow: 'ellipsis' }}>{v.name || ('Preview ' + String(i + 1).padStart(2, '0'))}</span>
                <span style={{ fontFamily: "'JetBrains Mono',monospace", fontSize: 10, color: 'rgba(255,255,255,0.45)' }}>
                  {exp.dur ? exp.dur.toFixed(1) + 's · ' : ''}{exp.size ? (exp.size > 1e6 ? (exp.size / 1e6).toFixed(1) + ' MB' : Math.round(exp.size / 1e3) + ' KB') : ''}
                </span>
                <div style={{ display: 'flex', gap: 4, marginTop: 4 }}>
                  <button onClick={() => onJump({ i, v })} title="Open this preview in the editor"
                    style={btnStyle({ flex: 1, padding: '5px 8px', fontSize: 11 })}>Open</button>
                  <button onClick={() => onDelete({ i, v })} title="Remove saved export"
                    style={btnStyle({ padding: '5px 8px', fontSize: 11, color: '#ff8089' })}>✕</button>
                </div>
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

function SavedPreviewThumb({ mediaKey }) {
  const [src, setSrc] = useState(null);
  useEffect(() => {
    let live = true, url = null;
    (async () => {
      try { const blob = await window.vidMediaGet(mediaKey); if (!live || !blob) return;
        url = URL.createObjectURL(blob); setSrc(url); } catch (e) {}
    })();
    return () => { live = false; if (url) URL.revokeObjectURL(url); };
  }, [mediaKey]);
  if (!src) return (
    <div style={{ width: '100%', aspectRatio: '9 / 16', maxHeight: 220, background: '#05070f',
      display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'rgba(255,255,255,0.3)',
      fontFamily: "'JetBrains Mono',monospace", fontSize: 11 }}>…</div>
  );
  return (
    <div style={{ position: 'relative', width: '100%', aspectRatio: '9 / 16', maxHeight: 220, background: '#000' }}>
      <video src={src} muted playsInline preload="metadata" style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }} />
      <span style={{ position: 'absolute', left: '50%', top: '50%', transform: 'translate(-50%,-50%)',
        width: 34, height: 34, borderRadius: '50%', background: 'rgba(3,5,14,0.55)', color: '#fff',
        display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 14 }}>▶</span>
    </div>
  );
}

function SavedPreviewModal({ item, onClose }) {
  const [src, setSrc] = useState(null);
  useEffect(() => {
    let live = true, url = null;
    (async () => {
      try { const blob = await window.vidMediaGet(item.key); if (!live || !blob) return;
        url = URL.createObjectURL(blob); setSrc(url); } catch (e) {}
    })();
    return () => { live = false; if (url) URL.revokeObjectURL(url); };
  }, [item.key]);
  return (
    <div onClick={onClose} style={{ position: 'fixed', inset: 0, background: 'rgba(3,5,14,0.82)',
      zIndex: 80, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24 }}>
      <div onClick={e => e.stopPropagation()} style={{ position: 'relative', maxWidth: 'min(90vw, 520px)',
        maxHeight: '90vh', background: '#05070f', borderRadius: 14, overflow: 'hidden',
        border: '1px solid rgba(255,255,255,0.08)' }}>
        {src ? (
          <video src={src} controls autoPlay playsInline style={{ display: 'block', width: '100%', height: 'auto', maxHeight: '85vh' }} />
        ) : (
          <div style={{ padding: 40, textAlign: 'center', color: 'rgba(255,255,255,0.5)' }}>Loading…</div>
        )}
        <div style={{ padding: '10px 14px', display: 'flex', alignItems: 'center', justifyContent: 'space-between',
          borderTop: '1px solid rgba(255,255,255,0.08)' }}>
          <span style={{ fontSize: 12.5, color: 'rgba(255,255,255,0.7)' }}>{item.name}</span>
          <div style={{ display: 'flex', gap: 6 }}>
            {src ? (
              <a href={src} download={item.name} style={{ ...btnStyle({ padding: '6px 10px', fontSize: 12 }), textDecoration: 'none' }}>↓ Download</a>
            ) : null}
            <button style={btnStyle({ padding: '6px 10px', fontSize: 12 })} onClick={onClose}>Close</button>
          </div>
        </div>
      </div>
    </div>
  );
}

/* ---------- Combine previews → one longer video ---------- */
function CombineModal({ videos, assets, setAssets, onClose, onStatus }) {
  const [picked, setPicked] = useState(() => videos.map((_, i) => i));
  const [order, setOrder] = useState(() => videos.map((_, i) => i));
  const [audioRef, setAudioRef] = useState(null);
  const [busy, setBusy] = useState(false);
  const [pickAudio, setPickAudio] = useState(false);
  const seq = order.filter(i => picked.includes(i));
  const forceDevice = (videos[seq[0]] || videos[0] || {}).device || 'iphone';

  const toggle = (i) => setPicked(p => p.includes(i) ? p.filter(x => x !== i) : [...p, i]);
  const move = (idx, dir) => setOrder(o => { const a = [...o]; const j = idx + dir; if (j < 0 || j >= a.length) return o;
    const t = a[idx]; a[idx] = a[j]; a[j] = t; return a; });

  const totalDur = seq.reduce((a, i) => a + window.previewDuration(videos[i]), 0);

  const run = async () => {
    if (busy || !seq.length) return;
    setBusy(true);
    try {
      onStatus && onStatus('Combining — capturing previews…');
      const segs = []; let offset = 0;
      for (const i of seq) {
        const env = await window.captureSceneEnv(videos[i], { forceDevice });
        segs.push({ env, offset });
        offset += env.duration;
      }
      const dims = (window.VIDEO_DEVICES[forceDevice] || window.VIDEO_DEVICES.iphone).canvas;
      const res = await window.recordTimeline({ dims, D: offset, segs,
        audioKey: audioRef && audioRef.key, onStatus });
      for (const sg of segs) window.vidDisposeEnv(sg.env);
      if (res && res.blob) {
        await window.storeAsset(setAssets, { blob: res.blob, kind: 'video', source: 'export', sub: 'combined',
          name: `combined-${seq.length}-previews.${res.ext}` });
        const a = document.createElement('a'); a.href = URL.createObjectURL(res.blob);
        a.download = `${((window.CURRENT_BRAND || {}).name || 'app').toLowerCase().replace(/[^a-z0-9]+/g, '')}-combined.${res.ext}`;
        a.click();
        onStatus && onStatus('Combined video saved ✓');
        setTimeout(() => onStatus && onStatus(null), 4000);
        onClose();
      }
    } catch (e) { console.error(e); onStatus && onStatus('Combine failed'); }
    setBusy(false);
  };

  return (
    <div onClick={busy ? undefined : onClose} style={{ position: 'fixed', inset: 0, zIndex: 1260, background: 'rgba(3,5,14,0.85)',
      display: 'flex', alignItems: 'center', justifyContent: 'center', fontFamily: BODY }}>
      <div onClick={e => e.stopPropagation()} style={{ width: 'min(560px,94vw)', maxHeight: '88vh', overflowY: 'auto',
        borderRadius: 18, background: '#0c1222', border: '1px solid rgba(255,255,255,0.12)', padding: 22 }}>
        <div style={{ fontFamily: DISPLAY, fontWeight: 600, fontSize: 17, color: '#fff', marginBottom: 4 }}>Combine previews</div>
        <p style={{ margin: '0 0 16px', fontSize: 12.5, color: 'rgba(255,255,255,0.5)' }}>
          Stitch previews end-to-end into one longer video. Reorder with the arrows. Output uses the
          first preview's device size ({(window.VIDEO_DEVICES[forceDevice] || {}).label}).</p>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginBottom: 16 }}>
          {order.map((i, idx) => (
            <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '8px 10px', borderRadius: 12,
              border: '1px solid rgba(255,255,255,0.10)', background: picked.includes(i) ? 'rgba(47,109,246,0.10)' : 'rgba(255,255,255,0.02)' }}>
              <input type="checkbox" checked={picked.includes(i)} onChange={() => toggle(i)} style={{ accentColor: '#3b78ff' }} />
              <div style={{ width: 34, height: 60, borderRadius: 6, overflow: 'hidden', flex: '0 0 auto', background: '#05070f',
                position: 'relative', border: '1px solid rgba(255,255,255,0.10)' }}>
                {(() => { const sc0 = (videos[i].scenes || [])[0];
                  return sc0 && sc0.media
                    ? <MediaThumb value={sc0.media} />
                    : <div style={{ width: '100%', height: '100%', background: window.resolveAccent(videos[i].accent) + '33' }} />; })()}
              </div>
              <div style={{ flex: 1 }}>
                <div style={{ fontSize: 13.5, color: '#fff', fontWeight: 600 }}>{videos[i].name || ('Preview ' + String(i + 1).padStart(2, '0'))}</div>
                <div style={{ fontFamily: "'JetBrains Mono',monospace", fontSize: 10.5, color: 'rgba(255,255,255,0.45)' }}>
                  {(videos[i].scenes || []).length} scenes · {Math.round(window.previewDuration(videos[i]))}s</div>
              </div>
              <div style={{ display: 'flex', gap: 4 }}>
                <button style={btnStyle({ padding: '4px 9px', fontSize: 12 })} onClick={() => move(idx, -1)}>↑</button>
                <button style={btnStyle({ padding: '4px 9px', fontSize: 12 })} onClick={() => move(idx, 1)}>↓</button>
              </div>
            </div>
          ))}
        </div>
        <Section title="Master soundtrack" hint="optional · across the whole video">
          {audioRef ? (
            <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
              <span style={{ flex: 1, fontSize: 12.5, color: 'rgba(255,255,255,0.75)' }}>♪ {audioRef.name}</span>
              <button style={btnStyle({ padding: '6px 10px', fontSize: 12 })} onClick={() => setPickAudio(true)}>Change</button>
              <button style={btnStyle({ padding: '6px 10px', fontSize: 12, color: '#ff8089' })} onClick={() => setAudioRef(null)}>✕</button>
            </div>
          ) : (
            <button style={btnStyle({ width: '100%', padding: '9px', fontSize: 12.5 })} onClick={() => setPickAudio(true)}>
              + Choose music from storage</button>
          )}
        </Section>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: 8 }}>
          <span style={{ fontFamily: "'JetBrains Mono',monospace", fontSize: 12, color: 'rgba(255,255,255,0.55)' }}>
            {seq.length} previews · {Math.round(totalDur)}s total</span>
          <div style={{ display: 'flex', gap: 8 }}>
            <button style={btnStyle()} onClick={onClose} disabled={busy}>Cancel</button>
            <button style={btnStyle({ background: 'linear-gradient(180deg,#3b78ff,#1f54d6)', border: '1px solid #3b78ff',
              opacity: busy || !seq.length ? 0.5 : 1 })} onClick={run} disabled={busy || !seq.length}>
              {busy ? 'Rendering…' : 'Render combined video'}</button>
          </div>
        </div>
      </div>
      {pickAudio ? (
        <StoragePicker assets={assets} accept="audio" title="Master soundtrack"
          onPick={a => { setAudioRef(window.assetToRef(a)); setPickAudio(false); }}
          onClose={() => setPickAudio(false)}
          onUpload={async (f) => { const a = await window.storeAsset(setAssets, { blob: f, source: 'upload', name: f.name }); setAudioRef(window.assetToRef(a)); setPickAudio(false); }} />
      ) : null}
    </div>
  );
}

Object.assign(window, { VideoEditor, PreviewThumb, CombineModal, AnimEditor, Pick });
