// SEO GUY — écrans
const { useState: useS, useEffect: useE, useRef: useR } = React;

function GuyThinkingLoader({ title, sub, block, compact }) {
  return (
    <div className={`sg-guy-thinking${block ? " is-block" : ""}${compact ? " is-compact" : ""}`}>
      <img className="sg-guy-thinking-img" src={POSE.analyse} alt="SEO GUY réfléchit" />
      <div className="sg-guy-thinking-text">
        <p className="sg-guy-thinking-title">{title}</p>
        {sub ? <p className="sg-guy-thinking-sub">{sub}</p> : null}
        <p className="sg-guy-thinking-status">
          <span className="sg-spinner sm" aria-hidden="true" />
          Rédaction et optimisation en cours…
        </p>
      </div>
    </div>
  );
}

// =================== ACCUEIL (cockpit zéro effort) ===================
function HomeScreen({ queue, quota: quotaProp, onValidate, onReview, publishedThisMonth, showMascot, obStep, obDismissed, onboardingIncomplete, onResumeOnboarding, liveDashboard, kpis: kpiList, busy, clientName, articlesPerWeek, gscConnected, shopifyConnected }) {
  const kpiData = (kpiList && kpiList.length) ? kpiList : mapKpisFromPerformance(liveDashboard?.performance);
  const stats = liveDashboard?.stats;
  const quota = quotaProp ?? liveDashboard?.quota;
  const next = queue[0];
  const [phase, setPhase] = useS("idle"); // idle | publishing | done
  const greetName = (clientName || "").trim() || null;
  const greeting = greetName ? `Bonjour ${greetName} 👋` : "Bonjour 👋";
  const needsGeneration =
    next &&
    !next?.bodyHtml?.trim() &&
    next?.raw?.status !== "content_ready" &&
    next?.status !== "content_ready";
  const quotaExhausted = quota && !quota.unlimited && !quota.canGenerate;
  const quotaBlocked = needsGeneration && quotaExhausted;
  const reviewBlocked = needsGeneration && quotaExhausted;
  const weeklyTarget =
    articlesPerWeek && articlesPerWeek > 0
      ? articlesPerWeek
      : quota && !quota.unlimited && quota.limit > 0
        ? quota.limit
        : 8;
  const compare = buildHomeCompareSnapshot({
    performance: liveDashboard?.performance ?? null,
    weeklyTarget,
    articlesPerYearBeforeInstall:
      liveDashboard?.stats?.articlesPerYearBeforeInstall ?? 0,
  });

  async function validate() {
    if (phase !== "idle" || busy || quotaBlocked || !next) return;
    setPhase("publishing");
    try {
      await onValidate?.(next);
      setPhase("done");
      setTimeout(() => setPhase("idle"), 2600);
    } catch (e) {
      setPhase("idle");
      alert(e?.message || "Publication impossible");
    }
  }

  return (
    <div className="sg-screen">
      {onboardingIncomplete && obDismissed && obStep > 0 && (
        <OnboardingResumeBanner step={obStep} onResume={onResumeOnboarding} />
      )}
      <PageHeader
        greeting={greeting}
        title={<>Votre blog tourne <span>tout seul.</span></>}
        sub="SEO GUY a rédigé, optimisé et programmé vos contenus. Il vous reste une chose à faire : valider."
        status="SEO GUY travaille en ce moment"
      />

      {/* Carte centrale : le prochain article prêt */}
      <section className={`sg-next ${phase}${next?.isPepite ? " is-pepite-home" : ""}`}>
        {phase === "done" ? (
          <div className="sg-next-success">
            <img className="sg-next-mascot" src={POSE.mission} alt="SEO GUY" />
            <div>
              <h2 className="sg-next-success-title">C'est publié ! 🎉</h2>
              <p className="sg-next-success-sub">Votre article est en ligne et déjà indexé. SEO GUY prépare le suivant…</p>
            </div>
          </div>
        ) : !next ? (
          <div className="sg-next-empty">
            <img className="sg-next-mascot" src={POSE.thumbsup} alt="SEO GUY" />
            <div>
              <h2 className="sg-next-title">
                {quotaExhausted
                  ? "Quota hebdomadaire atteint"
                  : "Aucune pépite pour l'instant"}
              </h2>
              <p className="sg-next-angle">
                {quotaExhausted && quota
                  ? <>Vous avez utilisé <b>{quota.used}/{quota.limit}</b> article(s) cette semaine. Revenez lundi ou passez au plan supérieur.</>
                  : "Générez des titres dans Mes articles pour alimenter le pipeline."}
              </p>
            </div>
          </div>
        ) : (
          <>
            <div className="sg-next-visual">
              <img className="sg-next-mascot" src={POSE.thumbsup} alt="SEO GUY" />
              {showMascot && <div className="sg-coach-bubble">J'ai trouvé une pépite pour vous 👇</div>}
            </div>
            <div className="sg-next-body">
              {next.isPepite ? (
                <div className="sg-pepite-banner sg-pepite-banner-home" aria-label="Pépite de la semaine">
                  <span><Icon name="spark" size={14} /> Pépite de la semaine</span>
                </div>
              ) : null}
              <span className="sg-next-eyebrow"><Icon name="spark" size={14} /> Prochain article prêt à publier</span>
              <h2 className="sg-next-title">{next.title}</h2>
              <div className="sg-next-meta">
                <span><b>Mot-clé</b> {next.keyword}</span>
                <span><b>Recherches</b> {next.volume}</span>
                <span><b>Difficulté</b> {next.difficulty}</span>
                <span><b>Lecture</b> {next.readMin} min</span>
              </div>
              <p className="sg-next-angle"><Icon name="target" size={15} /> {next.angle}</p>
              <div className="sg-next-foot">
                <div className="sg-next-foot-meta">
                  <div className="sg-next-score">
                    <ScoreRing value={next.score} size={48} />
                    <span>
                      Score SEO<br /><b>Excellent</b>
                      <p className="sg-seo-human-note">
                        Contenu optimisé juste ce qu&apos;il faut pour avoir des résultats sur le long terme
                      </p>
                    </span>
                  </div>
                  {quota && !quota.unlimited ? (
                    <span className="sg-next-quota-note">
                      Quota : <b>{quota.used}/{quota.limit}</b>
                      <small>cette semaine</small>
                    </span>
                  ) : null}
                </div>
                <div className="sg-next-actions">
                  <button type="button" className="sg-btn-ghost" onClick={() => onReview(next)} disabled={phase !== "idle" || busy || reviewBlocked} title={reviewBlocked ? `Quota hebdomadaire atteint (${quota.used}/${quota.limit})` : undefined}>
                    <Icon name="eye" size={16} /> Voir / ajuster
                  </button>
                  <button
                    type="button"
                    className="sg-btn-primary"
                    onClick={validate}
                    disabled={phase !== "idle" || busy || quotaBlocked || !next}
                    title={quotaBlocked ? `Quota hebdomadaire atteint (${quota.used}/${quota.limit})` : undefined}
                  >
                    {phase === "publishing"
                      ? (<><span className="sg-spinner" /> Publication…</>)
                      : (<><Icon name="check" size={16} /> Valider et publier</>)}
                  </button>
                </div>
              </div>
            </div>
          </>
        )}
      </section>

      {/* Bandeau pilotage auto (lecture seule, rassure) */}
      <section className="sg-auto">
        <div className="sg-auto-item">
          <span className="sg-auto-ic"><Icon name="calendar" /></span>
          <div><p className="sg-auto-val">{publishedThisMonth} articles</p><p className="sg-auto-lab">publiés ce mois-ci</p></div>
        </div>
        <div className="sg-auto-sep" />
        <div className="sg-auto-item">
          <span className="sg-auto-ic"><Icon name="bolt" /></span>
          <div><p className="sg-auto-val">Mardi 9 h</p><p className="sg-auto-lab">prochaine publication auto</p></div>
        </div>
        <div className="sg-auto-sep" />
        <div className="sg-auto-item">
          <span className="sg-auto-ic"><Icon name="target" /></span>
          <div><p className="sg-auto-val">{stats?.keywordsTotal ?? 0} mots-clés</p><p className="sg-auto-lab">suivis et priorisés</p></div>
        </div>
        <div className="sg-auto-sep" />
        <div className="sg-auto-item">
          <span className="sg-auto-ic"><Icon name="check" /></span>
          <div>
            <p className="sg-auto-val">
              {shopifyConnected && gscConnected
                ? "Tout est connecté"
                : shopifyConnected
                  ? "Shopify connecté"
                  : gscConnected
                    ? "GSC connectée"
                    : "À connecter"}
            </p>
            <p className="sg-auto-lab">
              {[shopifyConnected && "Shopify", gscConnected && "Search Console"].filter(Boolean).join(" · ") || "Shopify · Search Console"}
            </p>
          </div>
        </div>
      </section>

      {/* KPIs */}
      <section className="sg-kpis">
        {kpiData.map((k) => <KpiCard key={k.label} {...k} />)}
      </section>

      {/* Comparatif avant / après */}
      <section className="sg-compare">
        <div className="sg-compare-head">
          <h3 className="sg-block-title">Depuis SEO GUY</h3>
          <span className="sg-block-note">{publishedThisMonth} articles validés ce mois-ci</span>
        </div>
        <div className="sg-compare-progress">
          <div className="sg-compare-step">
            <p className="sg-compare-lab">{compare.beforeLabel}</p>
            <p className="sg-compare-val">{compare.beforeValue}</p>
            <p className="sg-compare-meta">{compare.beforeMeta}</p>
          </div>
          <div className="sg-compare-arrow" aria-hidden="true"><Icon name="arrow" size={18} /></div>
          <div className="sg-compare-step is-after">
            <p className="sg-compare-lab">{compare.afterLabel}</p>
            <p className="sg-compare-val">{compare.afterValue}</p>
            <p className="sg-compare-meta">{compare.afterMeta}</p>
          </div>
        </div>
      </section>
    </div>
  );
}

// =================== MES ARTICLES ===================
const STATUS_MAP = {
  published: { label: "Publié", tone: "published" },
  scheduled: { label: "Programmé", tone: "scheduled" },
  proposed: { label: "À valider", tone: "draft" },
  saved: { label: "Gardé", tone: "draft" },
  titled: { label: "Titre choisi", tone: "draft" },
  approved: { label: "Approuvé", tone: "draft" },
  generating: { label: "En génération", tone: "scheduled" },
  content_ready: { label: "Brouillon", tone: "draft" },
  failed: { label: "Échec", tone: "draft" },
};

function articleStatusMeta(status) {
  return STATUS_MAP[status] || { label: status || "—", tone: "draft" };
}

function ArticleCoverPicker({ articleId, imageUrl, disabled, busy, onFile }) {
  const inputId = `sg-cover-${articleId}`;
  return (
    <div className="sg-art-cover">
      {imageUrl ? (
        <div className="sg-art-card-cover">
          <img src={imageUrl} alt="" />
        </div>
      ) : null}
      <label
        className={`sg-btn-ghost sm sg-art-cover-btn${busy ? " is-busy" : ""}`}
        htmlFor={inputId}
      >
        {busy ? (
          <><span className="sg-spinner sm" /> Envoi…</>
        ) : (
          <>
            <Icon name="image" size={14} />
            {imageUrl ? "Changer le visuel" : "Ajouter un visuel"}
          </>
        )}
      </label>
      <input
        id={inputId}
        type="file"
        accept="image/jpeg,image/png,image/webp"
        hidden
        disabled={disabled || busy}
        onChange={(e) => {
          const file = e.target.files && e.target.files[0];
          e.target.value = "";
          if (file) onFile(file);
        }}
      />
      <p className="sg-art-cover-hint">{BLOG_COVER_HINT}</p>
    </div>
  );
}

function ArticlesScreen({ articles, queue, titleProposals, onReview, onGenerate, saved = [], onGenerateTitles, busy, generatingId, quota, proposalTotal, onCoverUploaded }) {
  const [coverBusyId, setCoverBusyId] = useS(null);
  const [localCovers, setLocalCovers] = useS({});

  function coverOf(a) {
    const id = a.raw?.id || a.id;
    return localCovers[id] || localCovers[a.id] || a.imageUrl || a.raw?.imageUrl || null;
  }

  async function uploadCover(a, file) {
    const id = a.raw?.id || a.id;
    if (!id || !file) return;
    setCoverBusyId(id);
    try {
      const dataUrl = await resizeBlogCoverFile(file);
      const res = await GuyAPI.uploadArticleImage(id, dataUrl);
      const url = res.article?.imageUrl || res.url;
      if (url) setLocalCovers((prev) => ({ ...prev, [id]: url }));
      onCoverUploaded?.(id, url);
    } catch (e) {
      alert(e.message || "Import du visuel impossible.");
    } finally {
      setCoverBusyId(null);
    }
  }
  const proposed = (titleProposals || queue || []).map((q) => ({
    ...q,
    status: "proposed",
    date: "Proposé aujourd'hui",
    visits: 0,
  }));
  const hiddenByQuota =
    quota && !quota.unlimited && typeof proposalTotal === "number"
      ? Math.max(
          0,
          Math.min(proposalTotal, quota.limit > 0 ? quota.limit : proposalTotal) -
            proposed.length,
        )
      : 0;

  return (
    <div className="sg-screen">
      {/* ---- Section : À valider ---- */}
      {proposed.length > 0 && (
        <section className="sg-art-validate">
          <div className="sg-art-validate-head">
            <div className="sg-art-validate-title-row">
              <img src={POSE.idea} alt="SEO GUY" className="sg-art-validate-img" />
              <div>
                <h3 className="sg-block-title">{proposed.length} sujet{proposed.length > 1 ? "s" : ""} à rédiger</h3>
                <p className="sg-art-validate-sub">
                  Cliquez sur Générer — SEO GUY rédige l'article complet pour vous.
                  {quota && !quota.unlimited ? (
                    <> Quota restant cette semaine : <strong>{quota.remaining}</strong>.</>
                  ) : null}
                  {hiddenByQuota > 0 ? (
                    <> {hiddenByQuota} autre(s) sujet(s) masqué(s) — quota hebdomadaire atteint.</>
                  ) : null}
                </p>
              </div>
            </div>
            {onGenerateTitles && (
              <button type="button" className="sg-btn-primary sm" disabled={busy || generatingId} onClick={onGenerateTitles}>Générer des titres</button>
            )}
          </div>
          <div className="sg-art-cards">
            {proposed.map((a) => (
              <article
                key={a.id}
                className={`sg-art-card${generatingId === a.id ? " is-generating" : ""}${a.isPepite ? " is-pepite" : ""}`}
              >
                {generatingId === a.id ? (
                  <GuyThinkingLoader
                    compact
                    title="SEO GUY rédige…"
                    sub="Contenu, structure et SEO."
                  />
                ) : (
                  <>
                    {a.isPepite ? (
                      <div className="sg-pepite-banner" aria-label="Pépite de la semaine">
                        <span><Icon name="spark" size={14} /> Pépite de la semaine</span>
                      </div>
                    ) : null}
                    <div className="sg-art-card-top">
                      <span className="sg-next-eyebrow"><Icon name="spark" size={13} /> Sujet proposé</span>
                      {a.score != null ? <ScoreRing value={a.score} size={42} /> : null}
                    </div>
                    <h4 className="sg-art-card-title">{a.title}</h4>
                    <div className="sg-art-card-meta">
                      <span><b>Mot-clé</b> {a.keyword}</span>
                      <span><b>Recherches</b> {a.volume}</span>
                      <span><b>Lecture</b> {a.readMin} min</span>
                    </div>
                    <p className="sg-art-card-angle"><Icon name="target" size={13} /> {a.angle}</p>
                    <div className="sg-art-card-cta">
                      <ArticleCoverPicker
                        articleId={a.raw?.id || a.id}
                        imageUrl={coverOf(a)}
                        disabled={busy || generatingId}
                        busy={coverBusyId === (a.raw?.id || a.id)}
                        onFile={(file) => void uploadCover(a, file)}
                      />
                      <button
                        type="button"
                        className="sg-btn-primary"
                        disabled={busy || generatingId || coverBusyId}
                        onClick={() => onGenerate?.(a)}
                      >
                        <Icon name="spark" size={15} /> Générer
                      </button>
                    </div>
                  </>
                )}
              </article>
            ))}
          </div>
        </section>
      )}

      {proposed.length === 0 && quota?.canGenerate && onGenerateTitles ? (
        <section className="sg-panel sg-art-empty-proposals">
          <p className="sg-block-note">
            Aucun sujet proposé pour l'instant.
            {quota && !quota.unlimited ? (
              <> Générez jusqu'à <strong>{quota.limit}</strong> titres pour remplir votre quota hebdomadaire.</>
            ) : null}
          </p>
          <button type="button" className="sg-btn-primary sm" disabled={busy} onClick={onGenerateTitles}>
            Générer des titres
          </button>
        </section>
      ) : null}

      {/* ---- Section : Articles produits (publiés, programmés, brouillons) ---- */}
      {saved.length > 0 && (
        <section className="sg-panel">
          <div className="sg-panel-head">
            <h3 className="sg-block-title">Gardés pour plus tard</h3>
            <span className="sg-block-note">{saved.length} sujet(s)</span>
          </div>
          <div className="sg-list">
            {saved.map((s) => (
              <div key={s.id} className="sg-row is-proposed">
                <div className="sg-row-main">
                  <p className="sg-row-title">{s.title}</p>
                  <p className="sg-row-meta"><span className="sg-row-kw">{s.keyword}</span> · {s.volume}</p>
                </div>
                <div className="sg-row-action">
                  <ArticleCoverPicker
                    articleId={s.raw?.id || s.id}
                    imageUrl={coverOf(s)}
                    disabled={busy || generatingId}
                    busy={coverBusyId === (s.raw?.id || s.id)}
                    onFile={(file) => void uploadCover(s, file)}
                  />
                  <button
                    type="button"
                    className="sg-btn-primary sm"
                    disabled={busy || generatingId === s.id || coverBusyId}
                    onClick={() => onGenerate?.(s)}
                  >
                    {generatingId === s.id ? (<><span className="sg-spinner sm" /> Génération…</>) : "Rédiger l'article"}
                  </button>
                </div>
              </div>
            ))}
          </div>
        </section>
      )}

      <section className="sg-panel">
        <div className="sg-panel-head">
          <h3 className="sg-block-title">Publiés, programmés &amp; brouillons</h3>
          <span className="sg-block-note">{articles.length} article{articles.length > 1 ? "s" : ""}</span>
        </div>
        <div className="sg-list">
          {articles.length === 0 ? (
            <p className="sg-block-note" style={{ padding: "12px 0" }}>Aucun article produit pour l'instant. Lancez une génération pour commencer.</p>
          ) : null}
          {articles.map((a) => {
            const st = articleStatusMeta(a.status);
            const title = a.title || "Sans titre";
            return (
              <div key={a.id} className="sg-row">
                <div className="sg-row-main">
                  <p className="sg-row-title">{title}</p>
                  <p className="sg-row-meta"><Pill tone={st.tone}>{st.label}</Pill> · {a.date} · <span className="sg-row-kw">{a.kw || "—"}</span></p>
                </div>
                <div className="sg-row-stat">
                  {a.status === "published" ? (
                    <>
                      <b>{(a.visits ?? 0).toLocaleString("fr-FR")}</b>
                      <span>visites</span>
                    </>
                  ) : (
                    <span className="sg-row-score-muted">—</span>
                  )}
                </div>
                <div className="sg-row-action">
                  <button type="button" className="sg-btn-ghost sm" onClick={() => onReview(a)}>
                    <Icon name="eye" size={15} /> Voir
                  </button>
                </div>
              </div>
            );
          })}
        </div>
      </section>
    </div>
  );
}

// =================== RÉSULTATS ===================
function Sparkline({ data, w = 720, h = 220 }) {
  const series = Array.isArray(data)
    ? data.map(Number).filter((v) => Number.isFinite(v))
    : [];
  if (series.length === 0) return null;
  const max = Math.max(...series);
  const min = Math.min(...series);
  const span = max - min || 1;
  const pts = series.map((v, i) => {
    const x = series.length === 1 ? w / 2 : (i / (series.length - 1)) * w;
    const y = h - ((v - min) / span) * (h - 30) - 15;
    return [x, y];
  });
  const line = pts.map((p, i) => `${i ? "L" : "M"}${p[0].toFixed(1)} ${p[1].toFixed(1)}`).join(" ");
  const area = `${line} L${w} ${h} L0 ${h} Z`;
  return (
    <svg className="sg-spark" viewBox={`0 0 ${w} ${h}`} preserveAspectRatio="none">
      <defs>
        <linearGradient id="sgfill" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stopColor="var(--accent)" stopOpacity="0.25" />
          <stop offset="100%" stopColor="var(--accent)" stopOpacity="0" />
        </linearGradient>
      </defs>
      <path d={area} fill="url(#sgfill)" />
      <path d={line} fill="none" stroke="var(--accent)" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" />
      {pts.map((p, i) => i === pts.length - 1 && <circle key={i} cx={p[0]} cy={p[1]} r="5" fill="var(--accent)" stroke="#fff" strokeWidth="2.5" />)}
    </svg>
  );
}

// =================== MOTS-CLÉS ===================
const INTENT_MAP = { info:"Informatif", commercial:"Commercial", transac:"Transactionnel" };
const INTENT_TONE = { info:"intent-info", commercial:"intent-com", transac:"intent-tx" };
function diffBadge(d) {
  if (d <= 30) return { label:`Facile · ${d}`, cls:"diff-easy" };
  if (d <= 55) return { label:`Moyen · ${d}`, cls:"diff-med" };
  return { label:`Difficile · ${d}`, cls:"diff-hard" };
}

function KeywordsScreen({ keywords, opportunities, onRefresh, busy }) {
  const [filter, setFilter] = useS("all");
  const filtered = filter==="favorites" ? keywords.filter(k=>k.favorite)
    : filter==="new" ? keywords.filter(k=>!k.hasArticle)
    : keywords;
  return (
    <div className="sg-screen">
      <PageHeader title="Mots-clés" sub={`${keywords.length} mots-clés suivis · SEO GUY les priorise et génère les articles au bon moment.`} status={`${opportunities.length} opportunités repérées`} />
      <div className="sg-toolbar-inline">
        <button type="button" className="sg-btn-ghost sm" disabled={busy} onClick={() => GuyAPI.keywordsPasteImport({ text: "", source: "manual" }).then(() => onRefresh?.())}>Import collage</button>
        <button type="button" className="sg-btn-ghost sm" disabled={busy} onClick={() => GuyAPI.keywordsExtractWebsite().then(() => onRefresh?.())}>Extraction site web</button>
        <button type="button" className="sg-btn-ghost sm" disabled={busy} onClick={() => GuyAPI.keywordsAiSuggest({ description: "" }).then(() => onRefresh?.())}>Suggestion IA</button>
        <button type="button" className="sg-btn-primary sm" disabled={busy} onClick={() => onRefresh?.()}>Actualiser</button>
      </div>
      <section className="sg-panel sg-kw-opps">
        <div className="sg-kw-opp-head">
          <img src={POSE.idea} alt="SEO GUY" className="sg-kw-opp-img" />
          <div>
            <p className="sg-kw-opp-title">J'ai repéré {opportunities.length} opportunités faciles à saisir</p>
            <p className="sg-kw-opp-sub">Peu de concurrence, bon volume — aucun article ne les cible encore. Un clic suffit.</p>
          </div>
        </div>
        <div className="sg-kw-chips">
          {opportunities.map(k => (
            <div key={k.id} className="sg-kw-chip">
              <div className="sg-kw-chip-info">
                <span className="sg-kw-chip-kw">{k.keyword}</span>
                <span className="sg-kw-chip-vol"><Icon name="trending" size={13} /> {k.volume.toLocaleString("fr")} rech./mois</span>
              </div>
              <button className="sg-btn-primary sm"><Icon name="spark" size={13} /> Générer l'article</button>
            </div>
          ))}
        </div>
      </section>
      <section className="sg-panel">
        <div className="sg-panel-head">
          <h3 className="sg-block-title">Tous les mots-clés</h3>
          <div className="sg-seg">
            {[["all","Tous"],["favorites","⭐ Favoris"],["new","Sans article"]].map(([v,l]) => (
              <button key={v} className={filter===v?"is-active":""} onClick={()=>setFilter(v)}>{l}</button>
            ))}
          </div>
        </div>
        <div className="sg-kw-thead">
          <span className="sg-kw-col-kw">Mot-clé</span>
          <span className="sg-kw-col-num">Volume</span>
          <span className="sg-kw-col-badge">Difficulté</span>
          <span className="sg-kw-col-badge">Intention</span>
          <span className="sg-kw-col-pos">Position</span>
          <span className="sg-kw-col-act"></span>
        </div>
        <div className="sg-kw-list">
          {filtered.map(k => {
            const db = diffBadge(k.difficulty);
            return (
              <div key={k.id} className="sg-kw-row">
                <span className="sg-kw-col-kw sg-kw-name">
                  <span className={`sg-star${k.favorite?" is-fav":""}`}>★</span>
                  {k.keyword}
                </span>
                <span className="sg-kw-col-num sg-kw-vol">{k.volume.toLocaleString("fr")}</span>
                <span className="sg-kw-col-badge"><span className={`sg-badge ${db.cls}`}>{db.label}</span></span>
                <span className="sg-kw-col-badge"><span className={`sg-badge ${INTENT_TONE[k.intent]}`}>{INTENT_MAP[k.intent]}</span></span>
                <span className="sg-kw-col-pos">
                  {k.position
                    ? <b className={k.position<=10?"sg-pos-top":k.position<=20?"sg-pos-mid":"sg-pos-low"}>#{k.position}</b>
                    : <span className="sg-pos-none">Non classé</span>}
                </span>
                <span className="sg-kw-col-act">
                  {k.hasArticle
                    ? <Pill tone="published">Article ✓</Pill>
                    : <button className="sg-btn-ghost sm"><Icon name="spark" size={13} /> Générer</button>}
                </span>
              </div>
            );
          })}
        </div>
      </section>
    </div>
  );
}

// =================== CONCURRENTS ===================
function posCell(v) {
  if (!v) return <span className="sg-serp-none">—</span>;
  const cls = v<=3?"sg-pos-top":v<=10?"sg-pos-mid":"sg-pos-low";
  return <b className={cls}>#{v}</b>;
}

// =================== RÉSULTATS (avec onglet GSC) ===================
function compactNum(n) {
  if (n == null || Number.isNaN(Number(n))) return "—";
  const v = Number(n);
  if (v >= 1000) return `${(v / 1000).toFixed(1).replace(".0", "")}K`;
  return v.toLocaleString("fr-FR");
}

function ResultsScreen({ gscQueries = [], performance }) {
  const perf = performance?.data || performance;
  const [tab, setTab] = useS("traffic");
  const gscOn = !!perf?.gscConnected;
  const trafficData = mapTrafficSeries(perf);
  const top = perf?.topArticles ?? [];
  const queries = Array.isArray(gscQueries) ? gscQueries : [];
  const days = perf?.daysBack ?? 30;
  const weekCount = perf?.trafficSeries?.length ?? 0;
  const weeksLabel =
    weekCount === 0
      ? (gscOn ? "Aucune donnée" : "GSC non connectée")
      : weekCount === 1
        ? "1 semaine"
        : `${weekCount} semaines`;
  const gscStatus = gscOn ? "Search Console connectée" : "Search Console non connectée";
  const emptyGsc = gscOn
    ? "Pas encore de données sur cette période."
    : "Connectez Google Search Console pour afficher le trafic.";

  return (
    <div className="sg-screen">
      <PageHeader title="Résultats" sub="L'effet de vos publications sur le trafic et vos positions Google." status={gscStatus} />
      <div className="sg-tabs">
        <button className={`sg-tab${tab==="traffic"?" is-active":""}`} onClick={()=>setTab("traffic")}><Icon name="chart" size={15} /> Trafic</button>
        <button className={`sg-tab${tab==="gsc"?" is-active":""}`} onClick={()=>setTab("gsc")}><Icon name="globe" size={15} /> Search Console</button>
      </div>
      {tab === "traffic" && (
        <>
          <section className="sg-kpis is-3">
            <KpiCard icon={(_R.iconTrafic)||"assets/features/trafic-qualifie.png"} label="Visites organiques" value={gscOn ? compactNum(perf?.visitsSeo?.current) : "—"} delta={gscOn ? formatDeltaPct(perf?.visitsSeo?.deltaPct) : "connectez la GSC"} up={(perf?.visitsSeo?.deltaPct ?? 0) >= 0} />
            <KpiCard icon={(_R.iconSeo)||"assets/features/seo-optimise.png"} label="Position moyenne" value={gscOn && perf?.averagePosition?.current > 0 ? String(perf.averagePosition.current) : "—"} delta={gscOn ? formatPositionDelta(perf?.averagePosition?.deltaPct) : "—"} up={(perf?.averagePosition?.deltaPct ?? 0) <= 0} />
            <KpiCard icon={(_R.iconPilotage)||"assets/features/pilotage-clair.png"} label="Valeur du trafic" value={gscOn && perf?.trafficValueEur != null ? `${Number(perf.trafficValueEur).toLocaleString("fr-FR")} €` : "—"} delta={gscOn ? `sur ${days} j` : "—"} up />
          </section>
          <section className="sg-panel">
            <div className="sg-panel-head"><h3 className="sg-block-title">Trafic organique</h3><div className="sg-legend"><span className="sg-legend-dot" /> {weeksLabel}</div></div>
            {trafficData.length > 0 ? <Sparkline data={trafficData} /> : <p className="sg-block-note">{emptyGsc}</p>}
          </section>
          <section className="sg-panel">
            <div className="sg-panel-head"><h3 className="sg-block-title">Articles les plus performants</h3></div>
            {top.length === 0 ? (
              <p className="sg-block-note">Publiez des articles et connectez la GSC pour voir les performances.</p>
            ) : (
              <div className="sg-top">
                {top.map((t,i) => (
                  <div key={t.articleId || t.title || i} className="sg-top-row">
                    <span className="sg-top-rank">{i+1}</span>
                    <div className="sg-top-main"><p className="sg-top-title">{t.title}</p><p className="sg-top-kw">{t.revenue ? `CA ${t.revenue} €` : ""}</p></div>
                    <div className="sg-top-pos"><b>#{t.position ?? t.pos ?? "—"}</b><span>Google</span></div>
                    <div className="sg-top-vis"><b>{Number(t.visits || 0).toLocaleString("fr")}</b><span>visites</span></div>
                  </div>
                ))}
              </div>
            )}
          </section>
        </>
      )}
      {tab === "gsc" && (
        <>
          <section className="sg-kpis is-3">
            <KpiCard icon={(_R.iconTrafic)||"assets/features/trafic-qualifie.png"} label={`Clics (${days} j)`} value={gscOn ? compactNum(perf?.visitsSeo?.current) : "—"} delta={gscOn ? formatDeltaPct(perf?.visitsSeo?.deltaPct) : "connectez la GSC"} up={(perf?.visitsSeo?.deltaPct ?? 0) >= 0} />
            <KpiCard icon={(_R.iconSeo)||"assets/features/seo-optimise.png"} label="Impressions" value={gscOn ? compactNum(perf?.impressionsSeo?.current) : "—"} delta={gscOn ? formatDeltaPct(perf?.impressionsSeo?.deltaPct) : "—"} up={(perf?.impressionsSeo?.deltaPct ?? 0) >= 0} />
            <KpiCard icon={(_R.iconPilotage)||"assets/features/pilotage-clair.png"} label="CTR moyen" value={gscOn && perf?.averageCtr?.current != null ? `${perf.averageCtr.current} %` : "—"} delta={gscOn ? formatDeltaPct(perf?.averageCtr?.deltaPct, " pt") : "—"} up={(perf?.averageCtr?.deltaPct ?? 0) >= 0} />
          </section>
          <section className="sg-panel">
            <div className="sg-panel-head">
              <h3 className="sg-block-title">Requêtes de recherche</h3>
              <span className="sg-block-note">{days} derniers jours</span>
            </div>
            {queries.length === 0 ? (
              <p className="sg-block-note">{emptyGsc}</p>
            ) : (
              <>
                <div className="sg-gsc-thead">
                  <span className="sg-gsc-col-q">Requête</span>
                  <span className="sg-gsc-col-n">Clics</span>
                  <span className="sg-gsc-col-n">Impressions</span>
                  <span className="sg-gsc-col-n">CTR</span>
                  <span className="sg-gsc-col-n">Position moy.</span>
                </div>
                <div className="sg-gsc-list">
                  {queries.map((q,i) => (
                    <div key={q.query || i} className="sg-gsc-row">
                      <span className="sg-gsc-col-q sg-gsc-query">{q.query}</span>
                      <span className="sg-gsc-col-n">{Number(q.clicks || 0).toLocaleString("fr")}</span>
                      <span className="sg-gsc-col-n">{Number(q.impressions || 0).toLocaleString("fr")}</span>
                      <span className="sg-gsc-col-n">{q.ctr} %</span>
                      <span className="sg-gsc-col-n">
                        <b className={q.position<=10?"sg-pos-top":q.position<=20?"sg-pos-mid":"sg-pos-low"}>{Number(q.position).toFixed(1)}</b>
                      </span>
                    </div>
                  ))}
                </div>
              </>
            )}
          </section>
        </>
      )}
    </div>
  );
}

function normalizePublicationSlots(slots) {
  return (slots || [])
    .map((s) => ({
      weekday: Number(s.weekday),
      hour: Number(s.hour),
      minute: Number(s.minute ?? 0),
      kind: normalizeSlotKind(s.kind),
      personaIndex:
        s.personaIndex != null && Number.isFinite(Number(s.personaIndex)) && Number(s.personaIndex) >= 0
          ? Number(s.personaIndex)
          : null,
    }))
    .filter((s) => s.weekday >= 1 && s.weekday <= 7 && s.hour >= 0 && s.hour <= 23)
    .sort((a, b) => a.weekday - b.weekday || a.hour - b.hour || a.minute - b.minute);
}

function rhythmSlotPersonaName(slot, personaOptions) {
  if (slot?.personaIndex == null) return null;
  return personaOptions?.[slot.personaIndex]?.name || null;
}

function slotAtDayHour(slots, weekday, hour) {
  return (slots || []).find((s) => s.weekday === weekday && s.hour === hour);
}

function slotKey(s) {
  return `${s.weekday}-${s.hour}-${s.minute ?? 0}`;
}

function findSlotIndexByKey(slots, slot, exceptIndex = -1) {
  const key = slotKey(slot);
  return (slots || []).findIndex((s, i) => i !== exceptIndex && slotKey(s) === key);
}

// =================== MODALE — CONFIGURATION CRÉNEAU ===================
function RhythmSlotModal({
  open,
  editIndex = null,
  initial,
  existingSlots = [],
  personaOptions = [],
  interviewsAllowed = false,
  disabled = false,
  busy = false,
  onClose,
  onSave,
  onDelete,
}) {
  const dayLabels = EDITORIAL_WEEKDAYS || [];
  const slotKinds = PUBLICATION_SLOT_KINDS || [];
  const [weekday, setWeekday] = useS(2);
  const [hour, setHour] = useS(9);
  const [minute, setMinute] = useS(0);
  const [kind, setKind] = useS("article");
  const [personaIndex, setPersonaIndex] = useS(null);
  const [error, setError] = useS("");

  useE(() => {
    if (!open || !initial) return;
    setWeekday(initial.weekday ?? 2);
    setHour(initial.hour ?? 9);
    setMinute(initial.minute ?? 0);
    setKind(normalizeSlotKind(initial.kind));
    setPersonaIndex(
      initial.personaIndex != null && Number.isFinite(Number(initial.personaIndex))
        ? Number(initial.personaIndex)
        : personaOptions[0]?.index ?? null,
    );
    setError("");
  }, [open, initial, personaOptions]);

  if (!open || !initial) return null;

  const isEdit = editIndex != null;
  const hourOptions = [...new Set([...(RHYTHM_GRID_HOURS || []), hour])].sort((a, b) => a - b);
  const dayLabel = dayLabels.find((d) => d.v === weekday)?.label || "—";

  function handleKindChange(nextKind) {
    setKind(nextKind);
    if (nextKind === "interview") {
      setPersonaIndex(null);
    } else if (personaOptions.length && personaIndex == null) {
      setPersonaIndex(personaOptions[0]?.index ?? null);
    }
  }

  function handleValidate() {
    const payload = {
      weekday: Number(weekday),
      hour: Number(hour),
      minute: Number(minute),
      kind: normalizeSlotKind(kind),
      personaIndex:
        normalizeSlotKind(kind) === "article" && personaOptions.length
          ? personaIndex
          : null,
    };
    if (normalizeSlotKind(kind) === "article" && personaOptions.length && payload.personaIndex == null) {
      setError("Choisissez un persona pour ce créneau article.");
      return;
    }
    if (findSlotIndexByKey(existingSlots, payload, editIndex ?? -1) >= 0) {
      setError("Un créneau existe déjà à cette date et heure.");
      return;
    }
    setError("");
    onSave?.(payload, editIndex);
  }

  return (
    <div className="sg-overlay" onClick={() => !busy && onClose?.()}>
      <div
        className="sg-panel sg-rhythm-slot-modal"
        onClick={(e) => e.stopPropagation()}
        role="dialog"
        aria-labelledby="sg-rhythm-slot-modal-title"
      >
        <h3 id="sg-rhythm-slot-modal-title" className="sg-block-title">
          {isEdit ? "Modifier le créneau" : "Nouveau créneau"}
        </h3>
        <p className="sg-set-sub">
          Guy placera ici un contenu du plan éditorial (article ou interview) à l&apos;horaire choisi.
        </p>

        <div className="sg-rhythm-modal-section">
          <p className="sg-cfg-sub-label">Jour</p>
          <div className="sg-rhythm-slot-days">
            {dayLabels.map((d) => (
              <button
                key={d.v}
                type="button"
                className={`sg-seg-product-pick sg-rhythm-day-pick${weekday === d.v ? " is-on" : ""}`}
                disabled={disabled || busy}
                onClick={() => setWeekday(d.v)}
              >
                {d.label}
              </button>
            ))}
          </div>
        </div>

        <div className="sg-rhythm-modal-section">
          <p className="sg-cfg-sub-label">Heure précise</p>
          <div className="sg-rhythm-modal-time">
            <div className="sg-seg sg-rhythm-time-seg sg-rhythm-time-scroll">
              {hourOptions.map((h) => (
                <button
                  key={h}
                  type="button"
                  className={hour === h ? "is-active" : ""}
                  disabled={disabled || busy}
                  onClick={() => setHour(h)}
                >
                  {String(h).padStart(2, "0")}h
                </button>
              ))}
            </div>
            <div className="sg-seg sg-rhythm-time-seg sg-rhythm-min-seg">
              {(EDITORIAL_MINUTE_OPTIONS || [0, 15, 30, 45]).map((m) => (
                <button
                  key={m}
                  type="button"
                  className={minute === m ? "is-active" : ""}
                  disabled={disabled || busy}
                  onClick={() => setMinute(m)}
                >
                  {m === 0 ? ":00" : `:${String(m).padStart(2, "0")}`}
                </button>
              ))}
            </div>
          </div>
          <p className="sg-field-hint">
            Publication : {dayLabel} à {String(hour).padStart(2, "0")}h
            {minute ? `:${String(minute).padStart(2, "0")}` : ""}
          </p>
        </div>

        <div className="sg-rhythm-modal-section">
          <p className="sg-cfg-sub-label">Format</p>
          <div className="sg-seg sg-rhythm-kind-seg">
            {slotKinds.map((k) => (
              <button
                key={k.id}
                type="button"
                className={`sg-rhythm-kind-btn is-${k.id}${normalizeSlotKind(kind) === k.id ? " is-active" : ""}`}
                disabled={disabled || busy || (k.id === "interview" && !interviewsAllowed)}
                onClick={() => handleKindChange(k.id)}
              >
                {k.label}
              </button>
            ))}
          </div>
          {normalizeSlotKind(kind) === "interview" && !interviewsAllowed ? (
            <p className="sg-field-hint">Les interviews ne sont pas incluses dans votre plan actuel.</p>
          ) : null}
        </div>

        {normalizeSlotKind(kind) === "article" && personaOptions.length > 0 ? (
          <div className="sg-rhythm-modal-section">
            <p className="sg-cfg-sub-label">Persona cible</p>
            <div className="sg-rhythm-persona-picks">
              {personaOptions.map((p) => (
                <button
                  key={p.id}
                  type="button"
                  className={`sg-seg-product-pick sg-rhythm-persona-pick${personaIndex === p.index ? " is-on" : ""}`}
                  disabled={disabled || busy}
                  style={
                    personaIndex === p.index
                      ? {
                          borderColor: personaSlotColor(p.index),
                          background: `${personaSlotColor(p.index)}18`,
                          color: personaSlotColor(p.index),
                        }
                      : undefined
                  }
                  onClick={() => setPersonaIndex(p.index)}
                >
                  {p.name}
                </button>
              ))}
            </div>
          </div>
        ) : null}

        {error ? <p className="sg-field-error">{error}</p> : null}

        <div className="sg-rhythm-modal-actions">
          {isEdit ? (
            <button
              type="button"
              className="sg-btn-ghost sm sg-rhythm-modal-delete"
              disabled={disabled || busy}
              onClick={() => onDelete?.(editIndex)}
            >
              Supprimer
            </button>
          ) : (
            <span />
          )}
          <div className="sg-rhythm-modal-actions-right">
            <button type="button" className="sg-btn-ghost sm" disabled={busy} onClick={() => onClose?.()}>
              Annuler
            </button>
            <button
              type="button"
              className="sg-btn-primary sm"
              disabled={disabled || busy}
              onClick={handleValidate}
            >
              {busy ? "Enregistrement…" : "Valider"}
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

// =================== ÉDITEUR GRAPHIQUE — RYTHME DE PUBLICATION ===================
function RhythmCollapsible({ id, title, hint, defaultOpen = false, badge, actions, children }) {
  const [open, setOpen] = useS(defaultOpen);

  return (
    <div className={`sg-rhythm-fold${open ? " is-open" : ""}`}>
      <div className="sg-rhythm-fold-head">
        <button
          type="button"
          className="sg-rhythm-fold-trigger"
          aria-expanded={open}
          aria-controls={`${id}-panel`}
          onClick={() => setOpen((v) => !v)}
        >
          <span className="sg-rhythm-chevron" aria-hidden="true" />
          <span className="sg-rhythm-fold-title">{title}</span>
          {badge != null && badge !== "" ? (
            <span className="sg-pill is-published sg-rhythm-fold-badge">{badge}</span>
          ) : null}
        </button>
        {actions ? (
          <div className="sg-rhythm-fold-actions" onClick={(e) => e.stopPropagation()}>
            {actions}
          </div>
        ) : null}
      </div>
      {!open && hint ? <p className="sg-field-hint sg-rhythm-fold-hint">{hint}</p> : null}
      {open ? (
        <div id={`${id}-panel`} className="sg-rhythm-fold-panel">
          {children}
        </div>
      ) : null}
    </div>
  );
}

function PublicationRhythmEditor({
  slots = [],
  maxSlots = 14,
  maxPerWeek = 4,
  rhythmOptions = [],
  rhythmValue,
  personaOptions = [],
  interviewsAllowed = false,
  disabled = false,
  busy = false,
  onChange,
  onApplyPreset,
}) {
  const [localSlots, setLocalSlots] = useS(() => normalizePublicationSlots(slots));
  const [slotModal, setSlotModal] = useS(null);

  useE(() => {
    setLocalSlots(normalizePublicationSlots(slots));
  }, [slots]);

  async function commit(nextSlots) {
    const normalized = normalizePublicationSlots(nextSlots);
    setLocalSlots(normalized);
    if (onChange) await onChange(normalized);
  }

  function nextArticlePersonaIndex(currentSlots) {
    if (!personaOptions.length) return null;
    const articleCount = currentSlots.filter((s) => normalizeSlotKind(s.kind) === "article").length;
    return articleCount % personaOptions.length;
  }

  function openCreateModal(weekday, hour, kind = "article") {
    if (disabled || busy || localSlots.length >= maxSlots) return;
    if (kind === "interview" && !interviewsAllowed) return;
    setSlotModal({
      editIndex: null,
      initial: {
        weekday,
        hour,
        minute: 0,
        kind,
        personaIndex:
          kind === "interview" ? null : nextArticlePersonaIndex(localSlots),
      },
    });
  }

  function openEditModal(index) {
    const slot = localSlots[index];
    if (!slot) return;
    setSlotModal({
      editIndex: index,
      initial: { ...slot },
    });
  }

  function closeSlotModal() {
    setSlotModal(null);
  }

  async function saveSlotFromModal(payload, editIndex) {
    let next;
    if (editIndex != null) {
      next = localSlots.map((s, i) => (i === editIndex ? payload : s));
    } else {
      if (localSlots.length >= maxSlots) return;
      next = [...localSlots, payload];
    }
    await commit(next);
    closeSlotModal();
  }

  async function deleteSlotFromModal(editIndex) {
    await commit(localSlots.filter((_, i) => i !== editIndex));
    closeSlotModal();
  }

  function handleGridCellClick(weekday, hour) {
    if (disabled || busy) return;
    const index = localSlots.findIndex((s) => s.weekday === weekday && s.hour === hour);
    if (index >= 0) {
      openEditModal(index);
      return;
    }
    openCreateModal(weekday, hour);
  }

  const dayLabels = EDITORIAL_WEEKDAYS || [];

  function slotCellStyle(slot) {
    if (!slot) return undefined;
    const kind = normalizeSlotKind(slot.kind);
    if (kind === "interview") return undefined;
    if (slot.personaIndex == null) return undefined;
    const color = personaSlotColor(slot.personaIndex);
    return {
      borderColor: color,
      background: `${color}1f`,
    };
  }

  function slotCellTitle(slot, dayLabel, hour) {
    if (!slot) return "";
    const min = slot.minute ? `:${String(slot.minute).padStart(2, "0")}` : "";
    const kind = normalizeSlotKind(slot.kind);
    const kindLabel = kind === "interview" ? "Interview" : "Article";
    const persona = rhythmSlotPersonaName(slot, personaOptions);
    const personaPart = persona ? ` · ${persona}` : "";
    return `${dayLabel} ${String(hour).padStart(2, "0")}h${min} · ${kindLabel}${personaPart} — modifier`;
  }

  function formatSlotTime(slot) {
    const min = slot.minute ? `:${String(slot.minute).padStart(2, "0")}` : "";
    return `${String(slot.hour).padStart(2, "0")}h${min}`;
  }

  const articleCount = localSlots.filter((s) => normalizeSlotKind(s.kind) === "article").length;
  const interviewCount = localSlots.filter((s) => normalizeSlotKind(s.kind) === "interview").length;

  return (
    <div className="sg-rhythm-editor">
      <p className="sg-field-hint sg-rhythm-slot-explainer">
        Créneaux horaires vides : Guy y insère articles et interviews selon votre plan éditorial.
        Cliquez une case de la grille pour ouvrir la configuration (format, persona, heure précise).
      </p>

      <div className="sg-rhythm-legend" aria-hidden="true">
        <span className="sg-rhythm-legend-kind is-article">Article</span>
        <span className="sg-rhythm-legend-kind is-interview">Interview</span>
        {personaOptions.map((p) => (
          <span key={p.id} className="sg-rhythm-legend-persona">
            <span className="sg-rhythm-legend-dot" style={{ background: personaSlotColor(p.index) }} />
            {p.name}
          </span>
        ))}
        {!personaOptions.length ? (
          <span className="sg-rhythm-legend-note">Personas : complétez vos segments</span>
        ) : null}
      </div>

      <RhythmCollapsible
        id="rhythm-grid"
        title="Grille hebdomadaire"
        hint={
          localSlots.length
            ? `${articleCount} art. · ${interviewCount} ITW · cliquez pour ajuster`
            : "Cliquez une case pour ajouter un créneau"
        }
        defaultOpen
        badge={localSlots.length ? String(localSlots.length) : null}
      >
        <p className="sg-field-hint">
          Cliquez une case pour configurer un créneau · {localSlots.length}/{maxSlots} max
          {maxPerWeek ? ` · plan : ${maxPerWeek} art./sem.` : ""}
        </p>

        <div className="sg-rhythm-grid" role="grid" aria-label="Créneaux de publication">
          <div className="sg-rhythm-grid-corner" aria-hidden="true" />
          {dayLabels.map((d) => (
            <div key={d.v} className="sg-rhythm-grid-head">{d.label}</div>
          ))}
          {RHYTHM_GRID_HOURS.map((hour) => (
            <React.Fragment key={hour}>
              <div className="sg-rhythm-hour-label">{String(hour).padStart(2, "0")}h</div>
              {dayLabels.map((d) => {
                const on = !!slotAtDayHour(localSlots, d.v, hour);
                const slot = slotAtDayHour(localSlots, d.v, hour);
                const atMax = !on && localSlots.length >= maxSlots;
                const kind = normalizeSlotKind(slot?.kind);
                return (
                  <button
                    key={`${d.v}-${hour}`}
                    type="button"
                    className={`sg-rhythm-cell${on ? " is-on" : ""}${on && kind === "interview" ? " is-interview" : ""}${on && kind !== "interview" ? " is-article" : ""}${atMax ? " is-disabled" : ""}`}
                    style={on ? slotCellStyle(slot) : undefined}
                    disabled={disabled || busy || atMax}
                    title={
                      on
                        ? slotCellTitle(slot, d.label, hour)
                        : `${d.label} ${String(hour).padStart(2, "0")}h — configurer un créneau`
                    }
                    aria-pressed={on}
                    onClick={() => handleGridCellClick(d.v, hour)}
                  >
                    {on ? (
                      <>
                        <span className={`sg-rhythm-cell-kind${kind === "interview" ? " is-itw" : " is-art"}`}>
                          {kind === "interview" ? "ITW" : "Art"}
                        </span>
                        <span className="sg-rhythm-cell-time">{formatSlotTime(slot)}</span>
                        {slot?.personaIndex != null && rhythmSlotPersonaName(slot, personaOptions) ? (
                          <span
                            className="sg-rhythm-cell-persona"
                            style={{ color: personaSlotColor(slot.personaIndex) }}
                          >
                            {rhythmSlotPersonaName(slot, personaOptions).slice(0, 3)}
                          </span>
                        ) : null}
                      </>
                    ) : null}
                  </button>
                );
              })}
            </React.Fragment>
          ))}
        </div>
      </RhythmCollapsible>

      {rhythmOptions.length > 1 && (
        <RhythmCollapsible
          id="rhythm-presets"
          title="Modèles rapides (plan)"
          hint="Préremplir la grille selon votre abonnement"
        >
          <div className="sg-seg sg-seg-wrap">
            {rhythmOptions.map((opt) => (
              <button
                key={opt.value}
                type="button"
                className={rhythmValue === opt.value ? "is-active" : ""}
                disabled={disabled || busy}
                onClick={() => onApplyPreset?.(opt.value)}
              >
                {opt.label}
              </button>
            ))}
          </div>
        </RhythmCollapsible>
      )}

      <RhythmSlotModal
        open={!!slotModal}
        editIndex={slotModal?.editIndex ?? null}
        initial={slotModal?.initial}
        existingSlots={localSlots}
        personaOptions={personaOptions}
        interviewsAllowed={interviewsAllowed}
        disabled={disabled}
        busy={busy}
        onClose={closeSlotModal}
        onSave={(payload, editIndex) => void saveSlotFromModal(payload, editIndex)}
        onDelete={(editIndex) => void deleteSlotFromModal(editIndex)}
      />
    </div>
  );
}

const FORBIDDEN_TERMS_MAX_UI = 200;
const FORBIDDEN_TERM_MAX_UI = 180;

function parseForbiddenTermsUi(raw) {
  if (!raw || !String(raw).trim()) return [];
  const text = String(raw).replace(/\r\n/g, "\n").trim();
  const chunks = text.includes("\n") ? text.split(/\n+/) : text.split(/[;\n]+/);
  const out = [];
  const seen = new Set();
  for (const chunk of chunks) {
    const trimmed = chunk.trim().replace(/^[-•*]\s*/, "");
    if (!trimmed) continue;
    const pieces = !text.includes("\n") && trimmed.includes(",") ? trimmed.split(",") : [trimmed];
    for (const piece of pieces) {
      const term = piece.trim().replace(/^[-•*]\s*/, "").replace(/\s+/g, " ");
      if (!term) continue;
      const clipped = term.slice(0, FORBIDDEN_TERM_MAX_UI);
      const key = clipped.toLowerCase();
      if (seen.has(key)) continue;
      seen.add(key);
      out.push(clipped);
      if (out.length >= FORBIDDEN_TERMS_MAX_UI) return out;
    }
  }
  return out;
}

function hydrateForbiddenEntriesUi(cfg) {
  const raw = Array.isArray(cfg?.forbiddenEntries) ? cfg.forbiddenEntries : [];
  if (raw.length) {
    const seen = new Set();
    const out = [];
    for (const entry of raw) {
      const term = String(entry?.term || "").trim();
      if (!term) continue;
      const key = term.toLowerCase();
      if (seen.has(key)) continue;
      seen.add(key);
      const origin =
        entry.origin === "pack" || entry.origin === "document" ? entry.origin : "manual";
      out.push({
        term,
        origin,
        sourceId: entry.sourceId || "",
        sourceLabel: entry.sourceLabel || "",
      });
    }
    return out;
  }
  return parseForbiddenTermsUi(cfg?.forbiddenKeywords).map((term) => ({
    term,
    origin: "manual",
    sourceId: "",
    sourceLabel: "",
  }));
}

function forbiddenChipTitle(entry) {
  if (!entry) return "";
  if (entry.origin === "pack" && entry.sourceLabel) {
    return `${entry.term} — ${entry.sourceLabel}`;
  }
  if (entry.origin === "document") {
    return `${entry.term} — extrait d’un référentiel`;
  }
  return entry.term;
}

// =================== RÉGLAGES ===================
function SettingsScreen({
  publicationSchedule,
  editorialQuota,
  planCode = "",
  planLabel = "",
  personas = [],
  targetAudiences = [],
  configuration,
  publicationScheduleAllowed = false,
  live,
  shopify,
  onSave,
  onSaveSchedule,
  onSaveConfig,
  onResyncShopify,
  onSaveShopifyConnection,
  onTestShopifyConnection,
  onDisconnectShopify,
  wordpress,
  onSaveWordpressConnection,
  onTestWordpressConnection,
  onDisconnectWordpress,
  busy,
}) {
  const [resyncMsg, setResyncMsg] = useS("");
  const [resyncErr, setResyncErr] = useS("");
  const [resyncBusy, setResyncBusy] = useS(false);
  const [saveMsg, setSaveMsg] = useS("");
  const [saveErr, setSaveErr] = useS("");
  const [saving, setSaving] = useS(false);
  const [liabilityOpen, setLiabilityOpen] = useS(false);
  const [rhythmOpen, setRhythmOpen] = useS(false);

  const maxPerWeek = editorialQuota?.articlesPerWeek ?? 1;
  const interviewsAllowed = (editorialQuota?.interviewsPerPeriod ?? 0) > 0;
  const rhythmOptions = buildRhythmOptions(maxPerWeek);
  const personaOptions = buildPersonaOptionsFromTargets(targetAudiences, personas);
  const sched = publicationSchedule || {};
  const fullAuto = !!sched.enabled && !!sched.publishWithoutReview;

  const [pilotMode, setPilotMode] = useS(fullAuto ? "auto" : "semi");
  const [rhythmValue, setRhythmValue] = useS(() =>
    inferRhythmFromSchedule(sched, maxPerWeek));
  const [toneLabel, setToneLabel] = useS(() =>
    inferDefaultEditorialTone(personas, targetAudiences));
  const [draftSlots, setDraftSlots] = useS(() =>
    normalizePublicationSlots(
      sched.slots?.length
        ? sched.slots
        : suggestSlotsForRhythm(inferRhythmFromSchedule(sched, maxPerWeek), planCode, maxPerWeek),
    ));

  useE(() => {
    setPilotMode(sched.enabled && sched.publishWithoutReview ? "auto" : "semi");
    const inferred = inferRhythmFromSchedule(sched, maxPerWeek);
    setRhythmValue(inferred);
    setToneLabel(inferDefaultEditorialTone(personas, targetAudiences));
    setDraftSlots(
      normalizePublicationSlots(
        sched.slots?.length
          ? sched.slots
          : suggestSlotsForRhythm(inferred, planCode, maxPerWeek),
      ),
    );
  }, [publicationSchedule, maxPerWeek, personas, targetAudiences, planCode]);

  const shopifyConnected =
    shopify?.publishingMode === "shopify" ||
    !!shopify?.canPublishOnShopify ||
    !!shopify?.connected ||
    !!live?.session?.shopifyConnection?.connected;
  const hasAccessToken =
    !!shopify?.hasAccessToken || !!live?.session?.shopifyConnection?.hasAccessToken;
  const pendingSync = shopify?.pendingSyncCount ?? 0;
  const withContent = shopify?.withContentCount ?? 0;
  const shopLabel = shopify?.shop || live?.site?.shop || "Boutique Shopify";
  const siteSettings = live?.settings || {};
  const isWebsiteSite =
    live?.site?.platform === "WEBSITE" ||
    String(shopify?.shop || live?.site?.shop || "").startsWith("website:");
  const wordpressConnected = !!wordpress?.connected || !!live?.wordpressConnection?.connected;
  const cmsConnected = wordpressConnected || shopifyConnected;
  const wpSuggestedUrl = (() => {
    const existing = wordpress?.baseUrl || live?.wordpressConnection?.baseUrl;
    if (existing) return existing;
    const shop = shopify?.shop || live?.site?.shop || "";
    const host = String(shop).replace(/^website:/i, "").replace(/^https?:\/\//i, "").split("/")[0];
    return host ? `https://${host}` : "";
  })();
  const canEditSchedule = publicationScheduleAllowed;

  const [defaultBlogGid, setDefaultBlogGid] = useS(() => siteSettings.defaultBlogGid || "");
  const [defaultAuthor, setDefaultAuthor] = useS(() => siteSettings.defaultAuthor || "Guy SEO");
  const [shopifyBlogs, setShopifyBlogs] = useS([]);
  const [blogsLoading, setBlogsLoading] = useS(false);
  const [cmsOpen, setCmsOpen] = useS(false);
  const [localeOpen, setLocaleOpen] = useS(false);
  const [forbidOpen, setForbidOpen] = useS(() =>
    parseForbiddenTermsUi(configuration?.forbiddenKeywords).length > 0 ||
    !!configuration?.compliancePackId,
  );
  const [connOpen, setConnOpen] = useS(false);
  const [forbiddenTerms, setForbiddenTerms] = useS(() =>
    parseForbiddenTermsUi(configuration?.forbiddenKeywords),
  );
  const [forbiddenEntries, setForbiddenEntries] = useS(() =>
    hydrateForbiddenEntriesUi(configuration),
  );
  const [compliancePacks, setCompliancePacks] = useS([]);
  const [packId, setPackId] = useS(() => configuration?.compliancePackId || "");
  const [regulationIds, setRegulationIds] = useS(() =>
    Array.isArray(configuration?.complianceRegulations)
      ? [...configuration.complianceRegulations]
      : [],
  );
  const [certificationIds, setCertificationIds] = useS(() =>
    Array.isArray(configuration?.complianceCertifications)
      ? [...configuration.complianceCertifications]
      : [],
  );
  const [forbidDraft, setForbidDraft] = useS("");
  const [forbidPaste, setForbidPaste] = useS("");
  const [regulatoryDraft, setRegulatoryDraft] = useS(() => configuration?.regulatoryReferences || "");
  const [forbidMsg, setForbidMsg] = useS("");
  const [forbidErr, setForbidErr] = useS("");
  const [forbidBusy, setForbidBusy] = useS(false);
  const [forbidSaving, setForbidSaving] = useS(false);
  const [tokenShop, setTokenShop] = useS(() => shopify?.shop || live?.session?.site?.shop || "");
  const [tokenValue, setTokenValue] = useS("");
  const [tokenBusy, setTokenBusy] = useS(false);
  const [tokenMsg, setTokenMsg] = useS("");
  const [tokenErr, setTokenErr] = useS("");
  const [wpOpen, setWpOpen] = useS(isWebsiteSite);
  const [wpBaseUrl, setWpBaseUrl] = useS(wpSuggestedUrl);
  const [wpUsername, setWpUsername] = useS(wordpress?.username || live?.wordpressConnection?.username || "");
  const [wpPassword, setWpPassword] = useS("");
  const [wpCategoryId, setWpCategoryId] = useS(wordpress?.categoryId || live?.wordpressConnection?.categoryId || "");
  const [wpCategories, setWpCategories] = useS([]);
  const [wpBusy, setWpBusy] = useS(false);
  const [wpMsg, setWpMsg] = useS("");
  const [wpErr, setWpErr] = useS("");
  const [preferredLanguage, setPreferredLanguage] = useS(() => siteSettings.preferredLanguage || "fr");
  const [targetCountries, setTargetCountries] = useS(() =>
    Array.isArray(siteSettings.targetCountries) ? [...siteSettings.targetCountries] : [],
  );

  useE(() => {
    setDefaultBlogGid(siteSettings.defaultBlogGid || "");
    setDefaultAuthor(siteSettings.defaultAuthor || "Guy SEO");
    setPreferredLanguage(siteSettings.preferredLanguage || "fr");
    setTargetCountries(Array.isArray(siteSettings.targetCountries) ? [...siteSettings.targetCountries] : []);
  }, [
    siteSettings.defaultBlogGid,
    siteSettings.defaultAuthor,
    siteSettings.preferredLanguage,
    siteSettings.targetCountries,
  ]);

  useE(() => {
    const entries = hydrateForbiddenEntriesUi(configuration);
    setForbiddenEntries(entries);
    setForbiddenTerms(entries.map((e) => e.term));
  }, [configuration?.forbiddenKeywords, configuration?.forbiddenEntries]);

  useE(() => {
    setPackId(configuration?.compliancePackId || "");
    setRegulationIds(
      Array.isArray(configuration?.complianceRegulations)
        ? [...configuration.complianceRegulations]
        : [],
    );
    setCertificationIds(
      Array.isArray(configuration?.complianceCertifications)
        ? [...configuration.complianceCertifications]
        : [],
    );
  }, [
    configuration?.compliancePackId,
    configuration?.complianceRegulations,
    configuration?.complianceCertifications,
  ]);

  useE(() => {
    if (typeof GuyAPI?.listCompliancePacks !== "function") return;
    let cancelled = false;
    GuyAPI.listCompliancePacks()
      .then((res) => {
        if (!cancelled) setCompliancePacks(Array.isArray(res?.packs) ? res.packs : []);
      })
      .catch(() => {
        if (!cancelled) setCompliancePacks([]);
      });
    return () => {
      cancelled = true;
    };
  }, []);

  useE(() => {
    setRegulatoryDraft(configuration?.regulatoryReferences || "");
  }, [configuration?.regulatoryReferences]);

  useE(() => {
    if (!shopifyConnected || typeof GuyAPI?.shopifyBlogs !== "function") return;
    let cancelled = false;
    setBlogsLoading(true);
    GuyAPI.shopifyBlogs()
      .then((res) => {
        if (!cancelled) setShopifyBlogs(res?.blogs || []);
      })
      .catch(() => {
        if (!cancelled) setShopifyBlogs([]);
      })
      .finally(() => {
        if (!cancelled) setBlogsLoading(false);
      });
    return () => {
      cancelled = true;
    };
  }, [shopifyConnected]);

  function selectedBlogLabel() {
    if (!defaultBlogGid) return "Brouillon local";
    const blog = shopifyBlogs.find((b) => b.id === defaultBlogGid);
    return blog ? blog.title : "Blog configuré";
  }

  async function persistSiteSettings(patch = {}, successMsg = "Réglages enregistrés.") {
    if (!onSave) return;
    if (patch.defaultBlogGid !== undefined) setDefaultBlogGid(patch.defaultBlogGid || "");
    if (patch.defaultAuthor !== undefined) setDefaultAuthor(patch.defaultAuthor || "Guy SEO");
    if (patch.preferredLanguage !== undefined) setPreferredLanguage(patch.preferredLanguage || "fr");
    if (patch.targetCountries !== undefined) {
      setTargetCountries(Array.isArray(patch.targetCountries) ? [...patch.targetCountries] : []);
    }
    setSaving(true);
    setSaveErr("");
    setSaveMsg("");
    try {
      const payload = {};
      if (patch.defaultBlogGid !== undefined) payload.defaultBlogGid = patch.defaultBlogGid || null;
      if (patch.defaultAuthor !== undefined) {
        payload.defaultAuthor = (patch.defaultAuthor || "Guy SEO").trim() || "Guy SEO";
      }
      if (patch.preferredLanguage !== undefined) payload.preferredLanguage = patch.preferredLanguage || "fr";
      if (patch.targetCountries !== undefined) payload.targetCountries = patch.targetCountries;
      await onSave(payload);
      setSaveMsg(successMsg);
    } catch (e) {
      setSaveErr(e.message || "Enregistrement impossible.");
    } finally {
      setSaving(false);
    }
  }

  async function persistCmsSettings(patch = {}) {
    await persistSiteSettings(patch, "Publication CMS enregistrée.");
  }

  function toggleTargetCountry(code) {
    const set = new Set(targetCountries);
    if (set.has(code)) set.delete(code);
    else set.add(code);
    void persistSiteSettings({ targetCountries: [...set] }, "Ciblage enregistré.");
  }

  async function persistForbiddenTerms(nextTerms, successMsg) {
    if (!onSaveConfig) return;
    const cleaned = parseForbiddenTermsUi((nextTerms || []).join("\n"));
    const byKey = new Map(forbiddenEntries.map((e) => [e.term.toLowerCase(), e]));
    const nextEntries = cleaned.map((term) => byKey.get(term.toLowerCase()) || {
      term,
      origin: "manual",
      sourceId: "",
      sourceLabel: "",
    });
    setForbiddenEntries(nextEntries);
    setForbiddenTerms(cleaned);
    setForbidSaving(true);
    setForbidErr("");
    setForbidMsg("");
    try {
      await onSaveConfig({ forbiddenKeywords: cleaned.join("\n") });
      setForbidMsg(successMsg || "Interdits enregistrés.");
    } catch (e) {
      setForbidErr(e.message || "Enregistrement impossible.");
    } finally {
      setForbidSaving(false);
    }
  }

  function addForbiddenDraft() {
    const extra = parseForbiddenTermsUi(forbidDraft);
    if (!extra.length) return;
    setForbidDraft("");
    void persistForbiddenTerms(
      [...forbiddenTerms, ...extra],
      extra.length > 1 ? `${extra.length} expressions ajoutées.` : "Expression ajoutée.",
    );
  }

  function addForbiddenPaste() {
    const extra = parseForbiddenTermsUi(forbidPaste);
    if (!extra.length) return;
    const next = parseForbiddenTermsUi([...forbiddenTerms, ...extra].join("\n"));
    const added = next.length - forbiddenTerms.length;
    setForbidPaste("");
    void persistForbiddenTerms(
      next,
      added > 0 ? `${added} expression(s) ajoutée(s).` : "Aucun nouveau terme (déjà présents).",
    );
  }

  function removeForbiddenTerm(term) {
    void persistForbiddenTerms(
      forbiddenTerms.filter((t) => t.toLowerCase() !== term.toLowerCase()),
      "Expression retirée.",
    );
  }

  function clearAllForbiddenTerms() {
    if (!forbiddenTerms.length) return;
    if (!window.confirm("Effacer tous les mots-clés et expressions interdits ?")) return;
    void persistForbiddenTerms([], "Tous les interdits ont été effacés.");
  }

  async function persistRegulatoryOnly() {
    if (!onSaveConfig) return;
    setForbidSaving(true);
    setForbidErr("");
    setForbidMsg("");
    try {
      await onSaveConfig({ regulatoryReferences: regulatoryDraft });
      setForbidMsg("Référentiel enregistré.");
    } catch (e) {
      setForbidErr(e.message || "Enregistrement impossible.");
      throw e;
    } finally {
      setForbidSaving(false);
    }
  }

  const isRegulatoryDirty =
    (regulatoryDraft || "").trim() !== (configuration?.regulatoryReferences || "").trim();
  useUnsavedChangesGuard({
    isDirty: isRegulatoryDirty,
    onSave: persistRegulatoryOnly,
    onDiscard: () => setRegulatoryDraft(configuration?.regulatoryReferences || ""),
  });

  async function analyzeRegulatory() {
    if (typeof GuyAPI?.analyzeForbiddenFromRegulatory !== "function") return;
    setForbidBusy(true);
    setForbidErr("");
    setForbidMsg("");
    try {
      const res = await GuyAPI.analyzeForbiddenFromRegulatory({
        regulatoryText: regulatoryDraft,
      });
      const terms = Array.isArray(res.terms)
        ? res.terms
        : parseForbiddenTermsUi(res.configuration?.forbiddenKeywords);
      const entries = hydrateForbiddenEntriesUi(res.configuration);
      setForbiddenEntries(entries.length ? entries : terms.map((term) => ({
        term,
        origin: "document",
        sourceId: "",
        sourceLabel: "Référentiel collé",
      })));
      setForbiddenTerms(terms);
      if (res.configuration?.regulatoryReferences != null) {
        setRegulatoryDraft(res.configuration.regulatoryReferences);
      }
      const added = Array.isArray(res.added) ? res.added.length : 0;
      setForbidMsg(
        added
          ? `${added} expression(s) extraite(s) et ajoutée(s).`
          : "Analyse terminée : aucun nouveau terme.",
      );
    } catch (e) {
      setForbidErr(e.message || "Analyse impossible.");
    } finally {
      setForbidBusy(false);
    }
  }

  const selectedPack = compliancePacks.find((p) => p.id === packId) || null;

  async function persistPackProfile(nextPackId, nextRegs, nextCerts) {
    if (!onSaveConfig) return;
    setForbidSaving(true);
    setForbidErr("");
    try {
      await onSaveConfig({
        compliancePackId: nextPackId || null,
        complianceRegulations: nextRegs,
        complianceCertifications: nextCerts,
      });
    } catch (e) {
      setForbidErr(e.message || "Enregistrement du pack impossible.");
    } finally {
      setForbidSaving(false);
    }
  }

  function onPackSelect(nextId) {
    const nextPack = compliancePacks.find((p) => p.id === nextId);
    const nextRegs = nextPack
      ? (nextPack.regulations || []).filter((s) => s.defaultOn).map((s) => s.id)
      : [];
    const nextCerts = [];
    setPackId(nextId);
    setRegulationIds(nextRegs);
    setCertificationIds(nextCerts);
    void persistPackProfile(nextId, nextRegs, nextCerts);
  }

  function toggleRegulation(id) {
    const set = new Set(regulationIds);
    if (set.has(id)) set.delete(id);
    else set.add(id);
    const next = [...set];
    setRegulationIds(next);
    void persistPackProfile(packId, next, certificationIds);
  }

  function toggleCertification(id) {
    const set = new Set(certificationIds);
    if (set.has(id)) set.delete(id);
    else set.add(id);
    const next = [...set];
    setCertificationIds(next);
    void persistPackProfile(packId, regulationIds, next);
  }

  async function generateFromPack() {
    if (typeof GuyAPI?.generateForbiddenFromPack !== "function" || !packId) return;
    setForbidBusy(true);
    setForbidErr("");
    setForbidMsg("");
    try {
      const res = await GuyAPI.generateForbiddenFromPack({
        packId,
        regulations: regulationIds,
        certifications: certificationIds,
      });
      const entries = hydrateForbiddenEntriesUi(res.configuration);
      const terms = entries.length
        ? entries.map((e) => e.term)
        : Array.isArray(res.terms)
          ? res.terms
          : parseForbiddenTermsUi(res.configuration?.forbiddenKeywords);
      setForbiddenEntries(entries.length ? entries : terms.map((term) => ({
        term,
        origin: "pack",
        sourceId: "",
        sourceLabel: "",
      })));
      setForbiddenTerms(terms);
      const added = Array.isArray(res.added) ? res.added.length : 0;
      const packCount = typeof res.packCount === "number" ? res.packCount : added;
      setForbidMsg(
        `${packCount} interdit${packCount > 1 ? "s" : ""} du pack. Les termes saisis à la main sont conservés${added ? ` · ${added} nouveau(x)` : ""}.`,
      );
    } catch (e) {
      setForbidErr(e.message || "Génération du pack impossible.");
    } finally {
      setForbidBusy(false);
    }
  }

  function buildSchedulePayload(overrides) {
    const slots = overrides.slots ?? draftSlots;
    return {
      enabled: sched.enabled ?? false,
      timezone: sched.timezone || "Europe/Paris",
      cadence: sched.cadence || "weekly",
      biweeklyOnEvenIsoWeeks: sched.biweeklyOnEvenIsoWeeks !== false,
      slots: slots.length ? slots : suggestSlotsForRhythm(rhythmValue, planCode, maxPerWeek),
      requireReviewBeforePublish: true,
      publishWithoutReview: false,
      liabilityAcceptedAt: null,
      lastProcessedSlotKey: sched.lastProcessedSlotKey ?? null,
      ...overrides,
    };
  }

  async function persistWithSlots(slots, extraOverrides = {}) {
    const normalized = normalizePublicationSlots(slots);
    setDraftSlots(normalized);
    setRhythmValue(inferRhythmFromSchedule({ slots: normalized }, maxPerWeek));
    await persistSchedule(buildSchedulePayload({ slots: normalized, ...extraOverrides }));
  }

  async function persistSchedule(payload) {
    if (!onSaveSchedule) return;
    setSaving(true);
    setSaveErr("");
    setSaveMsg("");
    try {
      await onSaveSchedule(payload);
      setSaveMsg("Réglages enregistrés.");
    } catch (e) {
      setSaveErr(e.message || "Enregistrement impossible.");
    } finally {
      setSaving(false);
    }
  }

  async function persistTone(nextTone) {
    if (!onSaveConfig || !targetAudiences?.length) return;
    setSaving(true);
    setSaveErr("");
    try {
      await onSaveConfig({
        targetAudiences: targetAudiences.map((t) => ({ ...t, tone: nextTone })),
      });
      setSaveMsg("Ton éditorial enregistré.");
    } catch (e) {
      setSaveErr(e.message || "Enregistrement du ton impossible.");
    } finally {
      setSaving(false);
    }
  }

  async function applySemiAuto() {
    setPilotMode("semi");
    await persistWithSlots(draftSlots, {
      enabled: true,
      publishWithoutReview: false,
      requireReviewBeforePublish: true,
      liabilityAcceptedAt: null,
    });
  }

  async function applyFullAuto() {
    setLiabilityOpen(false);
    setPilotMode("auto");
    await persistWithSlots(draftSlots, {
      enabled: true,
      publishWithoutReview: true,
      requireReviewBeforePublish: false,
      liabilityAcceptedAt: new Date().toISOString(),
    });
  }

  async function applyRhythmPreset(value) {
    setRhythmValue(value);
    const slots = enrichPublicationSlots(
      suggestSlotsForRhythm(value, planCode, maxPerWeek),
      personaOptions.length,
    );
    if (!canEditSchedule) return;
    await persistWithSlots(slots, {
      enabled: sched.enabled || pilotMode === "semi" || pilotMode === "auto",
      publishWithoutReview: pilotMode === "auto",
      requireReviewBeforePublish: pilotMode !== "auto",
      liabilityAcceptedAt: pilotMode === "auto" ? sched.liabilityAcceptedAt : null,
    });
  }

  async function applySlotsFromEditor(slots) {
    if (!canEditSchedule) return;
    await persistWithSlots(slots, {
      enabled: sched.enabled || pilotMode === "semi" || pilotMode === "auto",
      publishWithoutReview: pilotMode === "auto",
      requireReviewBeforePublish: pilotMode !== "auto",
      liabilityAcceptedAt: pilotMode === "auto" ? sched.liabilityAcceptedAt : null,
    });
  }

  async function applyTone(label) {
    setToneLabel(label);
    await persistTone(label);
  }

  async function runShopifyResync() {
    if (!onResyncShopify || resyncBusy) return;
    setResyncMsg("");
    setResyncErr("");
    setResyncBusy(true);
    try {
      const res = await onResyncShopify();
      setResyncMsg(res?.message || "Synchronisation terminée.");
    } catch (e) {
      setResyncErr(e.message || "Synchronisation impossible.");
    } finally {
      setResyncBusy(false);
    }
  }

  return (
    <div className="sg-screen sg-screen-assistants">
      <PageHeader
        title={<>Réglages <span>éditoriaux</span></>}
        sub="Pilotage, rythme et ton — alignés sur votre plan et votre calendrier éditorial."
      />

      <div className="sg-assistant-grid is-1" style={{ marginBottom: "var(--gap)" }}>
        <article className="sg-assistant-card is-rich sg-settings-card">
          <div className="sg-assistant-head">
            <span className="sg-assistant-ic"><Icon name="gear" size={22} /></span>
            {planLabel ? <span className="sg-pill is-published">{planLabel}</span> : null}
          </div>
          <h3>Pilotage</h3>
          <p className="sg-assistant-desc">
            Choisissez comment SEO Guy gère la rédaction et la publication de vos contenus.
          </p>

          <div className="sg-settings-modes">
            <button
              type="button"
              className={`sg-settings-mode${pilotMode === "semi" ? " is-active" : ""}`}
              disabled={!canEditSchedule || saving || busy}
              onClick={() => void applySemiAuto()}
            >
              <span className="sg-settings-mode-title">Semi-automatique</span>
              <span className="sg-settings-mode-desc">
                Guy suggère et rédige des contenus en brouillon à relire, puis à publier ou programmer facilement.
              </span>
            </button>
            <button
              type="button"
              className={`sg-settings-mode${pilotMode === "auto" ? " is-active" : ""}`}
              disabled={!canEditSchedule || saving || busy}
              onClick={() => setLiabilityOpen(true)}
            >
              <span className="sg-settings-mode-title">Pilote automatique</span>
              <span className="sg-settings-mode-desc">
                Guy rédige, publie et suit sans votre vérification préalable.
              </span>
            </button>
          </div>

          {pilotMode === "auto" && (
            <div className="sg-settings-warn" role="alert">
              <Icon name="spark" size={16} />
              <p>
                Assistant Guy rédige, publie et suit sans votre vérification. Vous restez néanmoins
                responsable de la publication au sens légal. Nous vous invitons à contrôler
                régulièrement les contenus créés.
              </p>
            </div>
          )}

          {!canEditSchedule && (
            <div className="sg-cfg-note">
              <Icon name="spark" size={16} />
              <span>Le pilotage automatique nécessite un plan Starter, Pro ou Business.</span>
            </div>
          )}

          <div className="sg-set-sep" />

          <section className={`sg-rhythm-section${rhythmOpen ? " is-open" : ""}`}>
            <div className="sg-rhythm-section-header">
              <button
                type="button"
                className="sg-rhythm-section-trigger"
                aria-expanded={rhythmOpen}
                onClick={() => setRhythmOpen((v) => !v)}
              >
                <span className="sg-rhythm-chevron" aria-hidden="true" />
                <div className="sg-set-txt sg-rhythm-section-titles">
                  <p className="sg-set-title">Rythme de publication</p>
                  <p className="sg-set-sub">
                    Créneaux horaires pour articles et interviews — Guy y pousse les contenus du plan
                    (personas, formats, intentions).
                    {maxPerWeek > 1
                      ? ` Jusqu'à ${maxPerWeek} article(s)/sem.`
                      : " 1 article/sem."}
                    {draftSlots.length && !rhythmOpen
                      ? ` · ${draftSlots.length} créneau(x).`
                      : ""}
                  </p>
                </div>
                <span className="sg-rhythm-section-toggle">
                  {rhythmOpen ? "Replier" : "Configurer"}
                </span>
              </button>

              {!rhythmOpen && draftSlots.length > 0 ? (
                <div className="sg-rhythm-summary">
                  {draftSlots.map((slot, index) => {
                    const kind = normalizeSlotKind(slot.kind);
                    const personaIdx = slot.personaIndex;
                    return (
                    <span
                      key={`${slot.weekday}-${slot.hour}-${slot.minute}-${index}`}
                      className={`sg-rhythm-chip${kind === "interview" ? " is-interview" : " is-article"}`}
                      style={
                        kind !== "interview" && personaIdx != null
                          ? {
                              borderColor: personaSlotColor(personaIdx),
                              color: personaSlotColor(personaIdx),
                              background: `${personaSlotColor(personaIdx)}14`,
                            }
                          : undefined
                      }
                    >
                      {kind === "interview" ? (
                        <span className="sg-rhythm-chip-kind is-itw">ITW</span>
                      ) : (
                        <span className="sg-rhythm-chip-kind is-art">Art</span>
                      )}
                      {formatRhythmSlotTime(slot)}
                      {rhythmSlotPersonaName(slot, personaOptions)
                        ? ` · ${rhythmSlotPersonaName(slot, personaOptions)}`
                        : ""}
                    </span>
                    );
                  })}
                </div>
              ) : null}
              {!rhythmOpen && !draftSlots.length ? (
                <p className="sg-field-hint sg-rhythm-summary-empty">
                  Aucun créneau — ouvrez la configuration pour définir votre rythme.
                </p>
              ) : null}
            </div>

            {rhythmOpen ? (
              <PublicationRhythmEditor
                slots={draftSlots}
                maxSlots={14}
                maxPerWeek={maxPerWeek}
                rhythmOptions={rhythmOptions}
                rhythmValue={rhythmValue}
                personaOptions={personaOptions}
                interviewsAllowed={interviewsAllowed}
                disabled={!canEditSchedule}
                busy={saving || busy}
                onChange={applySlotsFromEditor}
                onApplyPreset={applyRhythmPreset}
              />
            ) : null}
          </section>

          <div className="sg-set-sep" />

          <div className="sg-set-row sg-settings-row">
            <div className="sg-set-txt">
              <p className="sg-set-title">Ton éditorial</p>
              <p className="sg-set-sub">
                La voix de vos articles — alignée sur vos personas et le funnel segments.
                {!targetAudiences?.length ? " Complétez vos segments pour ancrer le ton en base." : ""}
              </p>
            </div>
            <div className="ob-tone-seg sg-cfg-tone-seg">
              {PERSONA_TONES.map((t) => (
                <button
                  key={t.id}
                  type="button"
                  className={`ob-tone-seg-btn${toneLabel === t.label ? " is-on" : ""}`}
                  disabled={saving || busy}
                  onClick={() => void applyTone(t.label)}
                >
                  {t.label}
                </button>
              ))}
            </div>
          </div>

          {saveMsg ? <p className="sg-field-hint sg-sync-ok">{saveMsg}</p> : null}
          {saveErr ? <p className="sg-field-error">{saveErr}</p> : null}
          {saving ? <p className="sg-field-hint">Enregistrement…</p> : null}
        </article>
      </div>

      <div className="sg-assistant-grid is-1" style={{ marginBottom: "var(--gap)" }}>
        <article className="sg-assistant-card is-rich sg-settings-card">
          <div className="sg-assistant-head">
            <span className="sg-assistant-ic"><Icon name="docs" size={22} /></span>
            <Pill tone={defaultBlogGid ? "published" : "draft"}>
              {defaultBlogGid ? "Shopify" : "Brouillon local"}
            </Pill>
          </div>
          <h3>Publication CMS</h3>
          <p className="sg-assistant-desc">
            Blog et auteur affichés sur vos articles publiés dans Shopify.
          </p>

          <section className={`sg-rhythm-section sg-cms-section${cmsOpen ? " is-open" : ""}`}>
            <div className="sg-rhythm-section-header">
              <button
                type="button"
                className="sg-rhythm-section-trigger"
                aria-expanded={cmsOpen}
                onClick={() => setCmsOpen((v) => !v)}
              >
                <span className="sg-rhythm-chevron" aria-hidden="true" />
                <div className="sg-set-txt sg-rhythm-section-titles">
                  <p className="sg-set-title">Destination Shopify</p>
                  <p className="sg-set-sub">
                    {!cmsOpen
                      ? `${selectedBlogLabel()} · ${(defaultAuthor || "Guy SEO").trim()}`
                      : "Choisissez le blog cible et le nom d'auteur par défaut."}
                  </p>
                </div>
                <span className="sg-rhythm-section-toggle">
                  {cmsOpen ? "Replier" : "Configurer"}
                </span>
              </button>
            </div>

            {cmsOpen ? (
              <div className="sg-cms-panel">
                <div className="sg-set-row sg-settings-row sg-cms-row">
                  <div className="sg-set-txt">
                    <p className="sg-set-title">Blog par défaut</p>
                    <p className="sg-set-sub">
                      Les nouveaux articles sont créés dans ce blog Shopify (statut brouillon).
                    </p>
                  </div>
                  <div className="sg-cms-field">
                    {!shopifyConnected ? (
                      <p className="sg-field-hint">
                        Boutique non connectée — publication en mode brouillon local.
                      </p>
                    ) : blogsLoading ? (
                      <p className="sg-field-hint">Chargement des blogs Shopify…</p>
                    ) : shopifyBlogs.length > 0 ? (
                      <select
                        className="sg-cfg-select"
                        value={defaultBlogGid}
                        disabled={saving || busy}
                        onChange={(e) => void persistCmsSettings({ defaultBlogGid: e.target.value })}
                      >
                        <option value="">Aucun (brouillon local)</option>
                        {shopifyBlogs.map((b) => (
                          <option key={b.id} value={b.id}>
                            {b.title} ({b.handle})
                          </option>
                        ))}
                      </select>
                    ) : (
                      <p className="sg-field-hint">Aucun blog détecté dans cette boutique.</p>
                    )}
                  </div>
                </div>

                <div className="sg-set-sep" />

                <div className="sg-set-row sg-settings-row sg-cms-row">
                  <div className="sg-set-txt">
                    <p className="sg-set-title">Auteur par défaut</p>
                    <p className="sg-set-sub">Nom affiché sur les articles publiés.</p>
                  </div>
                  <div className="sg-cms-field">
                    <input
                      type="text"
                      className="sg-cfg-input sg-cms-author-input"
                      value={defaultAuthor}
                      disabled={saving || busy}
                      placeholder="Guy SEO"
                      autoComplete="name"
                      onChange={(e) => setDefaultAuthor(e.target.value)}
                      onBlur={() => void persistCmsSettings({ defaultAuthor })}
                      onKeyDown={(e) => {
                        if (e.key === "Enter") {
                          e.preventDefault();
                          void persistCmsSettings({ defaultAuthor });
                        }
                      }}
                    />
                  </div>
                </div>

                {saveMsg ? <p className="sg-field-hint sg-sync-ok">{saveMsg}</p> : null}
                {saveErr ? <p className="sg-field-error">{saveErr}</p> : null}
                {saving ? <p className="sg-field-hint">Enregistrement…</p> : null}
              </div>
            ) : null}
          </section>
        </article>
      </div>

      <div className="sg-assistant-grid is-1" style={{ marginBottom: "var(--gap)" }}>
        <article className="sg-assistant-card is-rich sg-settings-card">
          <div className="sg-assistant-head">
            <span className="sg-assistant-ic"><Icon name="globe" size={22} /></span>
            <Pill tone="published">{labelForLanguage(preferredLanguage)}</Pill>
          </div>
          <h3>Langues &amp; ciblage</h3>
          <p className="sg-assistant-desc">
            Langue de rédaction des articles et marchés SEO visés par Guy.
          </p>

          <section className={`sg-rhythm-section sg-locale-section${localeOpen ? " is-open" : ""}`}>
            <div className="sg-rhythm-section-header">
              <button
                type="button"
                className="sg-rhythm-section-trigger"
                aria-expanded={localeOpen}
                onClick={() => setLocaleOpen((v) => !v)}
              >
                <span className="sg-rhythm-chevron" aria-hidden="true" />
                <div className="sg-set-txt sg-rhythm-section-titles">
                  <p className="sg-set-title">Préférences éditoriales</p>
                  <p className="sg-set-sub">
                    {!localeOpen
                      ? `${labelForLanguage(preferredLanguage)} · ${formatTargetCountriesSummary(targetCountries)}`
                      : "Langue principale et pays cibles pour orienter la rédaction SEO."}
                  </p>
                </div>
                <span className="sg-rhythm-section-toggle">
                  {localeOpen ? "Replier" : "Configurer"}
                </span>
              </button>
            </div>

            {localeOpen ? (
              <div className="sg-cms-panel">
                <div className="sg-set-row sg-settings-row sg-cms-row">
                  <div className="sg-set-txt">
                    <p className="sg-set-title">Langue principale</p>
                    <p className="sg-set-sub">Langue dans laquelle Guy rédige vos articles.</p>
                  </div>
                  <div className="sg-cms-field">
                    <select
                      className="sg-cfg-select"
                      value={preferredLanguage}
                      disabled={saving || busy}
                      onChange={(e) =>
                        void persistSiteSettings(
                          { preferredLanguage: e.target.value },
                          "Langue enregistrée.",
                        )
                      }
                    >
                      {(LANGUAGE_OPTIONS || []).map((l) => (
                        <option key={l.code} value={l.code}>
                          {l.label}
                        </option>
                      ))}
                    </select>
                  </div>
                </div>

                <div className="sg-set-sep" />

                <div className="sg-locale-countries-block">
                  <div className="sg-set-txt" style={{ marginBottom: 10 }}>
                    <p className="sg-set-title">Pays ciblés</p>
                    <p className="sg-set-sub">
                      Marchés prioritaires pour le SEO et les recommandations éditoriales.
                    </p>
                  </div>
                  <div className="sg-locale-countries">
                    {(COUNTRY_OPTIONS || []).map((c) => (
                      <button
                        key={c.code}
                        type="button"
                        className={`sg-seg-product-pick sg-locale-country-pick${targetCountries.includes(c.code) ? " is-on" : ""}`}
                        disabled={saving || busy}
                        onClick={() => toggleTargetCountry(c.code)}
                      >
                        {c.label}
                      </button>
                    ))}
                  </div>
                  <p className="sg-field-hint">
                    {targetCountries.length
                      ? `${targetCountries.length} pays sélectionné(s) · ${formatTargetCountriesSummary(targetCountries)}`
                      : "Sélectionnez au moins un pays pour ancrer le ciblage SEO."}
                  </p>
                </div>

                {saveMsg ? <p className="sg-field-hint sg-sync-ok">{saveMsg}</p> : null}
                {saveErr ? <p className="sg-field-error">{saveErr}</p> : null}
                {saving ? <p className="sg-field-hint">Enregistrement…</p> : null}
              </div>
            ) : null}
          </section>
        </article>
      </div>

      <div className="sg-assistant-grid is-1" style={{ marginBottom: "var(--gap)" }}>
        <article className="sg-assistant-card is-rich sg-settings-card">
          <div className="sg-assistant-head">
            <span className="sg-assistant-ic"><Icon name="docs" size={22} /></span>
            <Pill tone={forbiddenTerms.length ? "published" : "draft"}>
              {forbiddenTerms.length
                ? `${forbiddenTerms.length} interdit${forbiddenTerms.length > 1 ? "s" : ""}`
                : "Aucun"}
            </Pill>
          </div>
          <h3>Interdits rédactionnels</h3>
          <p className="sg-assistant-desc">
            Pack métier pour pré-remplir les interdits, ou saisie manuelle. Guy est un garde-fou éditorial, pas un juriste.
          </p>
          <button
            type="button"
            className="sg-btn-ghost sm sg-forbid-clear"
            disabled={forbidSaving || busy || !forbiddenTerms.length}
            onClick={clearAllForbiddenTerms}
          >
            Effacer tous les mots clés interdits
          </button>

          <section className={`sg-rhythm-section sg-forbid-section${forbidOpen ? " is-open" : ""}`}>
            <div className="sg-rhythm-section-header">
              <button
                type="button"
                className="sg-rhythm-section-trigger"
                aria-expanded={forbidOpen}
                onClick={() => setForbidOpen((v) => !v)}
              >
                <span className="sg-rhythm-chevron" aria-hidden="true" />
                <div className="sg-set-txt sg-rhythm-section-titles">
                  <p className="sg-set-title">Ce qu’on n’a pas le droit de dire</p>
                  <p className="sg-set-sub">
                    {!forbidOpen
                      ? (selectedPack
                        ? `${selectedPack.label}${forbiddenTerms.length ? ` · ${forbiddenTerms.length} interdit${forbiddenTerms.length > 1 ? "s" : ""}` : ""}`
                        : (forbiddenTerms.length
                          ? forbiddenTerms.slice(0, 3).join(" · ") + (forbiddenTerms.length > 3 ? ` · +${forbiddenTerms.length - 3}` : "")
                          : "Aucun pack — saisie manuelle."))
                      : "Pack métier, liste manuelle, ou extraction depuis un référentiel."}
                  </p>
                </div>
                <span className="sg-rhythm-section-toggle">
                  {forbidOpen ? "Replier" : "Configurer"}
                </span>
              </button>
            </div>

            {forbidOpen ? (
              <div className="sg-cms-panel">
                <div className="sg-forbid-pack">
                  <p className="sg-set-title">Pack métier</p>
                  <p className="sg-set-sub">
                    Sans pack, vous configurez les interdits à la main. Un pack génère une liste sourcée : vos termes saisis manuellement sont conservés.
                  </p>
                  <select
                    className="sg-cfg-select sg-forbid-pack-select"
                    value={packId}
                    disabled={forbidSaving || forbidBusy || busy}
                    onChange={(e) => onPackSelect(e.target.value)}
                  >
                    <option value="">Aucun — saisie manuelle</option>
                    {compliancePacks.map((pack) => (
                      <option key={pack.id} value={pack.id}>{pack.label}</option>
                    ))}
                  </select>
                  {selectedPack ? (
                    <>
                      <p className="sg-set-sub">{selectedPack.description}</p>
                      <p className="sg-set-title">Cadres réglementaires</p>
                      <div className="sg-forbid-checks">
                        {(selectedPack.regulations || []).map((item) => (
                          <label key={item.id} className="sg-forbid-check">
                            <input
                              type="checkbox"
                              checked={regulationIds.includes(item.id)}
                              disabled={forbidSaving || forbidBusy || busy}
                              onChange={() => toggleRegulation(item.id)}
                            />
                            <span>
                              {item.label}
                              {item.hint ? <small>{item.hint}</small> : null}
                            </span>
                          </label>
                        ))}
                      </div>
                      <p className="sg-set-title">Certifications (optionnel)</p>
                      <p className="sg-set-sub">Cochez seulement si la marque est réellement certifiée — Guy ajoute alors des interdits extra (surpromesses « 100 % naturel », etc.).</p>
                      <div className="sg-forbid-checks">
                        {(selectedPack.certifications || []).map((item) => (
                          <label key={item.id} className="sg-forbid-check">
                            <input
                              type="checkbox"
                              checked={certificationIds.includes(item.id)}
                              disabled={forbidSaving || forbidBusy || busy}
                              onChange={() => toggleCertification(item.id)}
                            />
                            <span>
                              {item.label}
                              {item.hint ? <small>{item.hint}</small> : null}
                            </span>
                          </label>
                        ))}
                      </div>
                      <button
                        type="button"
                        className="sg-btn-primary sm"
                        disabled={
                          forbidBusy ||
                          forbidSaving ||
                          busy ||
                          (!regulationIds.length && !certificationIds.length)
                        }
                        onClick={() => void generateFromPack()}
                      >
                        {forbidBusy ? "Génération…" : "Générer les interdits du pack"}
                      </button>
                      <p className="sg-forbid-legal">Garde-fou éditorial — ne remplace pas un conseil juridique.</p>
                    </>
                  ) : null}
                </div>

                {forbiddenTerms.length ? (
                  <div className="sg-forbid-chips-wrap">
                    <div className="sg-forbid-chips">
                    {(forbiddenEntries.length ? forbiddenEntries : forbiddenTerms.map((term) => ({ term, origin: "manual" }))).map((entry) => (
                      <span key={entry.term} className={`sg-forbid-chip is-${entry.origin || "manual"}`}>
                        <span title={forbiddenChipTitle(entry)}>{entry.term}</span>
                        <button
                          type="button"
                          className="sg-forbid-chip-x"
                          aria-label={`Retirer « ${entry.term} »`}
                          disabled={forbidSaving || busy}
                          onClick={() => removeForbiddenTerm(entry.term)}
                        >
                          <Icon name="close" size={12} />
                        </button>
                      </span>
                    ))}
                    </div>
                    <button
                      type="button"
                      className="sg-btn-ghost sm sg-forbid-clear"
                      disabled={forbidSaving || busy}
                      onClick={clearAllForbiddenTerms}
                    >
                      Effacer tous les mots clés interdits
                    </button>
                  </div>
                ) : (
                  <p className="sg-forbid-empty">Aucun mot-clé ni expression interdit pour le moment.</p>
                )}

                <div className="sg-forbid-add">
                  <input
                    className="sg-cfg-input"
                    placeholder="Ajouter un mot-clé ou une expression…"
                    value={forbidDraft}
                    disabled={forbidSaving || busy}
                    onChange={(e) => setForbidDraft(e.target.value)}
                    onKeyDown={(e) => {
                      if (e.key === "Enter") {
                        e.preventDefault();
                        addForbiddenDraft();
                      }
                    }}
                  />
                  <button
                    type="button"
                    className="sg-btn-primary sm"
                    disabled={forbidSaving || busy || !forbidDraft.trim()}
                    onClick={addForbiddenDraft}
                  >
                    Ajouter
                  </button>
                </div>

                <div className="sg-forbid-block">
                  <p className="sg-set-title">Coller une liste</p>
                  <p className="sg-set-sub">Un terme par ligne. Virgules acceptées si tout est sur une seule ligne.</p>
                  <textarea
                    className="sg-textarea"
                    rows={4}
                    placeholder={"guérit\nsoigne l’eczéma\nanti-cancer\ntraitement médical"}
                    value={forbidPaste}
                    disabled={forbidSaving || busy}
                    onChange={(e) => setForbidPaste(e.target.value)}
                  />
                  <button
                    type="button"
                    className="sg-btn-ghost sm"
                    disabled={forbidSaving || busy || !forbidPaste.trim()}
                    onClick={addForbiddenPaste}
                  >
                    Ajouter la liste
                  </button>
                </div>

                <div className="sg-set-sep" />

                <div className="sg-forbid-block">
                  <p className="sg-set-title">Référentiels normatifs</p>
                  <p className="sg-set-sub">
                    Collez un règlement, un code ou une consigne métier. Guy en extrait les mots et expressions interdits (ex. pas de vertus médicales sur un cosmétique ou une huile essentielle).
                  </p>
                  <textarea
                    className="sg-textarea"
                    rows={5}
                    placeholder="Ex. Règlement (CE) n° 1223/2009 : un produit cosmétique ne peut pas revendiquer de propriétés thérapeutiques. Interdiction des allégations du type « soigne », « guérit », « traitement médical »…"
                    value={regulatoryDraft}
                    disabled={forbidBusy || forbidSaving || busy}
                    onChange={(e) => setRegulatoryDraft(e.target.value)}
                  />
                  <div className="sg-forbid-actions">
                    <button
                      type="button"
                      className="sg-btn-ghost sm"
                      disabled={forbidBusy || forbidSaving || busy}
                      onClick={() => void persistRegulatoryOnly()}
                    >
                      Enregistrer le référentiel
                    </button>
                    <button
                      type="button"
                      className="sg-btn-primary sm"
                      disabled={forbidBusy || forbidSaving || busy || !regulatoryDraft.trim()}
                      onClick={() => void analyzeRegulatory()}
                    >
                      {forbidBusy ? "Analyse…" : "Analyser et extraire les interdits"}
                    </button>
                  </div>
                </div>

                {forbidMsg ? <p className="sg-field-hint sg-sync-ok">{forbidMsg}</p> : null}
                {forbidErr ? <p className="sg-field-error">{forbidErr}</p> : null}
                {forbidSaving && !forbidBusy ? <p className="sg-field-hint">Enregistrement…</p> : null}
              </div>
            ) : null}
          </section>
        </article>
      </div>

      <div className="sg-assistant-grid is-1" style={{ marginBottom: "var(--gap)" }}>
        <article className="sg-assistant-card is-rich sg-settings-card">
          <div className="sg-assistant-head">
            <span className="sg-assistant-ic"><Icon name="bolt" size={22} /></span>
            <Pill tone={cmsConnected ? "published" : "draft"}>
              {cmsConnected ? "Connectée" : "Mode autonome"}
            </Pill>
          </div>
          <h3>Connexions</h3>
          <p className="sg-assistant-desc">
            {isWebsiteSite
              ? "WordPress publie les articles Guy sur votre site. Shopify reste optionnel."
              : "Boutique Shopify et synchronisation des contenus vers votre blog."}
          </p>

          <section className={`sg-rhythm-section sg-conn-section${wpOpen ? " is-open" : ""}`}>
            <div className="sg-rhythm-section-header">
              <button
                type="button"
                className="sg-rhythm-section-trigger"
                aria-expanded={wpOpen}
                onClick={() => setWpOpen((v) => !v)}
              >
                <span className="sg-rhythm-chevron" aria-hidden="true" />
                <div className="sg-set-txt sg-rhythm-section-titles">
                  <p className="sg-set-title">Site WordPress</p>
                  <p className="sg-set-sub">
                    {!wpOpen
                      ? (wordpressConnected
                        ? `${wordpress?.baseUrl || wpBaseUrl || "Connecté"}`
                        : "URL, utilisateur Éditeur et mot de passe d’application")
                      : "Guy rédige, WordPress publie via l’API REST. Installez le plugin Assistant Guy pour générer le secret."}
                  </p>
                </div>
                <span className="sg-rhythm-section-toggle">
                  {wpOpen ? "Replier" : "Gérer"}
                </span>
              </button>
            </div>

            {wpOpen ? (
              <div className="sg-cms-panel">
                <div className="sg-conn-card">
                  <div className="sg-conn-card-row">
                    <span className="sg-conn-name">Statut</span>
                    <Pill tone={wordpressConnected ? "published" : "draft"}>
                      {wordpressConnected ? "Connecté" : "Non connecté"}
                    </Pill>
                  </div>
                  <p className="sg-set-sub" style={{ margin: "8px 0 12px" }}>
                    Dans WordPress : menu <strong>Assistant Guy</strong> (plugin) → générer le mot de passe d’application pour le compte Éditeur, puis le coller ici.
                    Il n’est jamais réaffiché. Pas le mot de passe de connexion WordPress.
                  </p>
                  <label className="sg-set-title" htmlFor="sg-wp-url">URL du site</label>
                  <input
                    id="sg-wp-url"
                    className="sg-cfg-select"
                    type="url"
                    autoComplete="off"
                    value={wpBaseUrl}
                    disabled={wpBusy || busy}
                    placeholder="https://votresite.com"
                    onChange={(e) => setWpBaseUrl(e.target.value)}
                    style={{ width: "100%", margin: "6px 0 12px" }}
                  />
                  <label className="sg-set-title" htmlFor="sg-wp-user">Identifiant WordPress</label>
                  <input
                    id="sg-wp-user"
                    className="sg-cfg-select"
                    type="text"
                    autoComplete="username"
                    value={wpUsername}
                    disabled={wpBusy || busy}
                    placeholder="éditeur (pas l’e-mail forcément)"
                    onChange={(e) => setWpUsername(e.target.value)}
                    style={{ width: "100%", margin: "6px 0 12px" }}
                  />
                  <label className="sg-set-title" htmlFor="sg-wp-pass">
                    Mot de passe d’application {wordpressConnected ? "(déjà enregistré)" : ""}
                  </label>
                  <input
                    id="sg-wp-pass"
                    className="sg-cfg-select"
                    type="password"
                    autoComplete="new-password"
                    value={wpPassword}
                    disabled={wpBusy || busy}
                    placeholder={wordpressConnected ? "Nouveau mot de passe pour remplacer" : "xxxx xxxx xxxx xxxx"}
                    onChange={(e) => setWpPassword(e.target.value)}
                    style={{ width: "100%", margin: "6px 0 12px" }}
                  />
                  {wpCategories.length > 0 ? (
                    <>
                      <label className="sg-set-title" htmlFor="sg-wp-cat">Catégorie par défaut</label>
                      <select
                        id="sg-wp-cat"
                        className="sg-cfg-select"
                        value={wpCategoryId || ""}
                        disabled={wpBusy || busy}
                        onChange={(e) => setWpCategoryId(e.target.value ? Number(e.target.value) : "")}
                        style={{ width: "100%", margin: "6px 0 12px" }}
                      >
                        <option value="">Aucune (Uncategorized)</option>
                        {wpCategories.map((c) => (
                          <option key={c.id} value={c.id}>{c.name}</option>
                        ))}
                      </select>
                    </>
                  ) : null}
                  <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
                    <button
                      type="button"
                      className="sg-btn-ghost sm"
                      disabled={wpBusy || busy}
                      onClick={async () => {
                        setWpErr("");
                        setWpMsg("");
                        setWpBusy(true);
                        try {
                          const payload = {
                            baseUrl: wpBaseUrl,
                            username: wpUsername,
                            appPassword: wpPassword,
                            categoryId: wpCategoryId || null,
                          };
                          const res = onTestWordpressConnection
                            ? await onTestWordpressConnection(payload)
                            : await GuyAPI.testWordpressConnection(payload);
                          setWpCategories(res?.categories || []);
                          const n = res?.categories?.length ?? 0;
                          setWpMsg(n ? `Connexion OK · ${n} catégorie(s)` : "Connexion OK");
                        } catch (e) {
                          setWpErr(e.message || String(e));
                        } finally {
                          setWpBusy(false);
                        }
                      }}
                    >
                      {wpBusy ? "Test…" : "Tester"}
                    </button>
                    <button
                      type="button"
                      className="sg-btn-primary sm"
                      disabled={wpBusy || busy || (!wpPassword.trim() && !wordpressConnected)}
                      onClick={async () => {
                        setWpErr("");
                        setWpMsg("");
                        setWpBusy(true);
                        try {
                          const payload = {
                            baseUrl: wpBaseUrl,
                            username: wpUsername,
                            appPassword: wpPassword,
                            categoryId: wpCategoryId || null,
                          };
                          if (onSaveWordpressConnection) {
                            await onSaveWordpressConnection(payload);
                          } else {
                            await GuyAPI.saveWordpressConnection(payload);
                          }
                          setWpPassword("");
                          setWpMsg("WordPress enregistré. Publier poussera les articles sur votre site.");
                        } catch (e) {
                          setWpErr(e.message || String(e));
                        } finally {
                          setWpBusy(false);
                        }
                      }}
                    >
                      Enregistrer
                    </button>
                    {wordpressConnected ? (
                      <button
                        type="button"
                        className="sg-btn-ghost sm"
                        disabled={wpBusy || busy}
                        onClick={async () => {
                          if (!window.confirm("Dissocier WordPress de ce compte Guy ?")) return;
                          setWpBusy(true);
                          setWpErr("");
                          try {
                            if (onDisconnectWordpress) await onDisconnectWordpress();
                            else await GuyAPI.disconnectWordpressConnection();
                            setWpPassword("");
                            setWpCategories([]);
                            setWpMsg("WordPress dissocié.");
                          } catch (e) {
                            setWpErr(e.message || String(e));
                          } finally {
                            setWpBusy(false);
                          }
                        }}
                      >
                        Dissocier
                      </button>
                    ) : null}
                  </div>
                  {wpMsg ? <p className="sg-field-hint sg-sync-ok">{wpMsg}</p> : null}
                  {wpErr ? <p className="sg-field-error">{wpErr}</p> : null}
                </div>
              </div>
            ) : null}
          </section>

          <section className={`sg-rhythm-section sg-conn-section${connOpen ? " is-open" : ""}`}>
            <div className="sg-rhythm-section-header">
              <button
                type="button"
                className="sg-rhythm-section-trigger"
                aria-expanded={connOpen}
                onClick={() => setConnOpen((v) => !v)}
              >
                <span className="sg-rhythm-chevron" aria-hidden="true" />
                <div className="sg-set-txt sg-rhythm-section-titles">
                  <p className="sg-set-title">Boutique Shopify</p>
                  <p className="sg-set-sub">
                    {!connOpen
                      ? `${shopLabel}${withContent > 0 ? ` · ${withContent} article(s)` : ""}${pendingSync > 0 ? ` · ${pendingSync} en attente` : ""}`
                      : "État de la connexion et resynchronisation manuelle des contenus."}
                  </p>
                </div>
                <span className="sg-rhythm-section-toggle">
                  {connOpen ? "Replier" : "Gérer"}
                </span>
              </button>
            </div>

            {connOpen ? (
              <div className="sg-cms-panel">
                <div className="sg-conn-card">
                  <div className="sg-conn-card-row">
                    <span className="sg-conn-name">Statut</span>
                    <Pill tone={shopifyConnected ? "published" : "draft"}>
                      {shopifyConnected ? "Connectée" : "Mode autonome"}
                    </Pill>
                  </div>
                  <div className="sg-conn-card-row">
                    <span className="sg-conn-name">Boutique</span>
                    <span className="sg-conn-value">{shopLabel}</span>
                  </div>
                  <div className="sg-set-sep" />
                  <p className="sg-set-title">Connexion technique (jeton Admin API)</p>
                  <p className="sg-set-sub" style={{ marginBottom: 12 }}>
                    Collez le jeton Admin API Shopify de cette boutique.
                    Il n’est jamais réaffiché après enregistrement.
                  </p>
                  <label className="sg-set-title" htmlFor="sg-shopify-shop">Boutique myshopify.com</label>
                  <input
                    id="sg-shopify-shop"
                    className="sg-cfg-select"
                    type="text"
                    autoComplete="off"
                    value={tokenShop}
                    disabled={tokenBusy || busy}
                    placeholder="boutique.myshopify.com"
                    onChange={(e) => setTokenShop(e.target.value)}
                    style={{ width: "100%", margin: "6px 0 12px" }}
                  />
                  <label className="sg-set-title" htmlFor="sg-shopify-token">
                    Jeton Admin API {hasAccessToken ? "(déjà enregistré)" : ""}
                  </label>
                  <input
                    id="sg-shopify-token"
                    className="sg-cfg-select"
                    type="password"
                    autoComplete="new-password"
                    value={tokenValue}
                    disabled={tokenBusy || busy}
                    placeholder={hasAccessToken ? "Nouveau jeton pour remplacer" : "shpat_…"}
                    onChange={(e) => setTokenValue(e.target.value)}
                    style={{ width: "100%", margin: "6px 0 12px" }}
                  />
                  <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
                    <button
                      type="button"
                      className="sg-btn-ghost sm"
                      disabled={tokenBusy || busy}
                      onClick={async () => {
                        setTokenErr("");
                        setTokenMsg("");
                        setTokenBusy(true);
                        try {
                          const res = onTestShopifyConnection
                            ? await onTestShopifyConnection({ shop: tokenShop, accessToken: tokenValue })
                            : await GuyAPI.testShopifyConnection({ shop: tokenShop, accessToken: tokenValue });
                          const n = res?.blogs?.length ?? 0;
                          setTokenMsg(n ? `Connexion OK · ${n} blog(s)` : "Connexion OK");
                        } catch (e) {
                          setTokenErr(e.message || String(e));
                        } finally {
                          setTokenBusy(false);
                        }
                      }}
                    >
                      {tokenBusy ? "Test…" : "Tester"}
                    </button>
                    <button
                      type="button"
                      className="sg-btn-primary sm"
                      disabled={tokenBusy || busy || !tokenValue.trim()}
                      onClick={async () => {
                        setTokenErr("");
                        setTokenMsg("");
                        setTokenBusy(true);
                        try {
                          if (onSaveShopifyConnection) {
                            await onSaveShopifyConnection({ shop: tokenShop, accessToken: tokenValue });
                          } else {
                            await GuyAPI.saveShopifyConnection({ shop: tokenShop, accessToken: tokenValue });
                          }
                          setTokenValue("");
                          setTokenMsg("Jeton enregistré. Boutique connectée.");
                        } catch (e) {
                          setTokenErr(e.message || String(e));
                        } finally {
                          setTokenBusy(false);
                        }
                      }}
                    >
                      Enregistrer
                    </button>
                    {hasAccessToken ? (
                      <button
                        type="button"
                        className="sg-btn-ghost sm"
                        disabled={tokenBusy || busy}
                        onClick={async () => {
                          if (!window.confirm("Dissocier le jeton Shopify de ce compte ?")) return;
                          setTokenBusy(true);
                          setTokenErr("");
                          try {
                            if (onDisconnectShopify) await onDisconnectShopify();
                            else await GuyAPI.disconnectShopifyConnection();
                            setTokenMsg("Jeton dissocié.");
                          } catch (e) {
                            setTokenErr(e.message || String(e));
                          } finally {
                            setTokenBusy(false);
                          }
                        }}
                      >
                        Dissocier
                      </button>
                    ) : null}
                  </div>
                  {tokenMsg ? <p className="sg-field-hint sg-sync-ok">{tokenMsg}</p> : null}
                  {tokenErr ? <p className="sg-field-error">{tokenErr}</p> : null}
                  <div className="sg-conn-card-row">
                    <span className="sg-conn-name">Contenus</span>
                    <span className="sg-field-hint">
                      {withContent > 0
                        ? `${withContent} article(s) avec contenu${pendingSync > 0 ? ` · ${pendingSync} non synchronisé(s)` : ""}`
                        : "Aucun contenu à pousser"}
                    </span>
                  </div>
                </div>

                <div className="sg-set-sep" />

                <div className="sg-set-row sg-settings-row sg-cms-row">
                  <div className="sg-set-txt">
                    <p className="sg-set-title">Synchronisation Shopify</p>
                    <p className="sg-set-sub">
                      Repousse titres, contenu et images vers le blog Shopify après une coupure
                      ou une génération en mode autonome.
                    </p>
                    {resyncMsg ? <p className="sg-field-hint sg-sync-ok">{resyncMsg}</p> : null}
                    {resyncErr ? <p className="sg-field-hint sg-sync-err">{resyncErr}</p> : null}
                  </div>
                  <button
                    type="button"
                    className="sg-btn-primary sm"
                    disabled={busy || resyncBusy || !onResyncShopify || !shopifyConnected}
                    onClick={runShopifyResync}
                  >
                    {resyncBusy ? "Synchronisation…" : "Resynchroniser Shopify"}
                  </button>
                </div>
                {!shopifyConnected ? (
                  <p className="sg-field-hint">
                    Connectez votre boutique Shopify pour activer la synchronisation automatique.
                  </p>
                ) : null}
              </div>
            ) : null}
          </section>
        </article>
      </div>

      {liabilityOpen ? (
        <div className="sg-overlay" onClick={() => setLiabilityOpen(false)}>
          <div
            className="sg-panel sg-liability-modal"
            onClick={(e) => e.stopPropagation()}
            role="dialog"
            aria-labelledby="sg-settings-liability-title"
          >
            <h3 id="sg-settings-liability-title" className="sg-block-title">Pilote automatique</h3>
            <p className="sg-set-sub">
              En activant le pilote automatique, SEO Guy rédige, publie et suit les contenus sans
              validation humaine. Vous restez responsable de la publication au sens légal et vous
              vous engagez à contrôler régulièrement les contenus créés.
            </p>
            <div className="sg-liability-actions">
              <button type="button" className="sg-btn-ghost sm" onClick={() => setLiabilityOpen(false)}>
                Annuler
              </button>
              <button type="button" className="sg-btn-primary sm" onClick={() => void applyFullAuto()}>
                J&apos;accepte et j&apos;active
              </button>
            </div>
          </div>
        </div>
      ) : null}
    </div>
  );
}

// =================== PANNEAU DE VALIDATION ===================
function ReviewPanel({
  article,
  rawArticle,
  shop,
  publishingMode = "standalone",
  hasFalKey,
  quota,
  onClose,
  onPublish,
  onSaveDraft,
  onSchedule,
  onRefresh,
  busy,
}) {
  const [phase, setPhase] = useS("idle");
  const [loading, setLoading] = useS(false);
  const [seoLoading, setSeoLoading] = useS(false);
  const [regenBusy, setRegenBusy] = useS(null);
  const [editMode, setEditMode] = useS(false);
  const [title, setTitle] = useS(article?.title || "");
  const [bodyHtml, setBodyHtml] = useS(article?.bodyHtml || "");
  const [imageUrl, setImageUrl] = useS(article?.imageUrl || null);
  const [articleMeta, setArticleMeta] = useS(rawArticle || {});
  const [seoMetrics, setSeoMetrics] = useS(null);
  const [scheduleOpen, setScheduleOpen] = useS(false);
  const [scheduleDate, setScheduleDate] = useS("");
  const [scheduleTime, setScheduleTime] = useS("09:00");
  const [error, setError] = useS("");
  const [imageLoading, setImageLoading] = useS(false);
  const [moreActionsOpen, setMoreActionsOpen] = useS(false);
  const [statsOpen, setStatsOpen] = useS(false);
  const [heroCollapsed, setHeroCollapsed] = useS(false);
  const docScrollRef = useR(null);

  const articleId = rawArticle?.id;
  const score = seoMetrics?.scoreSeo ?? article?.score ?? 0;
  const scoreLabel = scoreSeoLabel(score);
  const checklist = buildSeoChecklistFromMetrics(seoMetrics);
  const isShopifyMode = publishingMode === "shopify";
  const isWordpressMode = publishingMode === "wordpress";
  const shopifyPreviewUrl = buildShopifyArticlePreviewUrl(shop, articleMeta);
  const draftLabel = isWordpressMode
    ? "Brouillon WordPress"
    : isShopifyMode
      ? "Brouillon Shopify"
      : "Enregistrer en brouillon";
  useE(() => { document.body.style.overflow = "hidden"; return () => { document.body.style.overflow = ""; }; }, []);

  useE(() => {
    const el = docScrollRef.current;
    if (!el) return undefined;
    function onScroll() {
      setHeroCollapsed(el.scrollTop > 36);
    }
    onScroll();
    el.addEventListener("scroll", onScroll, { passive: true });
    return () => el.removeEventListener("scroll", onScroll);
  }, [phase, articleId]);
  function applyArticle(a) {
    if (!a) return;
    if (a.title) setTitle(a.title);
    if (a.bodyHtml) setBodyHtml(a.bodyHtml);
    if (a.imageUrl) setImageUrl(a.imageUrl);
    setArticleMeta((prev) => ({ ...prev, ...a }));
  }

  async function loadSeoMetrics(id) {
    if (!id) return;
    setSeoLoading(true);
    try {
      const res = await GuyAPI.articleSeoMetrics(id);
      setSeoMetrics(res.metrics);
    } catch {
      setSeoMetrics(null);
    } finally {
      setSeoLoading(false);
    }
  }

  async function waitForImage(id, previousImageUrl) {
    if (!hasFalKey) return;
    setImageLoading(true);
    try {
      const withImage = await GuyAPI.pollArticleImageReady(id, { previousImageUrl });
      applyArticle(withImage);
    } catch (e) {
      try {
        const { article: latest } = await GuyAPI.getArticle(id);
        if (latest?.imageUrl) applyArticle(latest);
        else setError(e.message || "Image indisponible");
      } catch {
        setError(e.message || "Image indisponible");
      }
    } finally {
      setImageLoading(false);
    }
  }

  async function ensureArticleContent(id) {
    const res = await GuyAPI.getArticle(id);
    let a = res.article;

    if (a?.bodyHtml?.trim()) {
      applyArticle(a);
      setLoading(false);
      if (hasFalKey && !a.imageUrl) {
        await waitForImage(id);
        a = (await GuyAPI.getArticle(id)).article;
      }
      return a;
    }

    const needsWrite =
      !a?.bodyHtml?.trim() &&
      ["proposed", "saved", "titled", "approved", "failed"].includes(a?.status);
    if (needsWrite) {
      if (a?.status !== "failed") {
        const { quota: freshQuota } = await GuyAPI.getArticleQuota();
        if (freshQuota && !freshQuota.unlimited && !freshQuota.canGenerate) {
          throw new Error(`Quota hebdomadaire atteint (${freshQuota.used}/${freshQuota.limit}).`);
        }
      }
      await GuyAPI.writeArticle(id);
      onRefresh?.();
    } else if (a?.status === "approved") {
      await GuyAPI.generateContent(id, {
        withImage: !!hasFalKey && a?.imagePrompt !== "client-upload" && !a?.imageUrl,
      });
    }

    const withText = await GuyAPI.pollArticleTextReady(id);
    applyArticle(withText);
    setLoading(false);
    await loadSeoMetrics(id);
    await waitForImage(id);
    return withText;
  }

  useE(() => {
    const id = rawArticle?.id;
    if (!id) return;
    let cancelled = false;
    setLoading(true);
    setError("");
    (async () => {
      try {
        const a = await ensureArticleContent(id);
        if (cancelled) return;
      } catch (e) {
        if (!cancelled) setError(e.message || "Chargement impossible");
      } finally {
        if (!cancelled) setLoading(false);
      }
    })();
    return () => { cancelled = true; };
  }, [rawArticle?.id]);

  useE(() => {
    if (!articleId || imageUrl || !hasFalKey) return undefined;
    let cancelled = false;
    async function tick() {
      try {
        const { article } = await GuyAPI.getArticle(articleId);
        if (!cancelled && article?.imageUrl) applyArticle(article);
      } catch {
        /* ignore */
      }
    }
    void tick();
    const t = setInterval(tick, 4000);
    return () => {
      cancelled = true;
      clearInterval(t);
    };
  }, [articleId, imageUrl, hasFalKey]);

  async function runAction(fn) {
    setError("");
    setPhase("busy");
    try {
      await fn();
      onRefresh?.();
      setPhase("done");
      setTimeout(() => onClose(), 1800);
    } catch (e) {
      setError(e.message || "Action impossible");
      setPhase("idle");
    }
  }

  function publish() {
    if (!articleId) {
      setError("Article non enregistré — relancez la génération.");
      return;
    }
    if (!bodyHtml?.trim()) {
      setError("Aucun contenu à publier.");
      return;
    }
    runAction(() => onPublish(articleId, bodyHtml));
  }

  function saveDraft() {
    if (!articleId) {
      setError("Article non enregistré — relancez la génération.");
      return;
    }
    runAction(() => onSaveDraft(articleId, bodyHtml));
  }

  function schedule() {
    if (!articleId || !scheduleDate) return;
    const iso = new Date(`${scheduleDate}T${scheduleTime || "09:00"}`).toISOString();
    runAction(() => onSchedule(articleId, iso, bodyHtml));
  }

  async function openPreview() {
    if (isShopifyMode) {
      if (shopifyPreviewUrl) {
        window.open(shopifyPreviewUrl, "_blank", "noopener,noreferrer");
        return;
      }
      setEditMode(false);
      setError("Enregistrez un brouillon Shopify pour obtenir l'URL d'aperçu boutique.");
      return;
    }
    if (!bodyHtml?.trim()) {
      setError("Générez le contenu avant l'aperçu.");
      return;
    }
    let cover = imageUrl;
    if (articleId) {
      try {
        const { article } = await GuyAPI.getArticle(articleId);
        if (article) {
          applyArticle(article);
          cover = article.imageUrl || cover;
        }
      } catch {
        /* garde l’état local */
      }
    }
    try {
      openGuyArticlePreview({
        title,
        bodyHtml,
        imageUrl: cover,
        imageAlt: articleMeta.imageAlt,
        metaDescription: articleMeta.metaDescription,
      });
    } catch (e) {
      setError(e.message || "Aperçu impossible");
    }
  }

  function exportHtml() {
    if (!bodyHtml?.trim()) {
      setError("Générez le contenu avant l'export.");
      return;
    }
    try {
      exportGuyArticleHtml({
        title,
        bodyHtml,
        imageUrl,
        imageAlt: articleMeta.imageAlt,
        metaDescription: articleMeta.metaDescription,
      });
    } catch (e) {
      setError(e.message || "Export impossible");
    }
  }

  async function regenerateContent() {
    if (!articleId) return;
    setError("");
    setRegenBusy("content");
    try {
      await GuyAPI.generateContent(articleId, {
        withImage: !!hasFalKey && articleMeta.imagePrompt !== "client-upload",
      });
      const withText = await GuyAPI.pollArticleTextReady(articleId);
      applyArticle(withText);
      setRegenBusy(null);
      await loadSeoMetrics(articleId);
      await waitForImage(articleId);
      onRefresh?.();
    } catch (e) {
      setError(e.message || "Régénération impossible");
      setRegenBusy(null);
    }
  }

  async function regenerateImage() {
    if (!articleId || !bodyHtml?.trim()) {
      setError("Générez le contenu avant l’illustration.");
      return;
    }
    if (!hasFalKey) {
      setError("Clé FAL non configurée — illustration indisponible.");
      return;
    }
    setError("");
    setRegenBusy("image");
    const previous = imageUrl || null;
    try {
      await GuyAPI.articleAction(articleId, { action: "regenerate_image" });
      const next = await GuyAPI.pollArticleImageReady(articleId, {
        previousImageUrl: previous,
      });
      applyArticle(next);
      await loadSeoMetrics(articleId);
      onRefresh?.();
    } catch (e) {
      setError(e.message || "Régénération image impossible");
    } finally {
      setRegenBusy(null);
    }
  }

  async function uploadHeroImage(file) {
    if (!articleId || !file) return;
    setError("");
    setRegenBusy("image");
    try {
      const dataUrl = await resizeBlogCoverFile(file);
      const res = await GuyAPI.uploadArticleImage(articleId, dataUrl);
      const nextUrl = res.article?.imageUrl || res.url;
      if (!nextUrl) throw new Error("Import du visuel impossible.");
      setImageUrl(nextUrl);
      setArticleMeta((prev) => ({
        ...prev,
        imageUrl: nextUrl,
        imagePrompt: res.article?.imagePrompt || "client-upload",
      }));
      setImageLoading(false);
      onRefresh?.();
    } catch (e) {
      setError(e.message || "Import du visuel impossible");
    } finally {
      setRegenBusy(null);
    }
  }

  const defaultSchedule = () => {
    const d = new Date();
    d.setDate(d.getDate() + 1);
    return d.toISOString().slice(0, 10);
  };

  const showTextLoader = loading || regenBusy === "content";
  const contentBusy = showTextLoader || imageLoading || regenBusy === "image" || phase === "busy";

  return (
    <div className="sg-overlay" onClick={onClose}>
      <div className="sg-review" onClick={(e) => e.stopPropagation()}>
        {phase === "done" ? (
          <div className="sg-review-done">
            <img src={POSE.optimise} alt="SEO GUY" />
            <h2>Opération réussie ✅</h2>
            <p>Votre article a été traité. Vous pouvez fermer cette fenêtre.</p>
            <button type="button" className="sg-btn-primary" onClick={onClose}>Parfait, fermer</button>
          </div>
        ) : (
          <>
            <div className="sg-review-doc" ref={docScrollRef}>
              <button type="button" className="sg-review-close" onClick={onClose} aria-label="Fermer">✕</button>
              <span className="sg-next-eyebrow"><Icon name="spark" size={14} /> Aperçu de l'article</span>
              {editMode && !showTextLoader ? (
                <input className="sg-review-title-input" value={title} onChange={(e) => setTitle(e.target.value)} />
              ) : (
                <h2 className="sg-review-title">{title || "Sans titre"}</h2>
              )}
              {showTextLoader ? (
                <GuyThinkingLoader
                  block
                  title={regenBusy === "content" ? "SEO GUY réécrit votre article…" : "SEO GUY rédige votre article…"}
                  sub="Rédaction et optimisation SEO — le texte s'affichera dès qu'il est prêt."
                />
              ) : (
                <>
                  <div className={`sg-review-hero-wrap${heroCollapsed ? " is-collapsed" : ""}`}>
                    {imageUrl ? (
                      <img src={imageUrl} alt={articleMeta.imageAlt || title || ""} className="sg-review-hero-img" />
                    ) : imageLoading ? (
                      <div className="sg-review-hero sg-review-hero--loading">
                        <span className="sg-spinner" aria-hidden="true" />
                        <span>Image en cours de génération…</span>
                      </div>
                    ) : (
                      <div className="sg-review-hero"><span>Pas encore d’illustration — importez la vôtre ou laissez Guy en générer une.</span></div>
                    )}
                    {regenBusy === "image" && (
                      <div className="sg-review-hero-busy">
                        <img src={POSE.analyse} alt="" className="sg-guy-thinking-img is-xs" />
                        <span>{imageUrl ? "Mise à jour du visuel…" : "Nouvelle image…"}</span>
                      </div>
                    )}
                  </div>
                  <p className="sg-art-cover-hint sg-review-cover-hint">{BLOG_COVER_HINT}</p>
                  {editMode ? (
                    <HtmlEditor
                      value={bodyHtml}
                      onChange={setBodyHtml}
                      disabled={contentBusy}
                    />
                  ) : (
                    <div
                      className="sg-review-body-html"
                      dangerouslySetInnerHTML={{
                        __html: bodyHtml?.trim()
                          ? bodyHtml
                          : "<p><em>Aucun contenu pour l’instant.</em></p>",
                      }}
                    />
                  )}
                </>
              )}
              {error && <p className="sg-review-error">{error}</p>}
            </div>
            <aside className="sg-review-side">
              <div className="sg-review-cta">
                <div className="sg-review-cta-primary is-3">
                  <button type="button" className="sg-btn-ghost sm" onClick={() => setEditMode(!editMode)} disabled={contentBusy}>
                    {editMode ? "Lecture" : "Éditer"}
                  </button>
                  <button type="button" className="sg-btn-ghost sm" onClick={saveDraft} disabled={contentBusy || busy || !articleId || !bodyHtml?.trim()}>
                    Brouillon
                  </button>
                  <button type="button" className="sg-btn-primary sm" onClick={publish} disabled={contentBusy || busy || !articleId || !bodyHtml?.trim()}>
                    {phase === "busy" ? (<><span className="sg-spinner" /> …</>) : (<><Icon name="check" size={14} /> Publier</>)}
                  </button>
                </div>
                <button
                  type="button"
                  className={`sg-review-more-toggle${moreActionsOpen ? " is-open" : ""}`}
                  onClick={() => {
                    setMoreActionsOpen((v) => !v);
                    if (moreActionsOpen) {
                      setScheduleOpen(false);
                      setStatsOpen(false);
                    }
                  }}
                  aria-expanded={moreActionsOpen}
                >
                  <span>Autres actions</span>
                  <Icon name="arrow" size={14} />
                </button>
                {moreActionsOpen && (
                  <div className="sg-review-more-sheet">
                    <button type="button" className="sg-btn-ghost sm" onClick={() => { setScheduleOpen(!scheduleOpen); if (!scheduleDate) setScheduleDate(defaultSchedule()); }} disabled={!articleId || !bodyHtml?.trim()}>
                      <Icon name="calendar" size={14} /> Programmer
                    </button>
                    {scheduleOpen && (
                      <div className="sg-review-schedule">
                        <input type="date" value={scheduleDate || defaultSchedule()} onChange={(e) => setScheduleDate(e.target.value)} />
                        <input type="time" value={scheduleTime} onChange={(e) => setScheduleTime(e.target.value)} />
                        <button type="button" className="sg-btn-primary sm" onClick={schedule} disabled={contentBusy || busy}>Confirmer</button>
                      </div>
                    )}
                    <button type="button" className="sg-btn-ghost sm" onClick={openPreview} disabled={contentBusy}>
                      {isShopifyMode ? "Aperçu boutique" : isWordpressMode ? "Aperçu WordPress" : "Aperçu article"}
                    </button>
                    {!isShopifyMode && (
                      <button type="button" className="sg-btn-ghost sm" onClick={exportHtml} disabled={contentBusy || !bodyHtml?.trim()}>
                        Exporter HTML
                      </button>
                    )}
                    <button type="button" className="sg-btn-ghost sm" onClick={regenerateContent} disabled={contentBusy || !articleId}>
                      <Icon name="spark" size={14} /> Autre version
                    </button>
                    <button type="button" className="sg-btn-ghost sm" onClick={regenerateImage} disabled={contentBusy || !hasFalKey || !bodyHtml?.trim()}>
                      <Icon name="spark" size={14} /> Nouvelle image
                    </button>
                    <label className={`sg-btn-ghost sm sg-art-cover-btn${regenBusy === "image" ? " is-busy" : ""}`} htmlFor={`sg-review-cover-${articleId}`}>
                      <Icon name="image" size={14} /> {imageUrl ? "Remplacer par mon visuel" : "Importer un visuel"}
                    </label>
                    <input
                      id={`sg-review-cover-${articleId}`}
                      type="file"
                      accept="image/jpeg,image/png,image/webp"
                      hidden
                      disabled={contentBusy || !articleId}
                      onChange={(e) => {
                        const file = e.target.files && e.target.files[0];
                        e.target.value = "";
                        if (file) void uploadHeroImage(file);
                      }}
                    />
                    <button
                      type="button"
                      className={`sg-review-more-toggle sg-review-stats-toggle${statsOpen ? " is-open" : ""}`}
                      onClick={() => setStatsOpen((v) => !v)}
                      aria-expanded={statsOpen}
                    >
                      <span>
                        Statistiques
                        {!seoLoading && score > 0 ? (
                          <em className="sg-review-stats-badge">{score} · {scoreLabel}</em>
                        ) : null}
                      </span>
                      <Icon name="arrow" size={14} />
                    </button>
                    {statsOpen && (
                      <div className="sg-review-stats-sheet">
                        <div className="sg-review-metrics">
                          <div className="sg-review-score">
                            {seoLoading ? (
                              <span className="sg-spinner sm" />
                            ) : (
                              <ScoreRing value={score} size={48} />
                            )}
                            <div>
                              <p className="sg-review-score-lab">Score SEO</p>
                              <p className="sg-review-score-val">{seoLoading ? "Analyse…" : scoreLabel}</p>
                              {!seoLoading ? <SeoScoreHumanNote /> : null}
                            </div>
                          </div>
                          <ul className="sg-checklist is-compact is-2col">
                            {(checklist.length ? checklist : [
                              { label: "Mot-clé bien placé", done: false },
                              { label: "Titre optimisé", done: false },
                              { label: "Structure Hn", done: false },
                              { label: "Maillage interne", done: false },
                              { label: "Méta-description", done: false },
                              { label: "Image + alt", done: false },
                            ]).map((c) => (
                              <li key={c.label}>
                                <span className={`sg-check-ic${c.done ? "" : " is-fail"}`}>
                                  {c.done ? <Icon name="check" size={11} /> : "·"}
                                </span>
                                <span>{c.label}</span>
                              </li>
                            ))}
                          </ul>
                        </div>
                      </div>
                    )}
                  </div>
                )}
              </div>
            </aside>
          </>
        )}
      </div>
    </div>
  );
}

// =================== INTERVIEWS ===================
function InterviewsScreen({ themes, past, toValidate, onLaunch, onValidate, showMascot, credits, onProposeThemes, busy }) {
  return (
    <div className="sg-screen">
      {toValidate?.length > 0 ? (
        <section className="sg-panel" style={{ marginBottom: 20 }}>
          <div className="sg-panel-head">
            <h3 className="sg-block-title">Interviews à valider</h3>
            <span className="sg-block-note">
              Relisez les réponses ou l&apos;article généré, puis validez pour publication
            </span>
          </div>
          <div className="sg-iv-past">
            {toValidate.map((p) => (
              <div key={`${p.kind}-${p.id}`} className="sg-top-row">
                <span className="sg-iv-past-ic"><Icon name="mic" size={16} /></span>
                <div className="sg-top-main">
                  <p className="sg-top-title">{p.title}</p>
                  <p className="sg-top-kw">
                    Avec {p.who} · {p.keyword}
                    {p.completedAt
                      ? ` · ${new Date(p.completedAt).toLocaleDateString("fr-FR")}`
                      : ""}
                  </p>
                </div>
                <button type="button" className="sg-btn-primary sm" onClick={() => onValidate(p)}>
                  <Icon name={p.kind === "article" ? "check" : "spark"} size={14} />
                  {p.actionLabel || "Valider"}
                </button>
              </div>
            ))}
          </div>
        </section>
      ) : null}

      {/* Bandeau coach : ce que c'est, en une ligne */}
      <section className="sg-iv-intro">
        <img src={POSE.idea} alt="SEO GUY" />
        <div className="sg-iv-intro-txt">
          <p className="sg-iv-intro-title">J'ai repéré 3 sujets où votre expertise ferait la différence</p>
          <p className="sg-iv-intro-sub">Choisissez-en un, répondez à 4 petites questions (4–5 min), et je transforme vos réponses en article optimisé.</p>
        </div>
      </section>

      {/* Sujets proposés */}
      <section className="sg-iv-grid">
        {themes.map((th) => (
          <article key={th.id} className="sg-iv-card">
            <span className="sg-next-eyebrow"><Icon name="mic" size={14} /> Sujet d'interview proposé</span>
            <h3 className="sg-iv-card-title">{th.title}</h3>
            <p className="sg-iv-card-angle"><Icon name="target" size={15} /> {th.angle}</p>
            <div className="sg-iv-card-meta">
              <span><b>Mot-clé</b> {th.keyword}</span>
              <span><b>Recherches</b> {th.volume}</span>
            </div>
            <div className="sg-iv-card-who">
              <span className="sg-iv-who-ic"><Icon name="mic" size={14} /></span>
              <span>Qui répond{' '}: <b>{th.who}</b></span>
            </div>
            <div className="sg-iv-card-foot">
              <span className="sg-iv-card-time"><Icon name="bolt" size={14} /> {th.minutes || 5} min · {(th.questions || []).length} questions</span>
              <button className="sg-btn-primary sm" onClick={() => onLaunch(th)}>
                <Icon name="mic" size={15} /> Démarrer l'interview
              </button>
            </div>
          </article>
        ))}
      </section>

      {/* Articles d'interview générés */}
      <section className="sg-panel">
        <div className="sg-panel-head">
          <h3 className="sg-block-title">Interviews générées</h3>
          <span className="sg-block-note">À relire, publier ou programmer</span>
        </div>
        <div className="sg-iv-past">
          {past.map((p) => (
            <div key={p.id} className="sg-top-row">
              <span className="sg-iv-past-ic"><Icon name="mic" size={16} /></span>
              <div className="sg-top-main">
                <p className="sg-top-title">{p.title}</p>
                <p className="sg-top-kw">Avec {p.who} · {p.date}</p>
              </div>
              <div className="sg-top-vis"><b>{(p.visits ?? 0).toLocaleString("fr-FR")}</b><span>visites</span></div>
              <ScoreRing value={p.score} size={40} />
            </div>
          ))}
        </div>
      </section>
    </div>
  );
}

function formatInterviewDisplayTitle(themeTitle, mode) {
  const t = (themeTitle || "").trim();
  if (!t) return mode === "round_table" ? "Table ronde" : "Interview";
  const lower = t.toLowerCase();
  if (lower.includes("interview") || lower.includes("table ronde")) return t;
  return mode === "round_table" ? `Table ronde : ${t}` : `Interview : ${t}`;
}

function InterviewReviewModal({ sessionId, onClose, onRefresh }) {
  const [loading, setLoading] = useS(true);
  const [busy, setBusy] = useS(false);
  const [error, setError] = useS("");
  const [session, setSession] = useS(null);
  const [transcript, setTranscript] = useS([]);

  useE(() => {
    document.body.style.overflow = "hidden";
    return () => { document.body.style.overflow = ""; };
  }, []);

  useE(() => {
    let cancelled = false;
    (async () => {
      setLoading(true);
      setError("");
      try {
        const data = await GuyAPI.fetchInterviewSession(sessionId);
        if (cancelled) return;
        setSession(data.session);
        setTranscript(data.session.transcript || []);
      } catch (e) {
        if (!cancelled) setError(e.message || "Chargement impossible");
      } finally {
        if (!cancelled) setLoading(false);
      }
    })();
    return () => { cancelled = true; };
  }, [sessionId]);

  async function generateArticle() {
    setBusy(true);
    setError("");
    try {
      const canSave =
        (session?.status === "completed" ||
          (session?.status === "in_progress" && session?.completedAt)) &&
        !session?.blogArticleId &&
        transcript.length > 0;
      if (canSave) {
        await GuyAPI.saveInterviewTranscript(sessionId, transcript);
      } else if (session?.blogArticleId) {
        throw new Error(
          "L'article a déjà été généré. Ouvrez-le depuis « Interviews générées ».",
        );
      }
      await GuyAPI.completeInterviewArticle(sessionId, false);
      if (onRefresh) await onRefresh();
      onClose();
    } catch (e) {
      setError(e.message || "Génération impossible");
    } finally {
      setBusy(false);
    }
  }

  async function polish() {
    setBusy(true);
    setError("");
    try {
      await GuyAPI.polishInterviewTranscript(sessionId);
      const data = await GuyAPI.fetchInterviewSession(sessionId);
      setTranscript(data.session.transcript || []);
    } catch (e) {
      setError(e.message || "Re-optimisation impossible");
    } finally {
      setBusy(false);
    }
  }

  function updateAnswer(turnIdx, answerIdx, value) {
    setTranscript((rows) =>
      rows.map((t, ti) => {
        if (ti !== turnIdx) return t;
        return {
          ...t,
          answers: t.answers.map((a, ai) =>
            ai === answerIdx ? { ...a, text: value } : a,
          ),
        };
      }),
    );
  }

  const participants = session?.participants || [];

  return (
    <div className="sg-overlay" onClick={onClose}>
      <div className="sg-iv-room" onClick={(e) => e.stopPropagation()}>
        <header className="sg-iv-room-head">
          <div>
            <span className="sg-next-eyebrow"><Icon name="mic" size={14} /> Interview terminée</span>
            <h2 className="sg-iv-room-title">
              {session
                ? formatInterviewDisplayTitle(session.themeTitle, session.mode)
                : "Relire l'interview"}
            </h2>
            {session ? (
              <p className="sg-set-sub" style={{ marginTop: 6 }}>
                Mot-clé : {session.seedKeyword} · {session.questionCount} réponses enregistrées
              </p>
            ) : null}
          </div>
          <button type="button" className="sg-review-close" onClick={onClose} aria-label="Fermer">✕</button>
        </header>
        <div className="sg-iv-wizard-body">
          {loading ? (
            <div className="sg-iv-writing">
              <span className="sg-spinner sm" />
              <p>Chargement de la transcription…</p>
            </div>
          ) : (
            <>
              <p className="sg-set-sub">
                Relisez les réponses, ajustez si besoin, puis générez l&apos;article SEO et le visuel.
              </p>
              {transcript.map((turn, ti) => (
                <div key={turn.questionIndex} className="sg-panel sg-iv-wizard-panel" style={{ marginBottom: 12 }}>
                  <p className="sg-set-title">{turn.question}</p>
                  {turn.answers.map((a, ai) => (
                    <div key={ai} style={{ marginTop: 8 }}>
                      <label className="sg-iv-hint">
                        {participants[a.participantIndex]?.fullName || `Participant ${a.participantIndex + 1}`}
                      </label>
                      <textarea
                        className="sg-textarea"
                        rows={3}
                        value={a.text}
                        onChange={(e) => updateAnswer(ti, ai, e.target.value)}
                        disabled={busy}
                      />
                    </div>
                  ))}
                </div>
              ))}
              {error ? <p className="sg-review-error">{error}</p> : null}
            </>
          )}
        </div>
        <footer className="sg-iv-room-foot">
          <button type="button" className="sg-btn-ghost" onClick={onClose} disabled={busy}>Fermer</button>
          <div className="sg-toolbar-inline">
            <button type="button" className="sg-btn-ghost sm" onClick={() => void polish()} disabled={loading || busy}>
              Re-optimiser (IA)
            </button>
            <button type="button" className="sg-btn-primary" onClick={() => void generateArticle()} disabled={loading || busy || !session}>
              {busy ? <span className="sg-spinner sm" /> : null} Générer l&apos;article
            </button>
          </div>
        </footer>
      </div>
    </div>
  );
}

// =================== PANNEAU SESSION INTERVIEW ===================
const IV_LINK_OPTS = [
  { days: 1, label: "24 heures" },
  { days: 3, label: "3 jours" },
  { days: 7, label: "7 jours" },
  { days: 14, label: "14 jours" },
  { days: 30, label: "30 jours" },
];

function formatIvExpiry(iso) {
  try {
    return new Date(iso).toLocaleString("fr-FR", { dateStyle: "long", timeStyle: "short" });
  } catch {
    return iso;
  }
}

function withIvLauncher(url, from) {
  try {
    const u = new URL(url);
    u.searchParams.set("from", from);
    return u.toString();
  } catch {
    const sep = url.includes("?") ? "&" : "?";
    return `${url}${sep}from=${from}`;
  }
}

function InterviewInviteSuccess({ roomUrl, expiresAt, onLaunch, onClose }) {
  const [copied, setCopied] = useS(false);
  async function copy() {
    try {
      await navigator.clipboard.writeText(roomUrl);
      setCopied(true);
      window.setTimeout(() => setCopied(false), 2200);
    } catch { /* ignore */ }
  }
  return (
    <div className="sg-iv-success">
      <div className="sg-iv-success-head">
        <img src={POSE.optimise} alt="" className="sg-iv-success-guy" />
        <div>
          <p className="sg-next-eyebrow"><Icon name="check" size={14} /> Invitation prête</p>
          <h2 className="sg-iv-success-title">Votre lien d&apos;interview est actif</h2>
          <p className="sg-set-sub">Copiez le lien pour un expert externe, ou lancez l&apos;interview vous-même.</p>
        </div>
      </div>
      <div className="sg-iv-success-card">
        <label className="sg-iv-success-label">Lien à partager</label>
        <div className="sg-iv-invite-row">
          <input className="sg-iv-invite-input" readOnly value={roomUrl} aria-label="Lien d'invitation" />
          <button type="button" className="sg-btn-ghost sm" onClick={() => void copy()}>
            {copied ? "Copié !" : "Copier le lien"}
          </button>
        </div>
        <p className="sg-iv-hint" style={{ marginTop: 10 }}>
          Valide jusqu&apos;au <strong>{formatIvExpiry(expiresAt)}</strong>
        </p>
      </div>
      <div className="sg-iv-success-actions">
        <button type="button" className="sg-btn-primary" onClick={onLaunch}>
          <Icon name="mic" size={16} /> Lancer l&apos;interview
        </button>
        <button type="button" className="sg-btn-ghost" onClick={onClose}>Fermer</button>
      </div>
    </div>
  );
}

const IV_WIZARD_STEPS = ["Le sujet", "Format & invités", "Invitation & lancement"];
const IV_ANALYSIS_MSGS = [
  "Nous recherchons les sujets SEO pour votre business…",
  "Analyse de vos mots-clés et des tendances de votre marché…",
  "Croisement de votre charte éditoriale avec le volume de recherche…",
  "Sélection des angles les plus porteurs pour votre audience…",
  "Préparation de propositions d'interview à fort potentiel…",
];

function themeToProposal(th) {
  if (!th) return null;
  return {
    sessionId: th.sessionId || th.id,
    themeTitle: th.themeTitle || th.title || "",
    seedKeyword: th.seedKeyword || th.keyword || "",
    angle: th.angle || "",
    estimatedVolume: th.estimatedVolume ?? th.volume ?? null,
    sampleQuestions: th.sampleQuestions || th.questions || [],
  };
}

function InterviewThinkingLoader({ messageIndex }) {
  return (
    <div className="sg-iv-thinking">
      <img src={POSE.analyse} alt="" className="sg-iv-thinking-img" />
      <div className="sg-iv-thinking-body">
        <span className="sg-spinner lg" aria-hidden="true" />
        <p className="sg-iv-thinking-title">{IV_ANALYSIS_MSGS[messageIndex]}</p>
        <p className="sg-iv-thinking-sub">Analyse approfondie en cours — cela prend quelques secondes.</p>
      </div>
    </div>
  );
}

function InterviewWizardModal({
  onClose,
  onRefresh,
  credits,
  savedThemes = [],
  initialTheme,
}) {
  const [wizardStep, setWizardStep] = useS(0);
  const [themes, setThemes] = useS([]);
  const [themesLoading, setThemesLoading] = useS(false);
  const [msgIdx, setMsgIdx] = useS(0);
  const [selectedIdx, setSelectedIdx] = useS(0);
  const [customSubject, setCustomSubject] = useS("");
  const [useCustom, setUseCustom] = useS(false);
  const [saveBusy, setSaveBusy] = useS(false);
  const [mode, setMode] = useS("solo");
  const [participants, setParticipants] = useS([{ fullName: "", jobTitle: "" }]);
  const [coverImageUrl, setCoverImageUrl] = useS(null);
  const [linkValidDays, setLinkValidDays] = useS(7);
  const [phase, setPhase] = useS("wizard");
  const [inviteResult, setInviteResult] = useS(null);
  const [loading, setLoading] = useS(false);
  const [error, setError] = useS("");

  const canStart = credits?.canStart ?? (credits?.totalAvailable > 0);

  useE(() => {
    document.body.style.overflow = "hidden";
    return () => { document.body.style.overflow = ""; };
  }, []);

  useE(() => {
    let interval;
    if (themesLoading) {
      interval = window.setInterval(
        () => setMsgIdx((i) => (i + 1) % IV_ANALYSIS_MSGS.length),
        2800,
      );
    }
    return () => { if (interval) window.clearInterval(interval); };
  }, [themesLoading]);

  async function loadThemes() {
    setThemesLoading(true);
    setError("");
    try {
      const data = await GuyAPI.proposeInterviewThemes(3);
      setThemes(data.themes || []);
      setSelectedIdx(0);
      setUseCustom(false);
      if (onRefresh) await onRefresh();
    } catch (e) {
      setError(e.message || "Impossible de proposer des sujets.");
    } finally {
      setThemesLoading(false);
    }
  }

  function chooseTheme(theme) {
    setThemes([theme]);
    setSelectedIdx(0);
    setUseCustom(false);
    setCustomSubject("");
    setWizardStep(1);
  }

  useE(() => {
    const proposal = themeToProposal(initialTheme);
    if (proposal) {
      chooseTheme(proposal);
      if (initialTheme?.who && initialTheme.who !== "Vous") {
        setParticipants([{ fullName: initialTheme.who, jobTitle: "" }]);
      }
    } else {
      void loadThemes();
    }
  }, []);

  const resolved = (() => {
    if (useCustom && customSubject.trim()) {
      const kw = customSubject.trim();
      return { themeTitle: kw, seedKeyword: kw, sessionId: undefined };
    }
    const t = themes[selectedIdx];
    if (!t) return null;
    return {
      themeTitle: t.themeTitle,
      seedKeyword: t.seedKeyword,
      sessionId: t.sessionId,
    };
  })();

  function buildPayload() {
    if (!resolved) return null;
    const clean = participants
      .map((p) => ({
        fullName: (p.fullName || "").trim(),
        jobTitle: (p.jobTitle || "").trim() || undefined,
      }))
      .filter((p) => p.fullName.length > 0);
    if (mode === "solo" && clean.length !== 1) return null;
    if (mode === "round_table" && clean.length < 2) return null;
    return {
      sessionId: resolved.sessionId,
      themeTitle: resolved.themeTitle,
      seedKeyword: resolved.seedKeyword,
      mode,
      participants: mode === "solo" ? [clean[0]] : clean,
      coverImageUrl,
      linkValidDays,
      launcherContext: "web",
    };
  }

  async function runInvite() {
    const payload = buildPayload();
    if (!payload || !canStart) return;
    setLoading(true);
    setError("");
    try {
      const data = await GuyAPI.inviteInterview(payload);
      setInviteResult(data);
      setPhase("invite");
      if (onRefresh) await onRefresh();
    } catch (e) {
      setError(e.message || "Impossible de créer le lien.");
    } finally {
      setLoading(false);
    }
  }

  async function runStart() {
    const payload = buildPayload();
    if (!payload || !canStart) return;
    setLoading(true);
    setError("");
    try {
      const data = await GuyAPI.startInterview(payload);
      if (data.roomUrl) {
        window.open(withIvLauncher(data.roomUrl, "web"), "_blank", "noopener,noreferrer");
      }
      if (onRefresh) await onRefresh();
      onClose();
    } catch (e) {
      setError(e.message || "Impossible de lancer l'interview.");
    } finally {
      setLoading(false);
    }
  }

  async function onCoverFile(e) {
    const file = e.target.files?.[0];
    if (!file) return;
    const reader = new FileReader();
    reader.onload = async () => {
      try {
        const body = await GuyAPI.uploadInterviewPhoto(reader.result);
        if (body.url) setCoverImageUrl(body.url);
      } catch (err) {
        setError(err.message || "Upload impossible");
      }
    };
    reader.readAsDataURL(file);
  }

  async function saveForLater(sessionId) {
    if (!sessionId) return;
    setSaveBusy(true);
    try {
      await GuyAPI.saveInterviewForLater(sessionId);
      setThemes((prev) => prev.filter((t) => t.sessionId !== sessionId));
      if (onRefresh) await onRefresh();
    } catch (e) {
      setError(e.message || "Impossible de garder ce sujet.");
    } finally {
      setSaveBusy(false);
    }
  }

  function updateParticipant(i, field, value) {
    setParticipants((arr) => {
      const n = [...arr];
      n[i] = { ...n[i], [field]: value };
      return n;
    });
  }

  const participantsValid =
    mode === "solo"
      ? participants[0]?.fullName?.trim()
      : participants.filter((p) => p.fullName?.trim()).length >= 2;

  const progressPct = Math.round(((wizardStep + 1) / IV_WIZARD_STEPS.length) * 100);

  return (
    <div className="sg-overlay" onClick={onClose}>
      <div className="sg-iv-room" onClick={(e) => e.stopPropagation()}>
        {phase === "invite" && inviteResult?.roomUrl ? (
          <InterviewInviteSuccess
            roomUrl={inviteResult.roomUrl}
            expiresAt={inviteResult.roomTokenExpiresAt}
            onLaunch={() =>
              window.open(
                withIvLauncher(inviteResult.roomUrl, "web"),
                "_blank",
                "noopener,noreferrer",
              )
            }
            onClose={onClose}
          />
        ) : (
          <>
            <header className="sg-iv-room-head">
              <div>
                <span className="sg-next-eyebrow"><Icon name="mic" size={14} /> SEO GUY · Interview expert</span>
                <h2 className="sg-iv-room-title">Nouvelle interview</h2>
                <p className="sg-set-sub" style={{ marginTop: 6 }}>
                  Étape {wizardStep + 1} sur {IV_WIZARD_STEPS.length} — {IV_WIZARD_STEPS[wizardStep]}
                </p>
              </div>
              <button type="button" className="sg-review-close" onClick={onClose} aria-label="Fermer">✕</button>
            </header>
            <div className="sg-iv-progress">
              <div className="sg-iv-progress-bar">
                <span style={{ width: `${progressPct}%` }} />
              </div>
              <span className="sg-iv-progress-lab">{progressPct}%</span>
            </div>
            <div className="sg-iv-wizard-steps" style={{ padding: "12px 30px 0" }}>
              {IV_WIZARD_STEPS.map((label, i) => (
                <span
                  key={label}
                  className={`sg-iv-wizard-step${i <= wizardStep ? " is-done" : ""}${i === wizardStep ? " is-current" : ""}`}
                >
                  {i + 1}. {label}
                </span>
              ))}
            </div>
            <div className="sg-iv-wizard-body">
              {wizardStep === 0 && (
                <section className="sg-iv-wizard-panel">
                  {savedThemes.length > 0 ? (
                    <div className="sg-iv-saved-block">
                      <p className="sg-set-title">Gardés pour plus tard</p>
                      <p className="sg-set-sub">Relancez un sujet que vous aviez mis de côté.</p>
                      {savedThemes.map((t) => {
                        const p = themeToProposal(t);
                        return (
                          <div key={p.sessionId} className="sg-iv-theme-pick">
                            <h4>{p.themeTitle}</h4>
                            <p>Mot-clé : {p.seedKeyword}{p.estimatedVolume ? ` · ~${p.estimatedVolume} recherches/mois` : ""}</p>
                            {p.angle ? <p style={{ marginTop: 8 }}>{p.angle}</p> : null}
                            <div className="sg-iv-theme-pick-actions">
                              <button type="button" className="sg-btn-primary sm" disabled={!canStart} onClick={() => chooseTheme(p)}>
                                Choisir ce sujet
                              </button>
                            </div>
                          </div>
                        );
                      })}
                    </div>
                  ) : null}
                  <p className="sg-set-title">Nouvelles suggestions</p>
                  <p className="sg-set-sub">Guy analyse votre marché pour proposer des sujets à fort potentiel SEO.</p>
                  {themesLoading ? (
                    <InterviewThinkingLoader messageIndex={msgIdx} />
                  ) : null}
                  {!themesLoading && themes.length > 0 ? (
                    <>
                      <p className="sg-set-sub" style={{ marginTop: 12 }}>
                        Voilà les sujets qui peuvent vous apporter des visites — choisissez ou saisissez un sujet libre.
                      </p>
                      {themes.map((t, i) => (
                        <div
                          key={t.sessionId || t.themeTitle}
                          className={`sg-iv-theme-pick${!useCustom && i === selectedIdx ? " is-selected" : ""}`}
                          onClick={() => { setSelectedIdx(i); setUseCustom(false); }}
                        >
                          <h4>{t.themeTitle}</h4>
                          <p>
                            Mot-clé : {t.seedKeyword}
                            {t.estimatedVolume ? ` · ~${t.estimatedVolume} recherches/mois` : ""}
                          </p>
                          {t.angle ? <p style={{ marginTop: 8 }}>{t.angle}</p> : null}
                          <div className="sg-iv-theme-pick-actions">
                            {t.sessionId ? (
                              <button
                                type="button"
                                className="sg-btn-ghost sm"
                                disabled={saveBusy}
                                onClick={(e) => { e.stopPropagation(); void saveForLater(t.sessionId); }}
                              >
                                Garder pour plus tard
                              </button>
                            ) : null}
                            <button
                              type="button"
                              className="sg-btn-primary sm"
                              disabled={!canStart}
                              onClick={(e) => { e.stopPropagation(); chooseTheme(t); }}
                            >
                              Choisir ce sujet
                            </button>
                          </div>
                        </div>
                      ))}
                    </>
                  ) : null}
                  {!themesLoading ? (
                    <div style={{ marginTop: 16 }}>
                      <label className="sg-set-title" style={{ display: "block", marginBottom: 8 }}>
                        Ou saisir un sujet qui vous inspire
                      </label>
                      <input
                        className="sg-textarea"
                        style={{ padding: "12px 14px", resize: "none", width: "100%" }}
                        placeholder="Ex. : tendances snowboard 2026…"
                        value={customSubject}
                        onChange={(e) => {
                          setCustomSubject(e.target.value);
                          setUseCustom(e.target.value.trim().length > 0);
                        }}
                      />
                    </div>
                  ) : null}
                  {error ? <p className="sg-review-error" style={{ marginTop: 12 }}>{error}</p> : null}
                </section>
              )}
              {wizardStep === 1 && (
                <section className="sg-panel sg-iv-wizard-panel">
                  <p className="sg-set-title">Format de l&apos;interview</p>
                  <div className="sg-seg" style={{ marginBottom: 16 }}>
                    <button type="button" className={mode === "solo" ? "is-active" : ""} onClick={() => { setMode("solo"); setParticipants([participants[0] || { fullName: "", jobTitle: "" }]); }}>Solo</button>
                    <button type="button" className={mode === "round_table" ? "is-active" : ""} onClick={() => setMode("round_table")}>Table ronde (2–3)</button>
                  </div>
                  {participants.map((p, i) => (
                    <div key={i} className="sg-set-row" style={{ marginBottom: 8 }}>
                      <input className="sg-textarea" style={{ padding: "10px 14px", resize: "none" }} placeholder="Nom complet" value={p.fullName} onChange={(e) => updateParticipant(i, "fullName", e.target.value)} />
                      <input className="sg-textarea" style={{ padding: "10px 14px", resize: "none", marginTop: 6 }} placeholder="Fonction (optionnel)" value={p.jobTitle || ""} onChange={(e) => updateParticipant(i, "jobTitle", e.target.value)} />
                    </div>
                  ))}
                  {mode === "round_table" && participants.length < 3 && (
                    <button type="button" className="sg-btn-ghost sm" onClick={() => setParticipants((arr) => [...arr, { fullName: "", jobTitle: "" }])}>
                      <Icon name="plus" size={14} /> Ajouter un participant
                    </button>
                  )}
                </section>
              )}
              {wizardStep === 2 && (
                <section className="sg-panel sg-iv-wizard-panel">
                  <p className="sg-set-title">Visuel de couverture</p>
                  <p className="sg-set-sub">Optionnel — sinon Guy génère une image à la publication.</p>
                  <div className="sg-iv-cover-upload">
                    <label className="sg-btn-ghost sm">
                      <Icon name="plus" size={14} /> Upload photo
                      <input type="file" accept="image/jpeg,image/png,image/webp" hidden onChange={onCoverFile} />
                    </label>
                  </div>
                  {coverImageUrl ? <img src={coverImageUrl} alt="" style={{ maxWidth: 200, borderRadius: 8, marginTop: 8 }} /> : null}
                  <p className="sg-set-title" style={{ marginTop: 20 }}>Validité du lien d&apos;invitation</p>
                  <p className="sg-set-sub">Pour envoyer l&apos;interview à une personne externe.</p>
                  <div className="sg-iv-link-opts">
                    {IV_LINK_OPTS.map((opt) => (
                      <button
                        key={opt.days}
                        type="button"
                        className={`sg-btn-ghost sm${linkValidDays === opt.days ? " is-active" : ""}`}
                        onClick={() => setLinkValidDays(opt.days)}
                      >
                        {opt.label}
                      </button>
                    ))}
                  </div>
                  {!canStart ? (
                    <p className="sg-review-error">Aucun crédit interview disponible.</p>
                  ) : null}
                  {error ? <p className="sg-review-error">{error}</p> : null}
                </section>
              )}
            </div>
            <footer className="sg-iv-room-foot">
              {wizardStep === 0 ? (
                <>
                  <button type="button" className="sg-btn-ghost" onClick={onClose} disabled={themesLoading}>
                    Fermer
                  </button>
                  <button
                    type="button"
                    className="sg-btn-primary"
                    disabled={themesLoading || !resolved || !canStart}
                    onClick={() => setWizardStep(1)}
                  >
                    Suivant
                  </button>
                </>
              ) : wizardStep === 1 ? (
                <>
                  <button type="button" className="sg-btn-ghost" onClick={() => setWizardStep(0)} disabled={loading}>
                    Retour
                  </button>
                  <button
                    type="button"
                    className="sg-btn-primary"
                    disabled={!participantsValid}
                    onClick={() => setWizardStep(2)}
                  >
                    Suivant
                  </button>
                </>
              ) : (
                <>
                  <button type="button" className="sg-btn-ghost" onClick={() => setWizardStep(1)} disabled={loading}>
                    Retour
                  </button>
                  <div className="sg-toolbar-inline">
                    <button type="button" className="sg-btn-ghost" disabled={loading || !canStart} onClick={() => void runInvite()}>
                      {loading ? <span className="sg-spinner sm" /> : null} Créer un lien
                    </button>
                    <button type="button" className="sg-btn-primary" disabled={loading || !canStart} onClick={() => void runStart()}>
                      {loading ? <span className="sg-spinner sm" /> : null} Lancer maintenant
                    </button>
                  </div>
                </>
              )}
            </footer>
          </>
        )}
      </div>
    </div>
  );
}

Object.assign(window, { GuyThinkingLoader, HomeScreen, ArticlesScreen, ResultsScreen, SettingsScreen, ReviewPanel, InterviewsScreen, InterviewWizardModal, InterviewReviewModal, KeywordsScreen });
