/* ============ Genius — SPOT VIDÉO (20/09/2026) ============
   Direction : Higgsfield = fournisseur principal des médias, Seedance 2.5 = moteur vidéo par défaut, Astra = concepts + scènes.
   Parcours : brief existant → concepts (Astra) → storyboard éditable (scènes précises) → estimation → génération d'UNE scène par clip
   (Seedance 2.5) → montage ffmpeg (logo, textes, appel à l'action, voix off) → aperçu → association à la campagne.
   Les clips sources sont conservés : textes / voix / écran de fin se refont sans payer ; refaire UN plan = une génération.
   Aucun pourcentage inventé : les états affichés sont ceux du serveur. */
const SP_STYLES = [['documentaire', 'Documentaire', 'réel, lumière naturelle, caméra à l’épaule'], ['premium', 'Premium', 'spot TV, lumière dorée, mouvements lents'], ['dynamique', 'Dynamique', 'rythmé, lumineux, moderne'], ['chaleureux', 'Chaleureux', 'famille, intérieur, moments proches']];
const SP_OBJ = [['leads', 'Demandes de devis'], ['notoriete', 'Notoriété'], ['offre', 'Mettre en avant l’offre']];
const SP_FORMATS = [['9:16', 'Vertical 9:16', 'Stories, Reels, TikTok'], ['16:9', 'Paysage 16:9', 'YouTube, site, écran'], ['1:1', 'Carré 1:1', 'Fil Facebook / LinkedIn']];
const SP_DURATIONS = [8, 12, 16, 24];
const SP_TERMINAL = ['completed', 'failed', 'nsfw', 'canceled', 'download_failed', 'submit_uncertain'];
function spFmtUsd(v) { return v == null ? '—' : Number(v).toLocaleString('fr-FR', { minimumFractionDigits: 2, maximumFractionDigits: 3 }) + ' $'; }
function spEstLine(est, mock) { if (mock) return 'Démonstration : aucun coût.'; if (!est) return 'Non estimé.'; if (!est.known) return 'Estimation indisponible' + (est.reason ? ' — ' + est.reason : '') + (est.scenes ? ' (' + est.scenes.filter(s => !s.known).map(s => 'plan ' + s.n + (s.reason ? ' : ' + s.reason : '')).join(' · ') + ')' : ''); return (est.computed ? 'Calculé selon la formule Higgsfield : ' : 'Estimation Higgsfield : ') + spFmtUsd(est.usd) + (est.credits != null ? ' · ' + est.credits + ' crédits' : '') + (est.eur != null ? ' (≈ ' + est.eur + ' €)' : '') + (est.computed ? ' — avant remise, pas un prix garanti.' : ' — indicative, pas un prix garanti.'); }

/* ---------- choix simples (objectif, style, format, durée) + références ---------- */
function SpChoices({ settings, onChange, brief, refs, setRefs, disabled }) {
  const set = (k, v) => onChange({ ...settings, [k]: v });
  return <div className="card st-settings">
    <div className="sec-head" style={{ padding: 0, marginBottom: 12 }}><div><div className="sec-title">Le spot en quatre choix</div><div className="sec-sub">Le reste (prompts, résolution, son) est dans « Avancé » après le storyboard.</div></div></div>
    <div className="st-set-row"><span>Objectif</span><div className="chips">{SP_OBJ.map(([k, l]) => <button key={k} type="button" className={'chip' + (settings.objective === k ? ' on' : '')} disabled={disabled} onClick={() => set('objective', k)}>{l}</button>)}</div></div>
    <div className="st-set-row"><span>Style</span><div className="chips">{SP_STYLES.map(([k, l, h]) => <button key={k} type="button" className={'chip' + (settings.style === k ? ' on' : '')} title={h} disabled={disabled} onClick={() => set('style', k)}>{l}</button>)}</div></div>
    <div className="st-set-row"><span>Format</span><div className="chips">{SP_FORMATS.map(([k, l, h]) => <button key={k} type="button" className={'chip' + (settings.aspect_ratio === k ? ' on' : '')} title={h} disabled={disabled} onClick={() => set('aspect_ratio', k)}>{l}</button>)}</div></div>
    <div className="st-set-row"><span>Durée</span><div className="chips">{SP_DURATIONS.map(d => <button key={d} type="button" className={'chip' + (Number(settings.duration) === d ? ' on' : '')} disabled={disabled} onClick={() => set('duration', d)}>{d} s</button>)}<span className="sec-sub">+ écran de fin (logo, appel à l’action)</span></div></div>
    {(brief.images || []).length > 0 && <div className="st-set-row"><span>Références</span><div><div className="st-refpick">{brief.images.map((im, i) => { const on = refs.includes(im.url); return <button type="button" key={i} className={'st-ref' + (on ? ' on' : '')} title={im.label} disabled={disabled} onClick={() => setRefs(on ? refs.filter(u => u !== im.url) : [...refs, im.url])}><img src={im.url} alt="" /></button>; })}</div><div className="sec-sub" style={{ marginTop: 4 }}>{refs.length ? 'Les scènes garderont le style de ' + refs.length + ' visuel' + (refs.length > 1 ? 's' : '') + ' (Seedance 2.5 références → vidéo).' : 'Facultatif : cochez un visuel de campagne pour garder son style dans chaque scène.'}</div></div></div>}
  </div>;
}

