// SEO GUY — Onboarding 4 étapes (analyse → cibles & plan → GSC → 1er article)
const { useState: useOS, useEffect: useOE, useMemo: useOM, useCallback: useOC } = React;

const OB_STEPS = [
  { id: 1, label: "Analyse" },
  { id: 2, label: "Cibles & plan" },
  { id: 3, label: "Google" },
  { id: 4, label: "1er article" },
];

const OB_RESUME_LABELS = [
  "Analyser votre site",
  "Définir vos cibles et votre formule",
  "Connecter Google",
  "Voir votre premier article",
];

const SEO_GUY_CPV_URL = "https://assistantguy.com/legal/cpv-abonnements/";
const WP_PLUGIN_ZIP_URL = "https://assistantguy.com/downloads/assistant-guy.zip";
const AUTOPILOT_MODE_DESCRIPTION =
  "SEO GUY rédige, illustre et publie vos articles sur votre site selon votre rythme — sans relecture ni validation préalable de votre part.";
const AUTOPILOT_LIABILITY_NOTICE =
  "En activant le pilotage automatique, vous reconnaissez que la publication s'effectue sans contrôle préalable de votre part. Vous demeurez seul responsable des contenus publiés (exactitude, conformité légale et réglementaire, droits des tiers, image de marque) ainsi que de leurs conséquences commerciales, SEO ou juridiques. EMARKETIS ne saurait être tenue responsable des contenus diffusés en mode automatique, dans les limites autorisées par les Conditions particulières de vente.";
const AUTOPILOT_LIABILITY_CHECKBOX_LABEL =
  "J'accepte que la publication automatique relève de ma seule responsabilité et je décharge EMARKETIS de toute responsabilité au titre des contenus publiés sans relecture préalable, conformément à l'article 8 des Conditions particulières de vente.";

function defaultAutopilotSlots(planCode) {
  if (!planCode || planCode === "discovery") {
    return [{ weekday: 1, hour: 9, minute: 0 }];
  }
  if (planCode === "starter") {
    return [1, 2, 3, 4].map((weekday) => ({ weekday, hour: 9, minute: 0 }));
  }
  const slots = [];
  for (const weekday of [1, 2, 3, 4]) {
    slots.push({ weekday, hour: 12, minute: 0 });
    slots.push({ weekday, hour: 18, minute: 0 });
  }
  return slots;
}

function parseAudienceLabel(audience) {
  const raw = (audience || "").trim();
  if (!raw) return { name: "", description: "" };
  const parts = raw.split(/\s*—\s*/).map((p) => p.trim()).filter(Boolean);
  if (parts.length <= 1) return { name: "", description: parts[0] || "" };
  if (parts.length >= 2 && parts[0] === parts[1]) {
    return { name: parts[0], description: parts.slice(2).join(" — ") || parts[1] };
  }
  return { name: parts[0], description: parts.slice(1).join(" — ") };
}

const PERSONA_INTEREST_MARKER = " · Recherche : ";

function splitInterests(text) {
  const raw = (text || "").trim();
  const idx = raw.indexOf(PERSONA_INTEREST_MARKER.trim());
  if (idx === -1) return { description: raw, interests: [] };
  // On coupe sur le marqueur (avec ou sans espaces autour).
  const markerIdx = raw.indexOf("Recherche :");
  const description = raw.slice(0, markerIdx).replace(/[·•|\-\s]+$/, "").trim();
  const interests = raw
    .slice(markerIdx + "Recherche :".length)
    .split(",")
    .map((s) => s.trim())
    .filter(Boolean);
  return { description, interests };
}

function defaultPersonasFromAudiences(audiences) {
  const ids = ["p1", "p2", "p3"];
  return ids.map((id, i) => {
    const preset = PERSONA_PRESETS[i % PERSONA_PRESETS.length];
    const raw = audiences[i];
    if (!raw) {
      return {
        id,
        name: preset.label,
        description: "",
        tone: i === 0 ? PERSONA_TONES[0].label : "",
        interests: [],
        customPhotoUrl: null,
      };
    }
    const audienceText = raw.audience || raw.desc || "";
    const { description: cleaned, interests } = splitInterests(audienceText);
    const { name, description } = parseAudienceLabel(cleaned);
    return {
      id,
      name: raw.name || name || preset.label,
      description: raw.desc || description,
      tone: raw.tone || PERSONA_TONES[0].label,
      interests: Array.isArray(raw.interests) ? raw.interests : interests,
      customPhotoUrl: raw.avatarUrl || null,
    };
  });
}

function personasToTargets(personas) {
  return personas
    .map((p) => {
      const name = (p.name || "").trim();
      const desc = (p.description || "").trim();
      const tone = (p.tone || "").trim();
      const interests = Array.isArray(p.interests) ? p.interests.filter(Boolean) : [];
      let audience = desc;
      if (name && desc) audience = `${name} — ${desc}`;
      else if (name) audience = name;
      if (!audience) return null;
      if (interests.length) audience = `${audience}${PERSONA_INTEREST_MARKER}${interests.join(", ")}`;
      const avatarUrl = p.customPhotoUrl?.trim() || null;
      return { audience, tone, avatarUrl };
    })
    .filter(Boolean);
}

// Limite de personas affichables/éditables dans l'assistant. Pour un plan
// "illimité" on plafonne l'UI à 6 (au-delà ce n'est pas pertinent en onboarding).
const SEGMENTS_UI_CAP = 6;
function segmentLimitForPlan(code, plans) {
  const plan = (plans || []).find((p) => p.code === code);
  const feat = plan?.raw?.features?.segmentsCount;
  if (!feat) return 3;
  if (feat.kind === "limit") return feat.limit === -1 ? SEGMENTS_UI_CAP : Math.max(1, feat.limit);
  return 3;
}

const PLAN_FEATURE_LABELS = {
  articlesPerWeek: "Articles par semaine",
  interviewsPerPeriod: "Interviews incluses",
  keywordsCount: "Mots-clés en base",
  segmentsCount: "Segments éditoriaux",
  competitorsCount: "Concurrents suivis",
  googleSearchConsole: "Google Search Console",
  editorialPlan: "Plan éditorial auto",
  seoRevenueAttribution: "Attribution CA SEO",
  proSeoAnalysis: "Analyse SEO Pro",
  aiKeywordsSuggest: "IA mots-clés",
  expertInterviews: "Interviews expert",
  prioritySupport: "Support prioritaire",
};

function featureLimitLabel(feat) {
  if (!feat) return "—";
  if (feat.kind === "bool") return feat.enabled ? "Inclus" : "—";
  if (feat.limit === -1) return "Illimité";
  return String(feat.limit);
}

function planArticlesLabel(plan) {
  const f = plan.raw?.features?.articlesPerWeek;
  if (!f || f.kind !== "limit") return "—";
  return f.limit === -1 ? "Illimité" : `${f.limit} / semaine`;
}

function planInterviewsLabel(plan) {
  const f = plan.raw?.features?.interviewsPerPeriod;
  if (!f || f.kind !== "limit" || f.limit === 0) return "À l'unité (5 €)";
  if (plan.code === "starter") return `${f.limit} / trimestre`;
  return `${f.limit} / mois`;
}

function planPriceLabel(plan, cycle) {
  const parsed = parseInt(String(plan.price).replace(/\D/g, ""), 10);
  const monthly = plan.raw?.monthlyPriceEur ?? (Number.isFinite(parsed) ? parsed : 0);
  const yearly = plan.raw?.yearlyPriceEur;
  if (monthly === 0) return "0 €";
  if (cycle === "yearly" && yearly) return `${Math.round(yearly / 12)} €`;
  return `${monthly} €`;
}