/* ---------- storyboard éditable (une scène = un clip) ---------- */
function SpStoryboard({ spot, onChange, disabled }) {
  const scenes = spot.scenes || []; const setScenes = a => onChange({ scenes: a });
  const upd = (i, patch) => setScenes(scenes.map((s, j) => j === i ? { ...s, ...patch } : s));
  const total = scenes.reduce((s, x) => s + (Number(x.seconds) || 0), 0);
  return <div className="card st-prep">
    <div className="sec-head" style={{ padding: 0, marginBottom: 12 }}><div><div className="sec-title">Storyboard — {scenes.length} plan{scenes.length > 1 ? 's' : ''} · {total} s</div><div className="sec-sub">Chaque plan est généré séparément par Seedance 2.5 (4 à 30 s), puis monté. Tout est modifiable ; un plan modifié après génération devra être regénéré (annoncé).</div></div></div>
    <div className="st-sb">{scenes.map((s, i) => <div key={i} className={'st-shot' + (s.stale ? ' st-shot-stale' : '')}>
      <div className="st-shot-h"><span className="st-shot-n">{i + 1}</span><input className="st-shot-title" value={s.shot || ''} disabled={disabled} onChange={e => upd(i, { shot: e.target.value })} placeholder="Titre du plan" /><label className="st-sec"><input type="number" min="4" max="30" value={s.seconds || 4} disabled={disabled} onChange={e => upd(i, { seconds: Math.max(4, Math.min(30, Number(e.target.value) || 4)) })} /> s</label>{scenes.length > 1 && !disabled && <button type="button" className="lnk lnk-muted" onClick={() => setScenes(scenes.filter((_, j) => j !== i).map((x, j) => ({ ...x, n: j + 1 })))} title="Retirer ce plan">✕</button>}</div>
      <textarea rows={2} value={s.action || ''} disabled={disabled} onChange={e => upd(i, { action: e.target.value })} placeholder="Sujet et action (qui, quoi, où)" />
      <div className="st-shot-row"><input value={s.camera || ''} disabled={disabled} onChange={e => upd(i, { camera: e.target.value })} placeholder="Cadrage et mouvement de caméra" /><input value={s.light || ''} disabled={disabled} onChange={e => upd(i, { light: e.target.value })} placeholder="Lumière (heure, source)" /></div>
      <div className="st-shot-row"><input value={s.overlay || ''} disabled={disabled} onChange={e => upd(i, { overlay: e.target.value })} placeholder="Texte à l’écran pendant ce plan (facultatif, ajouté au montage)" maxLength={60} /><span className="sec-sub" style={{ alignSelf: 'center' }}>{s.status && s.status !== 'draft' ? 'Clip : ' + ((ST_STATUS[s.status] || [s.status])[0]) + (s.stale ? ' · modifié → à regénérer' : '') : 'Pas encore généré'}</span></div>
      <details className="st-adv"><summary>Prompt envoyé à Seedance 2.5 (anglais)</summary><textarea rows={4} value={s.prompt || ''} disabled={disabled} onChange={e => upd(i, { prompt: e.target.value })} style={{ fontFamily: 'var(--mono)', fontSize: 12.5, width: '100%' }} /></details>
    </div>)}{scenes.length < 6 && !disabled && <button type="button" className="st-add" onClick={() => setScenes([...scenes, { n: scenes.length + 1, seconds: 4, shot: '', action: '', camera: '', light: '', prompt: '', overlay: '' }])}><Icon name="plus" size={14} /> Ajouter un plan</button>}</div>
    <div className="st-script" style={{ marginTop: 12 }}>
      <StField label="Voix off (française, lue par la voix de synthèse)" value={spot.script.voiceover} onChange={v => onChange({ script: { ...spot.script, voiceover: v } })} rows={2} />
      <div className="st-shot-row"><StField label="Appel à l’action (écran de fin)" value={spot.script.cta} onChange={v => onChange({ script: { ...spot.script, cta: v } })} /><StField label="Site (écran de fin)" value={spot.script.site} onChange={v => onChange({ script: { ...spot.script, site: v } })} /></div>
    </div>
    <details className="st-adv"><summary>Avancé — style commun et son</summary>
      <StField label="Consigne de style commune à toutes les scènes (anglais)" value={spot.settings.style_prompt} onChange={v => onChange({ settings: { ...spot.settings, style_prompt: v } })} rows={2} mono />
      <div className="st-set-row"><span>Son</span><Toggle on={spot.settings.generate_audio !== false} onChange={v => onChange({ settings: { ...spot.settings, generate_audio: v } })} label={spot.settings.generate_audio !== false ? 'Ambiance sonore générée par Seedance' : 'Clips muets'} /></div>
      <div className="sec-sub">Résolution : 720p (Seedance 2.5, maximum documenté). Format : {spot.settings.aspect_ratio}. Changer le format regénère tous les plans (annoncé avant confirmation).</div>
    </details>
  </div>;
}

/* ---------- scènes générées : états réels, aperçu, refaire un plan ---------- */
function SpScenes({ spot, onRedo, onCancel, onRetry, busy }) {
  return <div className="sp-scenes">{(spot.scenes || []).map(s => { const t = s.task; const st = t ? stStatus(t) : ['Non généré', 'muted']; const m = s.media; const portrait = (spot.settings.aspect_ratio || '9:16') === '9:16';
    return <div key={s.n} className={'card sp-scene ' + st[1]}>
      <div className="sp-scene-h"><span className="st-shot-n">{s.n}</span><b>{s.shot || 'Plan ' + s.n}</b><span className="sec-sub">{s.seconds} s{s.estimate && s.estimate.usd != null ? ' · ' + spFmtUsd(s.estimate.usd) : ''}</span><span className={'st-ver-s ' + st[1]} style={{ marginLeft: 'auto' }}>{st[0]}</span></div>
      <div className="sp-scene-v">{m && m.url ? <VideoPlayer src={m.url} poster={m.poster} width={portrait ? 162 : 288} height={portrait ? 288 : 162} /> : (t && (t.status === 'queued' || t.status === 'in_progress' || t.phase === 'downloading')) ? <VideoGenerationLoader phase={t.phase === 'downloading' ? 'final' : t.status === 'in_progress' ? 'gen' : 'prep'} title={t.status === 'queued' ? 'En attente chez Higgsfield…' : t.phase === 'downloading' ? 'Rapatriement…' : 'Seedance 2.5 génère ce plan…'} sub="État réel du fournisseur, sans pourcentage inventé." /> : <div className="sp-scene-ph">{t && t.error ? <span className="err-txt">{t.error}</span> : <span className="sec-sub">{s.action}</span>}</div>}</div>
      <div className="so-bar">{t && t.status === 'queued' && <Btn size="sm" kind="ghost" onClick={() => onCancel(t)} disabled={!!busy}>Annuler</Btn>}{t && t.status === 'download_failed' && <Btn size="sm" icon="refresh" onClick={() => onRetry(t)} disabled={!!busy}>Rapatrier</Btn>}{(!t || SP_TERMINAL.includes(t.status)) && <Btn size="sm" kind="ghost" icon="refresh" onClick={() => onRedo(s)} disabled={!!busy}>{t ? 'Refaire ce plan' : 'Générer ce plan'}</Btn>}{s.history > 0 && <span className="sec-sub">{s.history} version{s.history > 1 ? 's' : ''} précédente{s.history > 1 ? 's' : ''}</span>}</div>
    </div>; })}</div>;
}