function planBillingNote(plan, cycle) {
  const monthly = plan.raw?.monthlyPriceEur ?? 0;
  if (monthly === 0) return "Gratuit · sans carte bancaire";
  const yearly = plan.raw?.yearlyPriceEur;
  if (cycle === "yearly" && yearly) return `Facturé ${yearly} € / an via Shopify Billing`;
  return `Facturé ${monthly} € / mois via Shopify Billing`;
}

function ObBackBtn({ onBack, disabled }) {
  return (
    <button type="button" className="ob-btn-ghost ob-btn-back" onClick={onBack} disabled={disabled}>
      ← Retour
    </button>
  );
}

function difficultyLabel(score) {
  if (score == null) return "—";
  const n = score <= 1 ? score * 100 : score;
  if (n <= 33) return "faible";
  if (n <= 66) return "moyenne";
  return "élevée";
}

function OnboardProgress({ step }) {
  return (
    <div className="ob-progress">
      {OB_STEPS.map((s) => (
        <div key={s.id} className={`ob-prog-step${step >= s.id ? " is-done" : ""}${step === s.id ? " is-current" : ""}`}>
          <span className="ob-prog-dot">
            {step > s.id ? <Icon name="check" size={11} /> : s.id}
          </span>
          <span className="ob-prog-label">{s.label}</span>
        </div>
      ))}
    </div>
  );
}