/* ---------- montage : textes, voix, rendu, aperçu ---------- */
function SpMontage({ spot, onRender, onChange, busy, onAttach, onDownload }) {
  const r = spot.render || {}; const voices = spot.voices || { marin: 'Marin' }; const portrait = (spot.settings.aspect_ratio || '9:16') === '9:16'; const w = portrait ? 324 : 560; const h = Math.round(w * (portrait ? 16 / 9 : (spot.settings.aspect_ratio === '1:1' ? 1 : 9 / 16)));
  const ready = spot.gen_status === 'generated';
  return <div className="st-c">
    <div className="st-c-main"><div className="card st-stage">
      {r.status === 'done' && r.url ? <div className="st-view"><VideoPlayer src={r.url} poster={r.poster} width={w} height={h} /></div> : busy === 'render' ? <div className="st-view st-view-wait"><VideoGenerationLoader phase="final" title="Montage en cours…" sub="Plans, textes, écran de fin et voix off. Aucune génération payante." /></div> : <div className="st-empty"><Spark size={26} /><div>{ready ? 'Tous les plans sont prêts : lancez le montage.' : 'Le montage sera possible quand tous les plans seront générés.'}</div></div>}
      {r.status === 'done' && <div className="st-stage-meta"><div><b>Spot monté</b><span className="sec-sub"> · {r.seconds} s · {spot.settings.aspect_ratio}{r.stale ? ' · textes ou plans modifiés depuis → refaire le montage (gratuit)' : ''}{r.voice_error ? ' · voix : ' + r.voice_error : ''}{r.muted ? ' · ambiance coupée (dialogues détectés)' : ''}</span></div>
        <div className="st-stage-a"><Btn size="sm" kind="ghost" icon="download" onClick={onDownload}>Télécharger</Btn><Btn size="sm" icon="check" onClick={() => onAttach(true)} disabled={!!busy}>Utiliser comme vidéo de la campagne</Btn></div></div>}
      {r.status === 'failed' && <div className="err-txt" style={{ marginTop: 8 }}>Montage échoué : {r.error}</div>}
    </div></div>
    <div className="st-c-side"><div className="card st-actions">
      <div className="sec-title" style={{ marginBottom: 4 }}>Habillage</div><div className="sec-sub" style={{ marginBottom: 10 }}>Modifiable à volonté : le montage réutilise les clips déjà générés, rien n’est facturé.</div>
      {(spot.scenes || []).map(s => <StField key={s.n} label={'Texte plan ' + s.n + (s.shot ? ' · ' + s.shot : '')} value={s.overlay} onChange={v => onChange({ scenes: spot.scenes.map(x => x.n === s.n ? { ...x, overlay: v } : x) })} placeholder="aucun texte" />)}
      <StField label="Voix off" value={spot.script.voiceover} onChange={v => onChange({ script: { ...spot.script, voiceover: v } })} rows={3} placeholder="vide = pas de voix off" />
      <div className="st-set-row"><span>Voix</span><select value={r.voice || 'marin'} onChange={e => onChange({ voice: e.target.value })}>{Object.entries(voices).map(([k, l]) => <option key={k} value={k}>{l}</option>)}</select></div>
      <StField label="Appel à l’action (écran de fin)" value={spot.script.cta} onChange={v => onChange({ script: { ...spot.script, cta: v } })} />
      <StField label="Site (écran de fin)" value={spot.script.site} onChange={v => onChange({ script: { ...spot.script, site: v } })} />
      <Btn full icon="bolt" onClick={onRender} disabled={!ready || busy === 'render'}>{busy === 'render' ? 'Montage…' : (r.status === 'done' ? 'Refaire le montage' : 'Monter la vidéo')}</Btn>
    </div></div>
  </div>;
}

/* ---------- orchestration du spot (après le choix de la direction créative) ---------- */
function SpotStudio({ campaignId, spot, setSpot, status, testMode, T, step, setStep, flash, onBack }) {
  const [busy, setBusy] = React.useState(''); const [err, setErr] = React.useState(''); const [est, setEst] = React.useState(spot && spot.estimate || null); const [confirm, setConfirm] = React.useState(null);
  const saveT = React.useRef(null); const pend = React.useRef({});
  const mock = !!(testMode || (spot && spot.mock) || (status && status.mode === 'mock'));
  /* édition : état local immédiat, sauvegarde débouncée et fusionnée */
  const change = patch => { setSpot(sp => { const n = { ...sp }; if (patch.scenes) n.scenes = patch.scenes; if (patch.script) n.script = patch.script; if (patch.settings) n.settings = patch.settings; if (patch.refs) n.refs = patch.refs; if (patch.voice) n.render = { ...(n.render || {}), voice: patch.voice }; return n; });
    Object.assign(pend.current, patch); clearTimeout(saveT.current); saveT.current = setTimeout(() => { const p = pend.current; pend.current = {}; const body = { ...T(), spot: spot.id }; if (p.scenes) body.scenes = p.scenes.map(s => ({ n: s.n, seconds: s.seconds, shot: s.shot, action: s.action, camera: s.camera, light: s.light, prompt: s.prompt, overlay: s.overlay })); if (p.script) body.script = p.script; if (p.settings) body.settings = p.settings; if (p.refs) body.refs = p.refs; if (p.voice) body.voice = p.voice;
      stPost('studio_spot_update', body).then(j => { if (j && j.ok) setSpot(sp => ({ ...j.spot, scenes: j.spot.scenes.map((s, i) => ({ ...s, overlay: (sp.scenes[i] && sp.scenes[i].overlay !== undefined) ? sp.scenes[i].overlay : s.overlay })), script: sp.script })); }).catch(() => {}); setEst(null); }, 700); };
  /* suivi des scènes en cours : état réel, recul progressif */
  React.useEffect(() => { if (!spot) return; const pending = (spot.scenes || []).filter(s => s.task && !SP_TERMINAL.includes(s.task.status)); if (!pending.length) return; let delay = 3000, stop = false, timer = null;
    const tick = () => stPost('studio_spot', { ...T(), spot: spot.id, refresh: 1 }).then(j => { if (stop) return; if (j && j.ok) { setSpot(sp => ({ ...j.spot, script: sp.script, scenes: j.spot.scenes.map((s, i) => ({ ...s, overlay: sp.scenes[i] ? sp.scenes[i].overlay : s.overlay })) })); if (j.spot.gen_status === 'generated') flash('Tous les plans sont générés et conservés dans Genius.'); } delay = Math.min(10000, delay * 1.3); timer = setTimeout(tick, delay); }).catch(() => { if (!stop) timer = setTimeout(tick, 8000); });
    timer = setTimeout(tick, delay); return () => { stop = true; clearTimeout(timer); }; }, [spot && spot.scenes.map(s => (s.task ? s.task.id + ':' + s.task.status : 'x')).join('|')]);
  const runEstimate = (scene) => { setBusy('estimate'); return stPost('studio_spot_estimate', { ...T(), spot: spot.id, ...(scene ? { scene } : {}) }).then(j => { setBusy(''); const e = j && j.ok ? j : { known: false, reason: (j && j.error) || 'erreur' }; if (!scene) setEst(e); return e; }).catch(() => { setBusy(''); return { known: false, reason: 'réseau' }; }); };
  const askGenerate = (scenes) => { const c = { scenes, loading: true, estimate: null }; setConfirm(c); runEstimate(scenes && scenes.length === 1 ? scenes[0] : null).then(e => setConfirm(x => x ? { ...x, estimate: e, loading: false } : x)); };
  const doGenerate = () => { const c = confirm; if (!c) return; setConfirm(null); setBusy('generate'); setErr('');
    stPost('studio_spot_generate', { ...T(), spot: spot.id, confirmed: true, client_key: stKey(), ...(c.scenes ? { scenes: c.scenes } : {}) }).then(j => { setBusy(''); if (j && j.spot) setSpot(sp => ({ ...j.spot, script: sp.script })); if (!j || !j.ok) setErr((j && (j.message || (j.errors || []).join(' · ') || j.error)) || 'échec'); else { if (j.errors && j.errors.length) setErr(j.errors.join(' · ')); setStep(2); } }).catch(() => { setBusy(''); setErr('réseau'); }); };
  const cancelTask = t => stPost('studio_cancel', { task: t.id }).then(j => { if (!(j && j.ok)) flash((j && j.error) || 'annulation impossible'); return stPost('studio_spot', { ...T(), spot: spot.id }); }).then(j => { if (j && j.ok) setSpot(sp => ({ ...j.spot, script: sp.script })); });
  const retry = t => { setBusy('retry'); stPost('studio_retry_download', { task: t.id }).then(j => { setBusy(''); flash(j && j.ok ? 'Clip rapatrié.' : ((j && j.error) || 'échec')); return stPost('studio_spot', { ...T(), spot: spot.id }); }).then(j => { if (j && j.ok) setSpot(sp => ({ ...j.spot, script: sp.script })); }); };
  const render = () => { setBusy('render'); setErr(''); clearTimeout(saveT.current); const p = pend.current; pend.current = {};
    const body = { ...T(), spot: spot.id, script: spot.script, overlays: Object.fromEntries((spot.scenes || []).map(s => [String(s.n), s.overlay || ''])), voice: (spot.render && spot.render.voice) || 'marin' };
    window.geniusLong('studio_spot_render', body).then(j => { setBusy(''); if (j && j.ok) { setSpot(sp => ({ ...j.spot, script: sp.script })); flash('Spot monté.'); } else { if (j && j.spot) setSpot(sp => ({ ...j.spot, script: sp.script })); setErr((j && (j.message || j.error)) || 'montage échoué'); } }).catch(e => { setBusy(''); setErr(e.message || 'réseau'); }); };
  const attach = asMain => { setBusy('attach'); stPost('studio_spot_attach', { spot: spot.id, as_main: asMain }).then(j => { setBusy(''); flash(j && j.ok ? 'Spot associé à la campagne' + (asMain ? ' et défini comme vidéo principale.' : '.') : ((j && j.error) || 'échec')); }).catch(() => { setBusy(''); }); };
  const download = () => { const r = spot.render || {}; if (r.url) window.open(r.url, '_blank', 'noopener'); };
  if (!spot) return null;
  const costs = spot.costs || {}; const model = (status && status.defaults && status.defaults.video) || null; const blocker = status && status.blocker;
  return <>
    {err && <div className="err-txt" style={{ marginBottom: 12 }}>{err}</div>}
    {step === 1 && <div className="st-b">
      <SpStoryboard spot={spot} onChange={change} disabled={busy === 'generate'} />
      <div className="st-b-side">
        <div className="card st-settings">
          <div className="sec-title" style={{ marginBottom: 6 }}>Ce qui sera généré</div>
          <div className="st-modal-rows"><div><span>Modèle</span><b>{model ? model.label : 'Seedance 2.5'} · {status && status.video_resolution || '720p'}</b></div><div><span>Fournisseur</span><b>{mock ? 'aucun (démonstration)' : 'Higgsfield'}</b></div><div><span>Plans</span><b>{spot.scenes.length} clip{spot.scenes.length > 1 ? 's' : ''} · {spot.scenes.reduce((s, x) => s + Number(x.seconds || 0), 0)} s</b></div><div><span>Références</span><b>{(spot.refs || []).length ? (spot.refs.length + ' visuel' + (spot.refs.length > 1 ? 's' : '')) : 'aucune'}</b></div><div><span>Astra / GPT (préparation)</span><b>{spFmtUsd(costs.llm_usd)} · {costs.llm_calls || 0} appel{(costs.llm_calls || 0) > 1 ? 's' : ''}</b></div></div>
          {model && model.available === false && !mock && <div className="err-txt" style={{ marginTop: 8 }}>{model.blocker}</div>}
          <div className="st-cost"><div><div className="st-cost-l">Coût estimé (médias)</div><div className={'st-cost-v' + (est && est.known ? '' : ' unknown')}>{busy === 'estimate' ? 'Estimation…' : spEstLine(est, mock)}</div></div><Btn kind="ghost" size="sm" icon="refresh" onClick={() => runEstimate()} disabled={busy === 'estimate'}>Estimer</Btn></div>
          {est && est.known && est.scenes && <div className="sec-sub" style={{ marginTop: 6 }}>{est.scenes.map(s => 'plan ' + s.n + ' : ' + spFmtUsd(s.usd)).join(' · ')}</div>}
        </div>
        <div className="card st-launch">
          <div className="sec-sub">{mock ? 'Démonstration : génération simulée, aucun coût.' : 'Chaque plan est une génération payante chez Higgsfield : le modèle et l’estimation sont affichés avant confirmation.'}</div>
          <Btn full onClick={() => askGenerate(null)} disabled={!!busy || (!mock && blocker)} icon="bolt">{busy === 'generate' ? 'Lancement…' : (spot.gen_status === 'draft' ? 'Générer les plans' : 'Regénérer les plans modifiés')}</Btn>
          {!mock && blocker && <div className="err-txt" style={{ marginTop: 6 }}>{blocker}</div>}
          {spot.gen_status !== 'draft' && <button type="button" className="lnk lnk-muted" style={{ marginTop: 8 }} onClick={() => setStep(2)}>Voir les plans générés →</button>}
        </div>
      </div>
    </div>}
    {step === 2 && <>
      <div className="list-head" style={{ alignItems: 'center', marginBottom: 10 }}><div className="sec-sub">{spot.gen_status === 'generated' ? 'Tous les plans sont prêts.' : spot.gen_status === 'generating' ? 'Génération en cours — vous pouvez quitter cet écran, elle continue côté serveur.' : spot.gen_status === 'partial' ? 'Certains plans manquent : refaites-les ou ajustez le storyboard.' : 'Aucun plan généré.'} Médias : {spFmtUsd(costs.media_usd)} estimés · Astra : {spFmtUsd(costs.llm_usd)}</div><div className="head-actions"><Btn size="sm" kind="ghost" onClick={() => setStep(1)}>← Storyboard</Btn><Btn size="sm" icon="bolt" onClick={() => setStep(3)} disabled={spot.gen_status !== 'generated'}>Montage →</Btn></div></div>
      <SpScenes spot={spot} onRedo={s => askGenerate([s.n])} onCancel={cancelTask} onRetry={retry} busy={busy} />
    </>}
    {step === 3 && <SpMontage spot={spot} onRender={render} onChange={change} busy={busy} onAttach={attach} onDownload={download} />}
    {confirm && <div className="st-modal-bg" onClick={() => setConfirm(null)}><div className="st-modal" onClick={e => e.stopPropagation()}>
      <div className="sec-title">{confirm.scenes ? 'Refaire le plan ' + confirm.scenes.join(', ') + ' ?' : 'Générer les plans ?'}</div>
      <div className="st-modal-rows"><div><span>Fournisseur</span><b>{mock ? 'aucun (démonstration)' : 'Higgsfield'}</b></div><div><span>Modèle</span><b>{(spot.refs || []).length ? 'Seedance 2.5 · références → vidéo' : 'Seedance 2.5 · texte → vidéo'} · 720p</b></div><div><span>Plans</span><b>{confirm.scenes ? confirm.scenes.length : spot.scenes.filter(s => !s.task || s.stale || (s.task && ['failed', 'nsfw', 'canceled'].includes(s.task.status))).length} sur {spot.scenes.length}</b></div><div><span>Format · durée</span><b>{spot.settings.aspect_ratio} · {spot.scenes.reduce((s, x) => s + Number(x.seconds || 0), 0)} s</b></div><div><span>Coût estimé</span><b>{confirm.loading ? 'estimation…' : spEstLine(confirm.estimate, mock)}</b></div></div>
      {!mock && confirm.estimate && !confirm.loading && !confirm.estimate.known && <div className="st-notice" style={{ margin: '10px 0 0' }}><Icon name="alert" size={14} /> <span>Sans estimation du fournisseur, la génération sera facturée sur vos crédits Higgsfield sans montant annoncé.</span></div>}
      {confirm.scenes && <div className="sec-sub" style={{ marginTop: 8 }}>Le plan actuel est conservé dans l’historique ; le montage devra être refait (gratuit).</div>}
      <div className="st-modal-a"><Btn kind="ghost" onClick={() => setConfirm(null)}>Annuler</Btn><Btn onClick={doGenerate} disabled={confirm.loading} icon="bolt">Confirmer et lancer</Btn></div>
    </div></div>}
  </>;
}
Object.assign(window, { SpotStudio, SpChoices });