function StepAnalyse({ insights, onConfirm, initialSummary, initialProducts, quotaReached, quota, isWebsite }) {
  const hasInitialSummary = !!(initialSummary && initialSummary.trim());
  const [analyzing, setAnalyzing] = useOS(!hasInitialSummary);
  const [summary, setSummary] = useOS(initialSummary || "");
  const [analysis, setAnalysis] = useOS(null);
  const [products, setProducts] = useOS(initialProducts || []);
  const [productsLoading, setProductsLoading] = useOS(!isWebsite);
  const [saving, setSaving] = useOS(false);
  const [err, setErr] = useOS("");
  const ranRef = React.useRef(false);

  async function runAnalysis() {
    setAnalyzing(true);
    setErr("");
    try {
      const res = await GuyAPI.onboardingSuggestDescription();
      if (res?.description) setSummary(res.description);
      if (res?.analysis) setAnalysis(res.analysis);
      const pages = res?.pages || [];
      if (isWebsite && pages.length) {
        setProducts((prev) => {
          if (prev.some((p) => (p.name || "").trim() || (p.url || "").trim())) return prev;
          return pages.slice(0, 8).map((p, i) => ({
            name: p.name,
            url: p.url,
            selected: i < 4,
          }));
        });
      }
    } catch (e) {
      setErr(
        e.message ||
          (isWebsite
            ? "Impossible d'analyser le site. Vous pouvez rédiger le résumé à la main."
            : "Impossible d'analyser la boutique. Vous pouvez rédiger le résumé à la main."),
      );
    } finally {
      setAnalyzing(false);
      if (isWebsite) setProductsLoading(false);
    }
  }

  useOE(() => {
    if (ranRef.current) return;
    ranRef.current = true;

    if (!hasInitialSummary) runAnalysis();
    else if (isWebsite) setProductsLoading(false);

    if (isWebsite) return;

    if (!(initialProducts && initialProducts.length)) {
      (async () => {
        setProductsLoading(true);
        try {
          const res = await GuyAPI.shopifyProducts();
          const list = (res?.products || [])
            .filter((p) => p.url)
            .map((p) => ({
              name: p.title,
              url: p.url,
              imageUrl: p.imageUrl || "",
              shopifyProductId: p.id || "",
              selected: true,
            }));
          setProducts(list);
        } catch {
          setProducts([]);
        } finally {
          setProductsLoading(false);
        }
      })();
    } else {
      setProductsLoading(false);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  function toggleProduct(i) {
    setProducts((prev) => prev.map((p, idx) => (idx === i ? { ...p, selected: !p.selected } : p)));
  }
  function updateManual(i, field, value) {
    setProducts((prev) => prev.map((p, idx) => (idx === i ? { ...p, [field]: value } : p)));
  }
  function removeProduct(i) {
    setProducts((prev) => prev.filter((_, idx) => idx !== i));
  }
  function addManualProduct() {
    setProducts((prev) => [...prev, { name: "", url: "", selected: true, manual: true }]);
  }

  async function confirm() {
    setSaving(true);
    setErr("");
    const selected = products
      .filter((p) => p.selected && (p.name || "").trim() && /^https?:\/\//i.test((p.url || "").trim()))
      .map((p) => {
        const row = { name: p.name.trim(), url: p.url.trim() };
        if (p.imageUrl && /^https?:\/\//i.test(p.imageUrl)) row.imageUrl = p.imageUrl;
        if (p.shopifyProductId) row.shopifyProductId = p.shopifyProductId;
        return row;
      });
    try {
      await onConfirm({ summary: summary.trim(), products: selected });
    } catch (e) {
      setErr(e.message || "Enregistrement impossible.");
    } finally {
      setSaving(false);
    }
  }

  const pageCount = analysis?.pagesFromHome;
  const catalogCount =
    (analysis?.productsFromCatalog || analysis?.productsFromHome) ??
    insights.productCount;
  const productCount = isWebsite ? pageCount : catalogCount;
  const productLabel = isWebsite
    ? productCount != null
      ? `${productCount} page${productCount > 1 ? "s" : ""}`
      : analyzing
        ? "Site en cours d'analyse"
        : "Site analysé"
    : productCount != null
      ? `${productCount} produits`
      : "Catalogue analysé";
  const kwLabel = `${insights.keywordCount || 0} mots-clés`;
  const top = insights.topKeyword;
  const selectedCount = products.filter((p) => p.selected).length;

  return (
    <div className="ob-step ob-step-analyse">
      <div className="ob-analyse-hero">
        <img src={POSE.hero} alt="SEO GUY" className="ob-mascot-big" />
        <div className="ob-analyse-bubble">
          Bonjour, je suis <b>SEO GUY</b> 👋
        </div>
      </div>

      <h1 className="ob-h1">{isWebsite ? "J'ai analysé votre site" : "J'ai analysé votre boutique"}</h1>
      <p className="ob-sub">
        {analyzing
          ? isWebsite
            ? "J'inspecte votre site, vos pages et votre activité…"
            : "J'inspecte votre site, votre catalogue et vos mots-clés…"
          : "Voici ce que j'ai compris de votre activité. Vérifiez et ajustez si besoin."}
      </p>

      {quotaReached && (
        <div className="ob-error" style={{ background: "#fef3c7", color: "#92400e", borderColor: "#fcd34d" }}>
          <b>Quota hebdomadaire atteint{quota ? ` (${quota.used}/${quota.limit})` : ""}.</b>
          {" "}Vous pouvez refaire la configuration ; le premier article ne sera pas généré à la dernière étape.
        </div>
      )}

      <div className="ob-insights is-visible">
        <div className="ob-insight">
          <span className="ob-insight-ic" style={{ background: "#ede9fe", color: "#6233f6" }}>
            <Icon name="docs" size={18} />
          </span>
          <div>
            <p className="ob-insight-val">{productLabel}</p>
            <p className="ob-insight-lab">{isWebsite ? "repérées sur votre site" : "repérés dans votre catalogue"}</p>
          </div>
        </div>
        <div className="ob-insight">
          <span className="ob-insight-ic" style={{ background: "#d1fae5", color: "#065f46" }}>
            <Icon name="target" size={18} />
          </span>
          <div>
            <p className="ob-insight-val">{kwLabel}</p>
            <p className="ob-insight-lab">dans votre base SEO</p>
          </div>
        </div>
        {top && (
          <div className="ob-insight ob-insight-main">
            <span className="ob-insight-ic" style={{ background: "#fef3c7", color: "#92400e" }}>
              <Icon name="spark" size={18} />
            </span>
            <div>
              <p className="ob-insight-val">Meilleure opportunité</p>
              <p className="ob-insight-lab">
                « {top.keyword} » —{" "}
                {top.volume != null ? `${top.volume.toLocaleString("fr-FR")} rech./mois` : "volume à estimer"}
                · Difficulté {difficultyLabel(top.difficulty)}
              </p>
            </div>
          </div>
        )}
      </div>

      <div className="ob-summary-head">
        <p className="ob-section-label">Résumé de votre activité (rédigé par SEO GUY)</p>
        {!analyzing && (
          <button type="button" className="ob-summary-regen" onClick={runAnalysis}>
            <Icon name="spark" size={13} /> Régénérer
          </button>
        )}
      </div>
      <div className="ob-summary-wrap">
        {analyzing ? (
          <div className="ob-summary-loading">
            <span className="sg-spinner sm" /> SEO GUY rédige le résumé de votre {isWebsite ? "activité" : "boutique"}…
          </div>
        ) : (
          <textarea
            className="ob-summary-textarea"
            rows={4}
            value={summary}
            onChange={(e) => setSummary(e.target.value)}
            placeholder={
              isWebsite
                ? "Ce que vous proposez, à quelle clientèle, et comment vous vous différenciez."
                : "Ce que vous vendez, à quelle clientèle, et comment vous vous différenciez."
            }
          />
        )}
        <p className="ob-field-hint">
          Ce résumé guide SEO GUY pour rédiger des contenus alignés avec votre activité.
        </p>
      </div>

      <p className="ob-section-label">
        {isWebsite ? "Pages et offres à mettre en avant" : "Produits à mettre en avant"}
        {selectedCount > 0 ? ` · ${selectedCount} sélectionné${selectedCount > 1 ? "s" : ""}` : ""}
      </p>
      <div className="ob-products">
        {productsLoading || (isWebsite && analyzing && products.length === 0) ? (
          <div className="ob-summary-loading">
            <span className="sg-spinner sm" /> {isWebsite ? "Lecture des pages de votre site…" : "Récupération de vos produits Shopify…"}
          </div>
        ) : products.length === 0 ? (
          <p className="ob-field-hint">
            {isWebsite
              ? "Aucune page détectée automatiquement. Ajoutez un lien (services, à propos, offre…) ci-dessous."
              : "Aucun produit détecté automatiquement. Ajoutez-en manuellement ci-dessous."}
          </p>
        ) : (
          <div className="ob-product-chips">
            {products.map((p, i) =>
              p.manual ? (
                <div key={`m-${i}`} className="ob-product-manual">
                  <input
                    className="ob-persona-name"
                    placeholder={isWebsite ? "Nom de la page ou de l'offre" : "Nom du produit"}
                    value={p.name}
                    onChange={(e) => updateManual(i, "name", e.target.value)}
                  />
                  <input
                    className="ob-persona-desc"
                    placeholder={isWebsite ? "https://… (lien de la page)" : "https://… (lien du produit)"}
                    value={p.url}
                    onChange={(e) => updateManual(i, "url", e.target.value)}
                  />
                  <button type="button" className="ob-product-remove" onClick={() => removeProduct(i)} aria-label="Retirer">✕</button>
                </div>
              ) : (
                <button
                  key={`${p.url}-${i}`}
                  type="button"
                  className={`ob-product-chip${p.selected ? " is-on" : ""}`}
                  onClick={() => toggleProduct(i)}
                >
                  {p.selected ? <Icon name="check" size={13} /> : <Icon name="plus" size={13} />}
                  <span>{p.name}</span>
                </button>
              ),
            )}
          </div>
        )}
        <button type="button" className="ob-product-add" onClick={addManualProduct}>
          <Icon name="plus" size={14} /> {isWebsite ? "Ajouter une page (lien + nom)" : "Ajouter un produit (lien + nom)"}
        </button>
      </div>

      {err && <div className="ob-error">{err}</div>}

      <div className="ob-foot">
        <span />
        <button className="ob-btn-primary" onClick={confirm} disabled={saving || analyzing}>
          {saving ? (<><span className="sg-spinner sm" /> Enregistrement…</>) : (<>Parfait, on configure <Icon name="arrow" size={16} /></>)}
        </button>
      </div>
    </div>
  );
}

function resizePersonaPhotoFile(file, size = 400, quality = 0.82) {
  return new Promise((resolve, reject) => {
    const url = URL.createObjectURL(file);
    const img = new Image();
    img.onload = () => {
      try {
        const canvas = document.createElement("canvas");
        canvas.width = size;
        canvas.height = size;
        const ctx = canvas.getContext("2d");
        if (!ctx) throw new Error("Canvas indisponible");
        const side = Math.min(img.width, img.height);
        const sx = (img.width - side) / 2;
        const sy = (img.height - side) / 2;
        ctx.drawImage(img, sx, sy, side, side, 0, 0, size, size);
        let dataUrl = canvas.toDataURL("image/jpeg", quality);
        if (dataUrl.length > 400000) dataUrl = canvas.toDataURL("image/jpeg", 0.65);
        resolve(dataUrl);
      } catch (err) {
        reject(err);
      } finally {
        URL.revokeObjectURL(url);
      }
    };
    img.onerror = () => {
      URL.revokeObjectURL(url);
      reject(new Error("Image illisible"));
    };
    img.src = url;
  });
}

function PersonaCard({ persona, onChange, canRemove, onRemove, interestOptions }) {
  const [regen, setRegen] = useOS(false);
  const [regenErr, setRegenErr] = useOS("");
  const [imgBusy, setImgBusy] = useOS(false);
  const [imgErr, setImgErr] = useOS("");
  const fileRef = React.useRef(null);
  const interests = Array.isArray(persona.interests) ? persona.interests : [];
  const interestList = interestOptions && interestOptions.length ? interestOptions : PERSONA_INTERESTS;
  const avatarUrl = persona.customPhotoUrl || null;
  const initials = (persona.name || "?").trim().slice(0, 2).toUpperCase();

  function toggleInterest(label) {
    const has = interests.includes(label);
    const next = has ? interests.filter((x) => x !== label) : [...interests, label];
    onChange({ ...persona, interests: next });
  }

  async function onPhotoFile(e) {
    const file = e.target.files?.[0];
    if (!file) return;
    if (file.size > 8_000_000) {
      setImgErr("Image trop lourde (max 8 Mo).");
      return;
    }
    setImgBusy(true);
    setImgErr("");
    try {
      const dataUrl = await resizePersonaPhotoFile(file);
      onChange({ ...persona, customPhotoUrl: dataUrl });
    } catch {
      setImgErr("Impossible de traiter cette image.");
    } finally {
      setImgBusy(false);
    }
    e.target.value = "";
  }

  async function generateAvatar() {
    if (!(persona.description || "").trim()) {
      setImgErr("Décrivez d'abord le persona dans « Qui est-ce ? ».");
      return;
    }
    setImgBusy(true);
    setImgErr("");
    try {
      const res = await GuyAPI.onboardingGeneratePersonaAvatar({
        name: persona.name || "",
        description: persona.description || "",
      });
      if (res?.url) onChange({ ...persona, customPhotoUrl: res.url });
    } catch (e) {
      setImgErr(e.message || "Génération impossible.");
    } finally {
      setImgBusy(false);
    }
  }

  async function regenerate() {
    setRegen(true);
    setRegenErr("");
    try {
      const res = await GuyAPI.onboardingSuggestPersona({
        name: persona.name || "Client",
        interestOptions: interestList,
        toneOptions: PERSONA_TONES.map((t) => t.label),
      });
      if (res?.persona) {
        onChange({
          ...persona,
          name: res.persona.name || persona.name,
          description: res.persona.description || persona.description,
          tone: res.persona.tone || persona.tone,
          interests:
            Array.isArray(res.persona.interests) && res.persona.interests.length
              ? res.persona.interests
              : interests,
        });
      }
    } catch (e) {
      setRegenErr(e.message || "Génération impossible.");
    } finally {
      setRegen(false);
    }
  }

  return (
    <div className="ob-persona-card">
      <div className="ob-persona-card-head">
        <div className="ob-persona-avatar-block">
          <div className="ob-persona-card-avatar ob-persona-card-avatar--photo">
            {avatarUrl ? (
              <img src={avatarUrl} alt="" className="ob-persona-avatar-img" />
            ) : (
              <span className="ob-persona-avatar-initials">{initials}</span>
            )}
          </div>
          <div className="ob-persona-avatar-actions">
            <input
              ref={fileRef}
              type="file"
              accept="image/*"
              hidden
              onChange={onPhotoFile}
            />
            <button type="button" className="ob-persona-avatar-btn" onClick={() => fileRef.current?.click()}>
              Charger une photo
            </button>
            <button type="button" className="ob-persona-avatar-btn" onClick={generateAvatar} disabled={imgBusy}>
              {imgBusy ? "Génération…" : "Générer 400×400"}
            </button>
            {avatarUrl && (
              <button
                type="button"
                className="ob-persona-avatar-btn ob-persona-avatar-btn--muted"
                onClick={() => onChange({ ...persona, customPhotoUrl: null })}
              >
                Retirer
              </button>
            )}
          </div>
          {imgErr && <div className="ob-error ob-error-inline">{imgErr}</div>}
        </div>
        <div className="ob-persona-card-id">
          <span className="ob-persona-eyebrow">Persona</span>
          <input
            className="ob-persona-card-name"
            placeholder="Nom du persona"
            value={persona.name}
            onChange={(e) => onChange({ ...persona, name: e.target.value })}
          />
        </div>
        {canRemove && (
          <button type="button" className="ob-persona-card-remove" onClick={onRemove} title="Supprimer ce persona" aria-label="Supprimer ce persona">✕</button>
        )}
      </div>

      <div className="ob-persona-section-head">
        <p className="ob-section-label">Qui est-ce ?</p>
        <button type="button" className="ob-persona-regen" onClick={regenerate} disabled={regen}>
          {regen ? <span className="sg-spinner sm" /> : <Icon name="spark" size={13} />}
          {regen ? " Génération…" : " Regénérer avec l'IA"}
        </button>
      </div>
      <textarea
        className="ob-persona-textarea"
        rows={2}
        placeholder="Décrivez ce client cible : âge, centres d'intérêt, attentes…"
        value={persona.description}
        onChange={(e) => onChange({ ...persona, description: e.target.value })}
      />
      {regenErr && <div className="ob-error">{regenErr}</div>}

      <p className="ob-section-label">Ce qu'il/elle cherche vraiment</p>
      <div className="ob-persona-interests">
        {interestList.map((label) => (
          <button
            key={label}
            type="button"
            className={`ob-interest-chip${interests.includes(label) ? " is-on" : ""}`}
            onClick={() => toggleInterest(label)}
          >
            {label}
          </button>
        ))}
      </div>

      <p className="ob-section-label">Ton pour ce persona</p>
      <div className="ob-tone-seg">
        {PERSONA_TONES.map((t) => (
          <button
            key={t.id}
            type="button"
            className={`ob-tone-seg-btn${persona.tone === t.label ? " is-on" : ""}`}
            onClick={() => onChange({ ...persona, tone: t.label })}
          >
            {t.label}
          </button>
        ))}
      </div>
    </div>
  );
}

function PlanPicker({ plans, selectedCode, currentCode, cycle, onCycleChange, onSelect, couponCode, onCouponChange, isWebsite }) {
  const [expanded, setExpanded] = useOS(null);
  const selected = plans.find((p) => p.code === selectedCode);
  const hasCoupon = !!(couponCode || "").trim();

  return (
    <div>
      <p className="ob-plan-intro">
        {isWebsite
          ? "Choisissez votre rythme de publication. Les packs payants sont facturés via Stripe (paiement sécurisé, annulable)."
          : <>Choisissez votre rythme de publication. <b>Les plans payants sont facturés via Shopify Billing</b> (paiement sécurisé, annulable).</>}
        {" "}Le plan Découverte est gratuit sans carte bancaire.
      </p>
      <div className="ob-plan-toggle">
        <button type="button" className={cycle === "yearly" ? "is-active" : ""} onClick={() => onCycleChange("yearly")}>Annuel <span className="ob-plan-save">−17 %</span></button>
        <button type="button" className={cycle === "monthly" ? "is-active" : ""} onClick={() => onCycleChange("monthly")}>Mensuel</button>
      </div>
      <div className="ob-plan-grid">
        {plans.map((plan) => (
          <div
            key={plan.code}
            className={`ob-plan-card-wrap${selectedCode === plan.code ? " is-selected" : ""}`}
          >
            <button
              type="button"
              className={`ob-plan-card${selectedCode === plan.code ? " is-selected" : ""}${plan.highlighted ? " is-highlighted" : ""}`}
              onClick={() => onSelect(plan.code)}
            >
              {plan.highlighted && <span className="ob-plan-badge">Recommandé</span>}
              {currentCode === plan.code && !plan.highlighted && (
                <span className="ob-plan-badge" style={{ background: "#10b981" }}>Actuel</span>
              )}
              <p className="ob-plan-name">{plan.name}</p>
              <p className="ob-plan-kpi"><Icon name="docs" size={14} /> <b>{planArticlesLabel(plan)}</b></p>
              <p className="ob-plan-price">
                {hasCoupon && plan.code !== "discovery" ? "Offert" : planPriceLabel(plan, cycle)}
                {!(hasCoupon && plan.code !== "discovery") && <span className="ob-plan-period"> / mois</span>}
              </p>
              <p className="ob-plan-billing">
                {hasCoupon && plan.code !== "discovery"
                  ? "Code promo — sans paiement"
                  : planBillingNote(plan, cycle)}
              </p>
            </button>
            <button
              type="button"
              className="ob-plan-detail-btn"
              onClick={() => setExpanded(expanded === plan.code ? null : plan.code)}
            >
              {expanded === plan.code ? "Masquer le détail" : "Voir le détail"}
            </button>
            {expanded === plan.code && (
              <ul className="ob-plan-features">
                {(plan.features || []).map((feat) => (
                  <li key={feat.label}>
                    <span>{feat.label}</span>
                    <b>{feat.ok ? "✓" : "—"}</b>
                  </li>
                ))}
              </ul>
            )}
          </div>
        ))}
      </div>
      <div className="ob-plan-coupon">
        <label className="ob-section-label" htmlFor="ob-coupon">Code promo</label>
        <input
          id="ob-coupon"
          className="ob-persona-name"
          placeholder="Saisissez votre code"
          value={couponCode || ""}
          onChange={(e) => onCouponChange?.(e.target.value)}
          autoComplete="off"
          spellCheck={false}
        />
        <p className="ob-field-hint">Un code 100 % active le pack choisi sans paiement.</p>
      </div>
      {selected && (
        <p className="ob-plan-selected-note">
          Formule <b>{selected.name}</b> : {planArticlesLabel(selected)}
          {" · "}
          {hasCoupon && selected.code !== "discovery"
            ? "offert avec le code promo"
            : planBillingNote(selected, cycle)}
        </p>
      )}
    </div>
  );
}

function StepPersonasPlan({ personas, plans, currentPlanCode, planAlreadyChosen, onPersonasChange, onNext, onSkip, onBack, isWebsite }) {
  const [selectedPlan, setSelectedPlan] = useOS(currentPlanCode || "pro");
  const [cycle, setCycle] = useOS("yearly");
  const [couponCode, setCouponCode] = useOS("");
  const [active, setActive] = useOS(0);
  const [busy, setBusy] = useOS(false);
  const [error, setError] = useOS("");
  const [autopilot, setAutopilot] = useOS(true);
  const [cpvAccepted, setCpvAccepted] = useOS(false);
  const [autopilotLiabilityAccepted, setAutopilotLiabilityAccepted] = useOS(false);

  const planForLimit = planAlreadyChosen ? currentPlanCode : selectedPlan;
  const maxPersonas = Math.max(1, segmentLimitForPlan(planForLimit, plans));
  const visiblePersonas = personas.slice(0, maxPersonas);
  const activeIdx = Math.min(active, visiblePersonas.length - 1);
  const activePersona = visiblePersonas[activeIdx];

  function updatePersona(i, next) {
    const copy = [...personas];
    copy[i] = next;
    onPersonasChange(copy);
  }

  function addPersona() {
    if (personas.length >= maxPersonas) return;
    const next = [
      ...personas,
      {
        id: `p${Date.now()}`,
        name: "Nouveau persona",
        description: "",
        tone: PERSONA_TONES[0].label,
        interests: [],
        customPhotoUrl: null,
      },
    ];
    onPersonasChange(next);
    setActive(next.length - 1);
  }

  function removePersona(i) {
    if (personas.length <= 1) return;
    const next = personas.filter((_, idx) => idx !== i);
    onPersonasChange(next);
    setActive((cur) => Math.max(0, Math.min(cur, next.length - 1)));
  }

  async function confirm() {
    setError("");
    if (!cpvAccepted) {
      setError("Veuillez accepter les Conditions particulières de vente.");
      return;
    }
    if (autopilot && !autopilotLiabilityAccepted) {
      setError("Veuillez accepter la clause de responsabilité du pilotage automatique.");
      return;
    }
    setBusy(true);
    try {
      const now = new Date().toISOString();
      await GuyAPI.savePublicationSchedule({
        enabled: autopilot,
        timezone: "Europe/Paris",
        cadence: "weekly",
        biweeklyOnEvenIsoWeeks: true,
        slots: defaultAutopilotSlots(planAlreadyChosen ? currentPlanCode : selectedPlan),
        requireReviewBeforePublish: !autopilot,
        publishWithoutReview: autopilot,
        liabilityAcceptedAt: autopilot ? now : null,
        cpvAcceptedAt: now,
        lastProcessedSlotKey: null,
        rhythmCustomized: false,
      });
      const targets = personasToTargets(personas).slice(0, maxPersonas);
      await GuyAPI.saveConfig({ targetAudiences: targets, currentStep: 2 });
      if (!planAlreadyChosen) {
        const planRes = await GuyAPI.selectPlan({
          planCode: selectedPlan,
          cycle,
          couponCode: couponCode.trim() || undefined,
        });
        if (planRes.redirectUrl) {
          window.location.href = planRes.redirectUrl;
          return;
        }
      }
      onNext();
    } catch (e) {
      setError(e.message || "Erreur lors de la confirmation.");
    } finally {
      setBusy(false);
    }
  }

  return (
    <div className="ob-step">
      <div className="ob-bubble-head">
        <img src={POSE.avatar} alt="SEO GUY" className="ob-mascot-sm" />
        <div className="ob-bubble">
          <h2 className="ob-bubble-title">Pour qui rédigez-vous ?</h2>
          <p className="ob-bubble-sub">
            J'ai préparé {visiblePersonas.length} profil{visiblePersonas.length > 1 ? "s" : ""} pour vos articles. Validez ou ajustez — ils seront calibrés pour {isWebsite ? "vos lecteurs" : "vos acheteurs"}.
          </p>
        </div>
      </div>

      <div className="ob-persona-tabs">
        {visiblePersonas.map((p, i) => (
          <button
            key={p.id}
            type="button"
            className={`ob-persona-tab${i === activeIdx ? " is-active" : ""}`}
            onClick={() => setActive(i)}
          >
            {p.customPhotoUrl ? (
              <img src={p.customPhotoUrl} alt="" className="ob-persona-tab-photo" />
            ) : (
              <span className="ob-persona-tab-initials">{(p.name || "?").slice(0, 1).toUpperCase()}</span>
            )}
            <span className="ob-persona-tab-name">{p.name || "Persona"}</span>
          </button>
        ))}
        {visiblePersonas.length < maxPersonas && (
          <button type="button" className="ob-persona-tab ob-persona-tab-add" onClick={addPersona}>
            <Icon name="plus" size={14} /> Ajouter
          </button>
        )}
      </div>

      {activePersona && (
        <PersonaCard
          key={activePersona.id}
          persona={activePersona}
          canRemove={visiblePersonas.length > 1}
          onRemove={() => removePersona(activeIdx)}
          onChange={(next) => updatePersona(activeIdx, next)}
          interestOptions={isWebsite ? PERSONA_INTERESTS_WEBSITE : PERSONA_INTERESTS}
        />
      )}

      {!planAlreadyChosen && (
        <>
          <p className="ob-section-label">Votre formule</p>
          <PlanPicker
            plans={plans}
            selectedCode={selectedPlan}
            currentCode={currentPlanCode}
            cycle={cycle}
            onCycleChange={setCycle}
            onSelect={setSelectedPlan}
            couponCode={couponCode}
            onCouponChange={setCouponCode}
            isWebsite={isWebsite}
          />
        </>
      )}

      <div className="ob-legal">
        <label className="ob-legal-check">
          <input
            type="checkbox"
            checked={autopilot}
            onChange={(e) => {
              const on = e.target.checked;
              setAutopilot(on);
              if (!on) setAutopilotLiabilityAccepted(false);
            }}
          />
          <span>
            <strong>Pilotage automatique</strong>
            <span className="ob-legal-desc">{AUTOPILOT_MODE_DESCRIPTION}</span>
          </span>
        </label>
        {autopilot && (
          <>
            <p className="ob-legal-notice"><strong>Important —</strong> {AUTOPILOT_LIABILITY_NOTICE}</p>
            <label className="ob-legal-check">
              <input
                type="checkbox"
                checked={autopilotLiabilityAccepted}
                onChange={(e) => setAutopilotLiabilityAccepted(e.target.checked)}
              />
              <span>{AUTOPILOT_LIABILITY_CHECKBOX_LABEL}</span>
            </label>
          </>
        )}
        <label className="ob-legal-check">
          <input
            type="checkbox"
            checked={cpvAccepted}
            onChange={(e) => setCpvAccepted(e.target.checked)}
          />
          <span>
            J'accepte les{" "}
            <a href={SEO_GUY_CPV_URL} target="_blank" rel="noopener noreferrer">
              Conditions particulières de vente
            </a>{" "}
            et en ai pris connaissance.
          </span>
        </label>
      </div>

      {error && <div className="ob-error">{error}</div>}

      <div className="ob-foot ob-foot-3">
        <ObBackBtn onBack={onBack} disabled={busy} />
        <span className="ob-foot-counter">2 / 4</span>
        <button
          type="button"
          className="ob-btn-primary"
          onClick={confirm}
          disabled={busy || !cpvAccepted || (autopilot && !autopilotLiabilityAccepted)}
        >
          {busy ? (<><span className="sg-spinner sm" /> Enregistrement…</>) : (<>Continuer <Icon name="arrow" size={16} /></>)}
        </button>
      </div>
    </div>
  );
}

function GscConnectModal({ open, onClose, onSuccess, gscConnected }) {
  const [connecting, setConnecting] = useOS(false);
  const [error, setError] = useOS("");

  async function connect() {
    setConnecting(true);
    setError("");
    try {
      const body = await GuyAPI.gscConnectInit();
      if (!body.authUrl) {
        setError("Connexion indisponible pour le moment.");
        setConnecting(false);
        return;
      }
      const popup = window.open(body.authUrl, "gsc-oauth", "popup,width=520,height=680,scrollbars=yes");
      if (!popup) {
        setError("Popup bloquée — autorisez les popups ou réessayez plus tard.");
        setConnecting(false);
        return;
      }
      const onMessage = (e) => {
        if (typeof e.data === "object" && e.data?.type === "gsc:oauth") {
          window.removeEventListener("message", onMessage);
          setConnecting(false);
          if (e.data.payload?.ok) {
            onSuccess();
            onClose();
          } else {
            setError("Connexion Google non finalisée.");
          }
        }
      };
      window.addEventListener("message", onMessage);
    } catch (e) {
      setConnecting(false);
      setError(e.message || "Erreur de connexion");
    }
  }

  if (!open) return null;

  return (
    <div className="ob-gsc-modal-overlay" onClick={onClose}>
      <div className="ob-gsc-modal" onClick={(e) => e.stopPropagation()}>
        <button type="button" className="ob-gsc-modal-close" onClick={onClose} aria-label="Fermer">✕</button>
        <div className="ob-gsc-modal-head">
          <span className="ob-gsc-logo">G</span>
          <div>
            <h3>Connecter Google Search Console</h3>
            <p>Une fenêtre Google s&apos;ouvre — vous pouvez fermer cette pop-up et continuer l&apos;onboarding même si ça échoue.</p>
          </div>
        </div>
        {gscConnected && <div className="ob-gsc-note ok">GSC déjà connecté sur ce site.</div>}
        {error && <div className="ob-error">{error}</div>}
        <div className="ob-gsc-modal-actions">
          <button type="button" className="ob-btn-ghost" onClick={onClose}>Fermer et continuer sans GSC</button>
          <button type="button" className="ob-btn-primary" onClick={() => (gscConnected ? onSuccess() : connect())} disabled={connecting}>
            {connecting ? (<><span className="sg-spinner sm" /> Connexion…</>) : (gscConnected ? "Continuer" : (<><Icon name="globe" size={15} /> Ouvrir Google</>))}
          </button>
        </div>
      </div>
    </div>
  );
}

function StepGSC({ onNext, onSkip, onBack, gscConnected }) {
  const [modalOpen, setModalOpen] = useOS(false);

  return (
    <div className="ob-step">
      <div className="ob-step-head">
        <span className="ob-gsc-logo">G</span>
        <div>
          <h2 className="ob-h2">Google Search Console (optionnel)</h2>
          <p className="ob-sub">Recommandé pour des mots-clés réels — vous pouvez passer cette étape.</p>
        </div>
      </div>

      {gscConnected && (
        <div className="ob-gsc-note ok">Google Search Console est déjà connecté.</div>
      )}

      <div className="ob-gsc-benefits">
        {[
          { icon: "chart", text: "Requêtes réelles qui amènent du trafic" },
          { icon: "target", text: "Mots-clés cachés à cibler" },
          { icon: "trending", text: "Suivi des positions" },
        ].map((b) => (
          <div key={b.icon} className="ob-benefit">
            <span className="ob-benefit-ic"><Icon name={b.icon} size={16} /></span>
            <p>{b.text}</p>
          </div>
        ))}
      </div>

      <div className="ob-foot">
        <ObBackBtn onBack={onBack} />
        <div className="ob-foot-right">
          <button type="button" className="ob-btn-ghost" onClick={onSkip}>Continuer sans GSC</button>
          <button type="button" className="ob-btn-primary" onClick={() => setModalOpen(true)}>
            <Icon name="globe" size={15} /> Connecter Google
          </button>
        </div>
      </div>

      <GscConnectModal
        open={modalOpen}
        onClose={() => setModalOpen(false)}
        onSuccess={onNext}
        gscConnected={gscConnected}
      />
    </div>
  );
}

function StepFirstArticle({ article, loading, error, onPublish, onDashboard, onBack, quotaReached }) {
  const failed = !!error && !article && !loading && !quotaReached;
  return (
    <div className="ob-step ob-step-finale">
      <div className="ob-finale-mascot">
        <img src={POSE.mission} alt="SEO GUY" className="ob-mascot-big" />
      </div>
      <h1 className="ob-h1">
        {quotaReached
          ? "Configuration terminée"
          : failed
            ? "Le premier article n'a pas pu être généré"
            : "🎉 Votre premier article est prêt !"}
      </h1>
      <p className="ob-sub">
        {quotaReached
          ? "Merci d'avoir réalisé la configuration, vous avez déjà généré l'ensemble des articles pour cette semaine."
          : failed
            ? "Vous pourrez le lancer depuis le tableau de bord une fois le point ci-dessous réglé."
            : "J'ai rédigé, optimisé et illustré votre premier article. Il n'attend que vous."}
      </p>

      {loading && !quotaReached && (
        <p className="ob-sub"><span className="sg-spinner sm" /> SEO GUY rédige votre premier article…</p>
      )}
      {error && !quotaReached && <div className="ob-error">{error}</div>}

      {quotaReached ? (
        <div className="ob-art-preview" style={{ textAlign: "center" }}>
          <p className="ob-sub" style={{ margin: 0 }}>
            Votre prochain créneau de génération s&apos;ouvrira au début de la semaine prochaine.
            En attendant, vous pouvez ajuster vos réglages depuis le tableau de bord.
          </p>
        </div>
      ) : article ? (
        <div className="ob-art-preview">
          <span className="sg-next-eyebrow"><Icon name="spark" size={13} /> Prêt à publier</span>
          <h3 className="ob-art-title">{article.title}</h3>
          <div className="ob-art-meta">
            <span><b>Mot-clé</b> {article.keyword}</span>
            {article.volume && <span><b>Recherches</b> {article.volume}</span>}
            <span><b>Lecture</b> {article.readMin || 5} min</span>
          </div>
          {article.score && (
            <div className="ob-art-score">
              <ScoreRing value={article.score} size={52} />
              <div>
                <p className="ob-score-label">Score SEO</p>
                <p className="ob-score-val">Excellent</p>
                <SeoScoreHumanNote />
              </div>
            </div>
          )}
          {article.intro && <p className="ob-art-intro">{article.intro}</p>}
        </div>
      ) : (
        !error && (
          <div className="ob-art-preview ob-art-preview--skeleton">
            <span className="sg-next-eyebrow"><Icon name="spark" size={13} /> Rédaction en cours</span>
            <div className="ob-skel-line lg" />
            <div className="ob-skel-line" />
            <div className="ob-skel-line sm" />
          </div>
        )
      )}

      <div className="ob-foot ob-foot-finale">
        <ObBackBtn onBack={onBack} />
        <div className="ob-foot-right">
          {quotaReached ? (
            <button type="button" className="ob-btn-primary lg" onClick={onDashboard}>
              Tableau de bord
            </button>
          ) : (
            <>
              <button type="button" className="ob-btn-ghost" onClick={onDashboard}>Tableau de bord</button>
              <button type="button" className="ob-btn-primary lg" onClick={onPublish} disabled={loading || !article?.raw?.id}>
                <Icon name="eye" size={16} /> Voir l&apos;article
              </button>
            </>
          )}
        </div>
      </div>
    </div>
  );
}

function OnboardingQuitDialog({ open, busy, onCancel, onSaveQuit, onDiscardQuit }) {
  if (!open) return null;
  return (
    <div className="ob-quit-overlay" onClick={busy ? undefined : onCancel} role="presentation">
      <div
        className="ob-quit-dialog"
        role="dialog"
        aria-modal="true"
        aria-labelledby="ob-quit-title"
        onClick={(e) => e.stopPropagation()}
      >
        <h3 id="ob-quit-title">Quitter la configuration ?</h3>
        <p>Voulez-vous enregistrer vos modifications avant de quitter ?</p>
        <div className="ob-quit-actions">
          <button type="button" className="ob-btn-primary" onClick={onSaveQuit} disabled={busy}>
            {busy ? (<><span className="sg-spinner sm" /> Enregistrement…</>) : "Enregistrer et quitter"}
          </button>
          <button type="button" className="ob-btn-ghost" onClick={onDiscardQuit} disabled={busy}>
            Quitter sans enregistrer
          </button>
          <button type="button" className="ob-btn-ghost ob-quit-cancel" onClick={onCancel} disabled={busy}>
            Annuler
          </button>
        </div>
      </div>
    </div>
  );
}

function WordpressConnectStep({
  suggestedUrl = "",
  initialUsername = "",
  onConnected,
  onSkip,
}) {
  const [baseUrl, setBaseUrl] = useOS(suggestedUrl || "");
  const [username, setUsername] = useOS(initialUsername || "");
  const [appPassword, setAppPassword] = useOS("");
  const [busy, setBusy] = useOS(false);
  const [error, setError] = useOS("");
  const [message, setMessage] = useOS("");

  useOE(() => {
    if (!baseUrl && suggestedUrl) setBaseUrl(suggestedUrl);
  }, [suggestedUrl]);

  async function run(action) {
    setError("");
    setMessage("");
    setBusy(true);
    try {
      const payload = {
        baseUrl: baseUrl.trim(),
        username: username.trim(),
        appPassword: appPassword.trim(),
      };
      const res = action === "test"
        ? await GuyAPI.testWordpressConnection(payload)
        : await GuyAPI.saveWordpressConnection(payload);
      const n = res?.categories?.length ?? 0;
      if (action === "test") {
        setMessage(n ? `Connexion OK · ${n} catégorie(s)` : "Connexion OK");
        return;
      }
      onConnected?.(res);
    } catch (e) {
      setError(e.message || "Connexion WordPress impossible.");
    } finally {
      setBusy(false);
    }
  }

  return (
    <div className="ob-shell">
      <div className="ob-bg" />
      <div className="ob-card">
        <div className="ob-card-top">
          <p className="ob-wp-kicker">Étape 0 · WordPress</p>
        </div>
        <div className="ob-step">
          <div className="ob-bubble-head">
            <img src={POSE.avatar} alt="SEO GUY" className="ob-mascot-sm" />
            <div className="ob-bubble">
              <h2 className="ob-bubble-title">Connectons votre WordPress</h2>
              <p className="ob-bubble-sub">
                Installez le plugin, générez un mot de passe d’application, puis collez-le ici.
                Guy rédigera ; WordPress publiera.
              </p>
            </div>
          </div>

          <ol className="ob-wp-steps">
            <li>
              Téléchargez{" "}
              <a href={WP_PLUGIN_ZIP_URL} download="assistant-guy.zip">assistant-guy.zip</a>
              {" "}puis Extensions → Téléverser → Activer.
            </li>
            <li>Menu <strong>Assistant Guy</strong> → générer le mot de passe d’application (compte Éditeur).</li>
            <li>Collez URL + identifiant + secret ci-dessous. Tester, puis Enregistrer.</li>
          </ol>

          <label className="ob-section-label" htmlFor="ob-wp-url">URL du site</label>
          <input
            id="ob-wp-url"
            className="ob-persona-name"
            type="url"
            placeholder="https://votresite.com"
            value={baseUrl}
            onChange={(e) => setBaseUrl(e.target.value)}
            disabled={busy}
            autoComplete="off"
          />
          <label className="ob-section-label" htmlFor="ob-wp-user">Identifiant WordPress</label>
          <input
            id="ob-wp-user"
            className="ob-persona-name"
            type="text"
            placeholder="éditeur (pas forcément l’e-mail)"
            value={username}
            onChange={(e) => setUsername(e.target.value)}
            disabled={busy}
            autoComplete="username"
          />
          <label className="ob-section-label" htmlFor="ob-wp-pass">Mot de passe d’application</label>
          <input
            id="ob-wp-pass"
            className="ob-persona-name"
            type="password"
            placeholder="xxxx xxxx xxxx xxxx"
            value={appPassword}
            onChange={(e) => setAppPassword(e.target.value)}
            disabled={busy}
            autoComplete="new-password"
          />
          <p className="ob-field-hint">
            Ce n’est pas le mot de passe de connexion WordPress. Il ne s’affiche qu’une fois.
          </p>

          {message && <p className="ob-wp-ok">{message}</p>}
          {error && <div className="ob-error">{error}</div>}

          <div className="ob-foot ob-foot-3">
            <button type="button" className="ob-btn-ghost" onClick={onSkip} disabled={busy}>
              Plus tard — Guy hébergera les articles
            </button>
            <span className="ob-foot-counter">Plugin</span>
            <div className="ob-wp-actions">
              <button type="button" className="ob-btn-ghost" onClick={() => run("test")} disabled={busy || !baseUrl.trim() || !username.trim() || !appPassword.trim()}>
                {busy ? "…" : "Tester"}
              </button>
              <button type="button" className="ob-btn-primary" onClick={() => run("save")} disabled={busy || !baseUrl.trim() || !username.trim() || !appPassword.trim()}>
                {busy ? (<><span className="sg-spinner sm" /> Enregistrement…</>) : (<>Enregistrer <Icon name="arrow" size={16} /></>)}
              </button>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

function OnboardingFlow({
  onComplete,
  onExit,
  onOpenReview,
  onStepChange,
  initialStep = 1,
  insights,
  plans,
  currentPlanCode,
  onboardingCompleted = false,
  featuredProducts: featuredProductsSeed = [],
  businessSummary: businessSummarySeed = "",
  personas: personasSeed,
  keywords = [],
  gscConnected = false,
  quota = null,
  isWebsite = false,
}) {
  const [step, setStep] = useOS(initialStep);
  const [personas, setPersonas] = useOS(() =>
    defaultPersonasFromAudiences(personasSeed || []),
  );
  const [businessSummary, setBusinessSummary] = useOS(businessSummarySeed || "");
  const [featuredProducts, setFeaturedProducts] = useOS(featuredProductsSeed || []);
  const [articlePreview, setArticlePreview] = useOS(null);
  const [articleLoading, setArticleLoading] = useOS(false);
  const [articleError, setArticleError] = useOS("");
  const [quitOpen, setQuitOpen] = useOS(false);
  const [quitBusy, setQuitBusy] = useOS(false);
  const articleTriggeredRef = React.useRef(false);

  const planAlreadyChosen =
    !!onboardingCompleted || (!!currentPlanCode && currentPlanCode !== "discovery");

  const quotaReached = !!(quota && !quota.unlimited && !quota.canGenerate);

  const targetStrings = useOM(
    () => personasToTargets(personas).map((t) => t.audience),
    [personas],
  );

  function goTo(s) {
    setStep(s);
    onStepChange?.(s);
    GuyAPI.saveConfig({ currentStep: s - 1 }).catch(() => undefined);
  }

  function next() { goTo(Math.min(step + 1, 4)); }
  function skip() { goTo(Math.min(step + 1, 4)); }
  function back() { goTo(Math.max(step - 1, 1)); }

  async function confirmAnalyse({ summary, products }) {
    setBusinessSummary(summary || "");
    setFeaturedProducts(products || []);
    const patch = { currentStep: 1 };
    if (summary) patch.editorialCharter = summary;
    if (Array.isArray(products)) patch.featuredProducts = products;
    await GuyAPI.saveConfig(patch);
    // Seed IA en arrière-plan : ~30 mots-clés générés depuis la description
    // puis enrichis Ranxplorer (idempotent côté serveur). Les tops GSC s'y
    // ajouteront à l'étape suivante si la GSC est connectée.
    GuyAPI.onboardingSeedKeywords({ description: summary || undefined }).catch(
      () => undefined,
    );
    next();
  }

  async function finish(publish) {
    await GuyAPI.saveConfig({ onboardingCompleted: true, currentStep: 4 }).catch(() => undefined);
    onComplete();
    if (publish) onOpenReview(articlePreview || PROPOSED_QUEUE[0]);
  }

  async function saveAndQuit() {
    setQuitBusy(true);
    try {
      const patch = {
        currentStep: Math.max(0, step - 1),
        featuredProducts: featuredProducts || [],
      };
      if (businessSummary) patch.editorialCharter = businessSummary;
      const targets = personasToTargets(personas);
      if (targets.length) patch.targetAudiences = targets;
      await GuyAPI.saveConfig(patch);
      setQuitOpen(false);
      onExit?.();
    } catch (e) {
      setQuitBusy(false);
      window.alert(e.message || "Enregistrement impossible.");
    }
  }

  function discardAndQuit() {
    setQuitOpen(false);
    onExit?.();
  }

  useOE(() => {
    if (step !== 4 || articleTriggeredRef.current) return;
    articleTriggeredRef.current = true;

    // Quota atteint : on finalise la config sans générer d'article.
    if (quotaReached) {
      setArticleLoading(false);
      setArticlePreview(null);
      setArticleError("");
      return;
    }

    setArticleLoading(true);
    setArticleError("");

    (async () => {
      try {
        let featured = (featuredProducts || []).slice(0, 5);
        if (!featured.length) {
          const productsRes = await GuyAPI.shopifyProducts().catch(() => ({ products: [] }));
          featured = (productsRes.products || []).slice(0, 5).map((p) => {
            const row = { name: p.title, url: p.url };
            if (p.imageUrl) row.imageUrl = p.imageUrl;
            if (p.id) row.shopifyProductId = p.id;
            return row;
          });
        }

        // Liste fraîche depuis l'API : la connexion GSC (étape précédente)
        // importe automatiquement les tops requêtes en mots-clés favoris, que
        // le bootstrap chargé avant l'onboarding ne contient pas encore.
        let kwSource = keywords;
        try {
          const fresh = await GuyAPI.keywordsList();
          if (fresh.keywords?.length) kwSource = fresh.keywords;
        } catch {
          // On retombe sur les mots-clés du bootstrap.
        }

        const kws = kwSource.length
          ? kwSource.slice(0, 20).map((k) => ({
              keyword: k.keyword,
              volume: k.volume ?? 0,
              favorite: true,
            }))
          : insights?.topKeyword
            ? [{ keyword: insights.topKeyword.keyword, volume: insights.topKeyword.volume ?? 0, favorite: true }]
            : [{ keyword: "guide achat", volume: 0, favorite: true }];

        const titlesRes = await GuyAPI.onboardingPreviewTitles({
          count: 3,
          keywords: kws,
          targets: targetStrings.length ? targetStrings : undefined,
          precautions: businessSummary
            ? [businessSummary, "Respecter les précautions de rédaction standard."]
            : ["Respecter les précautions de rédaction standard."],
          featuredProducts: featured,
        });

        const chosen = titlesRes.titles?.[0];
        if (!chosen) throw new Error("Aucun titre généré pour ce site.");

        const defer = await GuyAPI.onboardingDeferArticle({
          title: chosen.title,
          keywords: chosen.keywords,
          persona: chosen.persona || "",
        });

        const article = await GuyAPI.pollArticleReady(defer.articleId);
        const plain = (article.bodyHtml || "").replace(/<[^>]+>/g, " ").trim();

        setArticlePreview({
          id: article.id,
          title: article.title || chosen.title,
          keyword: chosen.keywords,
          volume: "",
          readMin: 5,
          score: 92,
          intro: plain.slice(0, 220),
          bodyHtml: article.bodyHtml,
          imageUrl: article.imageUrl,
          raw: { id: article.id, title: article.title, bodyHtml: article.bodyHtml, imageUrl: article.imageUrl },
        });
      } catch (e) {
        // Pas de fallback démo : afficher l'erreur réelle (quota, IA, etc.)
        // plutôt qu'un faux article qui sème la confusion.
        setArticleError(e.message || "Génération impossible.");
        setArticlePreview(null);
      } finally {
        setArticleLoading(false);
      }
    })();
  }, [step, keywords, insights, targetStrings, quotaReached]);

  const wide = step === 2 && !planAlreadyChosen;

  return (
    <div className="ob-shell">
      <div className="ob-bg" />
      <div className={`ob-card${wide ? " ob-card-wide" : ""}`}>
        <div className="ob-card-top">
          <OnboardProgress step={step} />
          <button
            type="button"
            className="ob-quit-btn"
            onClick={() => setQuitOpen(true)}
            aria-label="Quitter la configuration"
          >
            Quitter
          </button>
        </div>
        {step === 1 && (
          <StepAnalyse
            insights={insights || {}}
            onConfirm={confirmAnalyse}
            initialSummary={businessSummary}
            initialProducts={featuredProducts}
            quotaReached={quotaReached}
            quota={quota}
            isWebsite={isWebsite}
          />
        )}
        {step === 2 && (
          <StepPersonasPlan
            personas={personas}
            plans={plans || PLANS}
            currentPlanCode={currentPlanCode}
            planAlreadyChosen={planAlreadyChosen}
            onPersonasChange={setPersonas}
            onNext={next}
            onSkip={skip}
            onBack={back}
            isWebsite={isWebsite}
          />
        )}
        {step === 3 && <StepGSC onNext={next} onSkip={skip} onBack={back} gscConnected={gscConnected} />}
        {step === 4 && (
          <StepFirstArticle
            article={articlePreview}
            loading={articleLoading}
            error={articleError}
            quotaReached={quotaReached}
            onBack={back}
            onPublish={() => finish(true)}
            onDashboard={() => finish(false)}
          />
        )}
      </div>
      <OnboardingQuitDialog
        open={quitOpen}
        busy={quitBusy}
        onCancel={() => !quitBusy && setQuitOpen(false)}
        onSaveQuit={saveAndQuit}
        onDiscardQuit={discardAndQuit}
      />
    </div>
  );
}

function OnboardingResumeBanner({ step, onResume }) {
  const next = OB_RESUME_LABELS[step - 1] || OB_RESUME_LABELS[3];
  return (
    <div className="ob-resume">
      <span className="ob-resume-ic"><Icon name="bolt" size={16} /></span>
      <div className="ob-resume-txt">
        <b>Votre configuration n'est pas terminée.</b>
        <span> Prochaine étape : {next}</span>
      </div>
      <button className="sg-btn-primary sm" onClick={onResume}>Reprendre <Icon name="arrow" size={14} /></button>
    </div>
  );
}

Object.assign(window, { OnboardingFlow, OnboardingResumeBanner, WordpressConnectStep });
