/* app/onboarding.jsx — le parcours d'onboarding, une question par écran.
 *
 * Le client le remplit seul (chapitres 1 à 5) avant la visio ; Julia voit les mêmes écrans en
 * admin, avec les détails techniques repliés, puis les chapitres 6 et 7 (épreuve du réel,
 * lancement) qui se font en visio. Chaque changement est enregistré tout seul dans le brouillon
 * du compte (table Clients moteur), via /api/onboarding, action « brouillon ».
 *
 * Règles d'expérience (retour de Julia du 11/10/2026) : une seule chose à décider par écran,
 * pré-remplir puis laisser corriger, un bouton principal, « Enregistré » visible, aucun
 * identifiant technique côté client.
 */

const { useState, useEffect, useRef, useMemo, useCallback } = React;
const { ArrowUpRight } = window.BMUI;

const CALENDLY_OB = 'https://calendly.com/julia-boommaker/30min';
const TAILLES = ['Solo', '1 à 10', '10 à 50', '50 à 250', '250 et +'];
const COINCE = ['Les trouver', 'Les contacter', 'Traiter les réponses', 'Les closer'];
const REGISTRES = [
  ['Tutoiement décideurs, vouvoiement sinon', 'Tu avec les dirigeants, vous avec les autres'],
  ['Vouvoiement systématique', 'Vous, toujours'],
  ['Tutoiement systématique', 'Tu, toujours'],
];
const OBJECTIFS = ['Rendez-vous commerciaux', 'Candidats franchisés', 'Démos produit', 'Prescripteurs et partenaires', 'Recrutement', 'Autre'];
const CANAUX = [
  ['Multicanal (email + LinkedIn)', 'Email et LinkedIn', 'Le plus efficace : chaque prospect est touché sur les deux.'],
  ['LinkedIn seul', 'LinkedIn seulement', "Si ta cible ne lit pas ses emails ou si tu n'as pas de boîte dédiée."],
  ['Email seul', 'Email seulement', 'Si tu ne veux pas utiliser ton compte LinkedIn.'],
];

const FICHE_VIDE = {
  client: '', site: '', description: '', signataire: '', registre: REGISTRES[0][0],
  cible: '', promesse: '', zone: 'France', meilleur_client: '', taille: '', manque: '', coince: '',
  qui_decide: '', declencheur: '',
  offres: '', chiffres: '', cas: '', interdits: '', vocabulaire: '', bannis: '',
  objectif: OBJECTIFS[0], apport: '', canal: CANAUX[0][0], volume: 50, statut: 'Brouillon', voix: '', prix: '',
};

const lignesOb = (t) => String(t || '').split('\n').map((x) => x.trim()).filter(Boolean);
const nombre = (n) => new Intl.NumberFormat('fr-FR').format(n);

async function apiOb(action, corps = {}) {
  const r = await fetch('/api/onboarding', {
    method: 'POST', credentials: 'same-origin',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ action, ...corps }),
  });
  const data = await r.json().catch(() => ({}));
  if (!r.ok) { const e = new Error(data.message || data.error || `Erreur ${r.status}`); e.status = r.status; e.code = data.error; throw e; }
  return data;
}

/* ---- Les écrans : un par question ---------------------------------------------------- */

const CHAPITRES = [
  { n: 1, titre: 'Toi et ton entreprise' },
  { n: 2, titre: 'Ce que tu vends' },
  { n: 3, titre: 'Ton client idéal' },
  { n: 4, titre: 'Ton objectif' },
  { n: 5, titre: 'Tes mots' },
  { n: 6, titre: "L'épreuve du réel", admin: true },
  { n: 7, titre: 'Le lancement', admin: true },
];

const ECRANS = [
  { id: 'site', ch: 1, titre: 'Commençons par ton site.', aide: 'On le lit pour pré-remplir tout ce qui suit. Ajoute si tu veux une plaquette ou une grille tarifaire en PDF : on y prend tes offres, tes prix et ta façon de parler. Ensuite, tu corriges au lieu de rédiger.', ok: () => true },
  { id: 'entreprise', ch: 1, titre: 'Ton entreprise, en deux phrases.', aide: 'Son nom tel que tes clients le disent, et ce que tu fais, sans slogan.', ok: (f) => !!f.client },
  { id: 'signataire', ch: 1, titre: 'Qui signe les messages ?', aide: 'Les messages partent en son nom, à la première personne. Dis qui c\'est et pourquoi on l\'écoute.', ok: (f) => !!f.signataire },
  { id: 'madlib', ch: 2, titre: 'Tu aides qui, à quoi ?', aide: 'La phrase la plus importante du parcours. Au pluriel pour la cible, à l\'infinitif pour le bénéfice.', ok: (f) => !!f.cible && !!f.promesse && !!f.zone },
  { id: 'offres', ch: 2, titre: 'Tes offres, dites simplement.', aide: 'Une ligne par offre : ce que le client obtient, jamais comment c\'est fait. Les messages ne connaîtront de ton offre que ce qui est écrit ici.', ok: (f) => lignesOb(f.offres).length > 0 },
  { id: 'preuves', ch: 2, titre: 'Ce qu\'on a le droit de dire.', aide: 'Les chiffres et les cas clients que les messages peuvent citer, avec leur formulation exacte. Rien d\'autre ne sera inventé.', ok: () => true, optionnel: true },
  { id: 'meilleur', ch: 3, titre: 'Décris ton meilleur client.', aide: 'Celui que tu voudrais dix fois. Ce que toi seul sais de lui : c\'est le point d\'ancrage du ciblage.', ok: (f) => !!f.meilleur_client && !!f.taille },
  { id: 'besoin', ch: 3, titre: 'Qu\'est-ce qui te manque aujourd\'hui ?', aide: 'Et à quelle étape ça coince. Le parcours attaque cette étape, pas celle qu\'on suppose.', ok: (f) => !!f.manque && !!f.coince },
  { id: 'decideur', ch: 3, titre: 'Qui décide chez tes clients, et quand ?', aide: 'Deux hypothèses pré-remplies depuis ton site. Corrige si tu sais mieux, c\'est souvent le cas.', ok: () => true, optionnel: true },
  { id: 'cibles', ch: 3, titre: 'Tes cercles de cibles.', aide: 'À partir de tout ce que tu as dit, on calcule les cercles d\'entreprises à viser en premier, et on compte combien il y en a en France.', ok: (f, s) => (s.cartes || []).some((c) => c.actif !== false) },
  { id: 'objectif', ch: 4, titre: 'Qu\'est-ce que la prospection doit obtenir ?', aide: 'Et ce que le prospect gagne en acceptant. C\'est ce qui fait tenir la demande de rendez-vous.', ok: (f) => !!f.objectif && !!f.apport },
  { id: 'canal', ch: 4, titre: 'Par où on les contacte ?', aide: 'Jusqu\'à cinquante nouveaux prospects par semaine, le volume est réglé pour toi.', ok: (f) => !!f.canal },
  { id: 'mots', ch: 5, titre: 'Les mots de ton métier.', aide: 'Les messages parlent du marché du prospect avec ses mots. Pré-rempli depuis ton site, un mot ou une expression par ligne.', ok: () => true },
  { id: 'voix', ch: 5, titre: 'Comment tu parles.', aide: 'Trois à cinq phrases sur ton ton : tu ou vous, phrases courtes ou longues, humour ou pas, les mots que tu répètes. Pré-rempli depuis ton site et tes documents. Le rédacteur s\'y tient.', ok: () => true, optionnel: true },
  { id: 'interdits', ch: 5, titre: 'Ce qu\'on ne doit jamais écrire.', aide: 'Des formules, des sujets, des promesses qui te feraient grincer. Le relecteur les bloque mécaniquement.', ok: () => true, optionnel: true },
  { id: 'fin', ch: 5, titre: 'C\'est tout pour toi.', aide: '', ok: () => true },
  { id: 'epreuve', ch: 6, titre: "L'épreuve du réel.", aide: 'Trois prospects réels, trois séquences écrites en direct, notées par le relecteur. Ce que tu valides avec le client devient ses exemples de voix.', ok: () => true, admin: true },
  { id: 'lancement', ch: 7, titre: 'Le lancement.', aide: 'La fiche et les cartes partent dans la base du client, puis le premier lot de dix prospects est prélevé et écrit. Le client le validera dans son espace.', ok: () => true, admin: true },
];

/* ---- Petits composants ---------------------------------------------------------------- */

const Champ = ({ label, aide, children }) => (
  <div className="ob-field">
    {label ? <label>{label}</label> : null}
    {children}
    {aide ? <small>{aide}</small> : null}
  </div>
);

const Choix = ({ options, valeur, onChange, descriptions }) => (
  <div className="ob-choix">
    {options.map((o) => {
      const [v, l, d] = Array.isArray(o) ? o : [o, o, null];
      return (
        <button key={v} type="button" className={'ob-choix-item' + (valeur === v ? ' on' : '')} onClick={() => onChange(v)}>
          <span className="ob-choix-l">{l}</span>
          {d ? <span className="ob-choix-d">{d}</span> : null}
        </button>
      );
    })}
  </div>
);

/* Des réponses prêtes à cliquer, déduites du site. `ajouter` : on empile au lieu de remplacer. */
const Suggestions = ({ liste, onChoisir, ajouter }) => {
  if (!Array.isArray(liste) || !liste.length) return null;
  return (
    <div className="ob-sugg">
      <span className="ob-sugg-l">{ajouter ? 'Ajouter :' : 'Suggestions :'}</span>
      {liste.map((t, i) => <button key={i} type="button" className="ob-sugg-item" onClick={() => onChoisir(t)}>{t}</button>)}
    </div>
  );
};

const Attente = ({ texte, etapes }) => {
  const [i, setI] = useState(0);
  useEffect(() => {
    if (!etapes?.length) return undefined;
    const t = setInterval(() => setI((x) => Math.min(x + 1, etapes.length - 1)), 14000);
    return () => clearInterval(t);
  }, [etapes]);
  return <div className="ob-wait"><span className="ob-spin" aria-hidden="true" /><span>{etapes?.length ? etapes[i] : texte}</span></div>;
};

const Score = ({ note }) => {
  const c = note >= 8 ? 'bon' : note >= 6 ? 'moyen' : 'faible';
  return <span className={'ob-score ' + c}>{Number(note).toFixed(1)}<small>/10</small></span>;
};

const CHAMPS_SEQ = [
  ['Seq_Email1_Objet', 'Email 1, objet', 'J1', true], ['Seq_Email1_Corps', 'Email 1', 'J1', false],
  ['Seq_Email2_Objet', 'Email 2, objet', 'J5', true], ['Seq_Email2_Corps', 'Email 2', 'J5', false],
  ['Seq_Email3_Objet', 'Email 3, objet', 'J12', true], ['Seq_Email3_Corps', 'Email 3', 'J12', false],
  ['Seq_LI_Msg1', 'LinkedIn 1', 'J3', false], ['Seq_LI_Msg2', 'LinkedIn 2', 'J8', false], ['Seq_LI_Msg3', 'LinkedIn 3', 'J16', false],
];
const motsOb = (t) => String(t || '').split(/[^\wÀ-ÿ'-]+/).filter(Boolean).length;

/* ---- Le parcours ------------------------------------------------------------------------ */

function Onboarding({ compte, brouillon: initial, admin, onSortie, onChangement }) {
  const debloque = !!compte?.etape6Le;
  const ecransVisibles = ECRANS.filter((e) => !e.admin || admin);
  const [fiche, setFiche] = useState({ ...FICHE_VIDE, ...(initial?.fiche || {}), site: initial?.fiche?.site || compte?.siteWeb || '' });
  const [cartes, setCartes] = useState(initial?.cartes || []);
  const [ciblesInfo, setCiblesInfo] = useState(initial?.ciblesInfo || null);
  const [prospects, setProspects] = useState(initial?.prospects || []);
  const [tests, setTests] = useState(initial?.tests || {});
  const [exemples, setExemples] = useState(initial?.exemples || []);
  const [suggestions, setSuggestions] = useState(initial?.suggestions || {});
  const [documents, setDocuments] = useState(compte?.documents || []);
  const [envoiDoc, setEnvoiDoc] = useState('');
  const [carteTest, setCarteTest] = useState(initial?.carteTest || 0);
  const [ecranId, setEcranId] = useState(() => {
    const voulu = initial?.ecran;
    return ecransVisibles.some((e) => e.id === voulu) ? voulu : ecransVisibles[0].id;
  });
  const [enCours, setEnCours] = useState('');
  const [erreur, setErreur] = useState('');
  const [info, setInfo] = useState('');
  const [sauvegarde, setSauvegarde] = useState('');
  const [lanceDetail, setLanceDetail] = useState(null);
  const premierRendu = useRef(true);

  const idx = ecransVisibles.findIndex((e) => e.id === ecranId);
  const ecran = ecransVisibles[idx];
  const chapitre = CHAPITRES.find((c) => c.n === ecran.ch);
  const ecransDuChapitre = ecransVisibles.filter((e) => e.ch === ecran.ch);
  const posDansChapitre = ecransDuChapitre.findIndex((e) => e.id === ecranId);
  const chapitresVisibles = CHAPITRES.filter((c) => !c.admin || admin);
  const etat = { cartes };
  const peutContinuer = ecran.ok(fiche, etat);

  /* Enregistrement automatique, 1,2 s après le dernier changement. */
  const brouillonCourant = useMemo(() => ({ fiche, cartes, ciblesInfo, prospects, tests, exemples, suggestions, carteTest, ecran: ecranId, etape: Math.max(initial?.etape || 1, ecran.ch) }),
    [fiche, cartes, ciblesInfo, prospects, tests, exemples, suggestions, carteTest, ecranId]);
  useEffect(() => {
    if (premierRendu.current) { premierRendu.current = false; return undefined; }
    setSauvegarde('en_cours');
    const t = setTimeout(async () => {
      try {
        await apiOb('brouillon', { brouillon: brouillonCourant, client: admin ? compte.id : undefined });
        setSauvegarde('ok');
        onChangement && onChangement(brouillonCourant);
      } catch (e) {
        setSauvegarde('erreur');
      }
    }, 1200);
    return () => clearTimeout(t);
  }, [brouillonCourant]);

  const maj = (k, v) => setFiche((f) => ({ ...f, [k]: v }));
  const majCarte = (i, k, v) => setCartes((cs) => cs.map((c, j) => (j === i ? { ...c, [k]: v } : c)));
  const aller = (id) => { setErreur(''); setInfo(''); setEcranId(id); window.scrollTo({ top: 0, behavior: 'smooth' }); };
  const suivant = () => { const n = ecransVisibles[idx + 1]; if (n) aller(n.id); };
  const precedent = () => { const p = ecransVisibles[idx - 1]; if (p) aller(p.id); };

  const run = async (label, fn) => {
    setErreur(''); setInfo(''); setEnCours(label);
    try { await fn(); } catch (e) { setErreur(e.message || 'Erreur.'); } finally { setEnCours(''); }
  };
  const corps = (extra) => ({ ...extra, client: admin ? compte.id : undefined });

  const lireLeSite = () => run('lecture', async () => {
    const r = await apiOb('lecture', corps({ site: fiche.site }));
    const l = r.lecture;
    if (l.suggestions) setSuggestions(l.suggestions);
    setFiche((f) => ({
      ...f,
      site: r.urlFinale || f.site,
      client: f.client || l.entreprise,
      description: f.description || l.description,
      signataire: f.signataire || l.signataire,
      registre: l.registre || f.registre,
      cible: f.cible || l.cible, promesse: f.promesse || l.promesse, zone: l.zone || f.zone || 'France',
      qui_decide: f.qui_decide || l.qui_decide, declencheur: f.declencheur || l.declencheur,
      offres: f.offres || (l.offres || []).join('\n'),
      chiffres: f.chiffres || (l.chiffres || []).join('\n'),
      cas: f.cas || (l.cas || []).join('\n'),
      vocabulaire: f.vocabulaire || (l.vocabulaire || []).join('\n'),
      bannis: f.bannis || (l.bannis || []).join('\n'),
      voix: f.voix || l.voix || '',
      prix: f.prix || (l.prix || []).join('\n'),
    }));
    setInfo(`${r.documents_lus ? `Site et ${r.documents_lus} document(s) lus.` : 'Site lu.'} Tout ce qui suit est pré-rempli, et chaque question ouverte te propose des réponses à choisir d'un clic. Corrige ce qui est faux.`);
  });

  const calculerCibles = (forcer) => run('cibles', async () => {
    const r = await apiOb('cibles', corps({ fiche, forcer: !!forcer }));
    setCartes(r.cartes || []);
    const { cartes: _c, deja, ...reste } = r;
    setCiblesInfo(reste);
    setInfo(deja ? 'Tes cercles étaient déjà calculés.' : 'Cercles calculés et comptés. Décoche ceux qui ne te ressemblent pas.');
  });

  const trouver = () => run('prospects', async () => {
    const c = cartes.filter((x) => x.actif !== false)[carteTest];
    const r = await apiOb('prospects', corps({ carte: c, n: 3, exclure: prospects.map((p) => p.societe) }));
    if (!r.prospects?.length) throw new Error(r.raison === 'aucune_societe_avec_site' ? "Aucune société avec un site dans cette cible. Change de cible." : 'Aucun prospect trouvé.');
    setProspects(r.prospects); setTests({});
    setInfo(`${r.prospects.length} prospects sortis d'un vivier de ${nombre(r.total)} sociétés.`);
  });
  const ecrirePour = (i) => run(`sequence-${i}`, async () => {
    const c = cartes.filter((x) => x.actif !== false)[carteTest];
    const r = await apiOb('sequence', corps({ fiche, carte: c, prospect: prospects[i] }));
    setTests((t) => ({ ...t, [i]: { ...r, prospect: prospects[i] } }));
  });
  const corrigerPour = (i) => run(`corriger-${i}`, async () => {
    const t = tests[i];
    const c = cartes.filter((x) => x.actif !== false)[carteTest];
    const r = await apiOb('corriger', corps({ fiche, carte: c, prospect: t.prospect, diagnostic: t.diagnostic, sequence: t.sequence, siteTexte: t.siteTexte }));
    setTests((ts) => ({ ...ts, [i]: { ...t, sequence: r.sequence, note: r.note, constats: r.constats, corrige: r.corrige, note_avant: r.note_avant ?? t.note } }));
  });
  const ajouterDocument = (fichier) => run('document', async () => {
    if (!fichier) return;
    if (fichier.type !== 'application/pdf') throw new Error('Seuls les PDF sont acceptés.');
    if (fichier.size > 3 * 1024 * 1024) throw new Error('Un document fait 3 Mo au plus. Compresse-le ou choisis-en un plus léger.');
    setEnvoiDoc(fichier.name);
    const base64 = await new Promise((resolve, reject) => {
      const lecteur = new FileReader();
      lecteur.onload = () => resolve(String(lecteur.result).split(',')[1] || '');
      lecteur.onerror = () => reject(new Error('Lecture du fichier impossible.'));
      lecteur.readAsDataURL(fichier);
    });
    try {
      const r = await apiOb('document', corps({ nom: fichier.name, type: fichier.type, base64 }));
      setDocuments(r.documents || []);
      setInfo(`${fichier.name} ajouté. Relance la lecture pour en tenir compte.`);
    } finally { setEnvoiDoc(''); }
  });
  const supprimerDoc = (id) => run('document', async () => {
    const r = await apiOb('document_supprimer', corps({ id }));
    setDocuments(r.documents || []);
  });

  const majMessage = (i, champ, v) => setTests((ts) => ({ ...ts, [i]: { ...ts[i], sequence: { ...ts[i].sequence, [champ]: v }, modifie: true } }));
  const validerExemple = (i) => {
    const t = tests[i]; const s = t.sequence;
    const texte = CHAMPS_SEQ.filter(([c]) => String(s[c] || '').trim()).map(([c, l]) => `[${l}]\n${s[c]}`).join('\n\n');
    setExemples((ex) => [...ex.filter((e) => e.societe !== t.prospect.societe), { societe: t.prospect.societe, texte }]);
    setInfo(`Séquence de ${t.prospect.societe} gardée comme exemple de voix.`);
  };
  const debloquer = () => run('debloquer', async () => {
    const r = await apiOb('debloquer', corps({ nb: 10 }));
    setLanceDetail(r);
    setInfo(r.premierLot?.ok ? 'Fiche et cartes enregistrées, premier lot lancé. Compte une quinzaine de minutes.' : `Fiche et cartes enregistrées, mais le premier lot n'est pas parti : ${r.premierLot?.raison || 'inconnu'}.`);
    onChangement && onChangement({ ...brouillonCourant, statut: r.statut });
  });

  /* ---- Rendu d'un écran ---- */
  const contenu = () => {
    switch (ecran.id) {
      case 'site': return (
        <>
          <div className="ob-row">
            <input className="ob-input" value={fiche.site} onChange={(e) => maj('site', e.target.value)} placeholder="https://ton-site.fr" autoFocus onKeyDown={(e) => { if (e.key === 'Enter' && fiche.site) lireLeSite(); }} />
            <button className="btn btn-glow" type="button" disabled={!!enCours || (!fiche.site && !documents.length)} onClick={lireLeSite}>{enCours === 'lecture' ? 'Lecture…' : (documents.length ? (fiche.site ? 'Lire mon site et mes documents' : 'Lire mes documents') : 'Lire mon site')} <ArrowUpRight /></button>
          </div>
          {enCours === 'lecture' ? <Attente etapes={documents.length ? ['On ouvre ton site…', 'On lit tes documents…', 'On prépare ta fiche…'] : ['On ouvre ton site…', 'On lit tes pages…', 'On prépare ta fiche…']} /> : null}
          <div className="ob-docs">
            <div className="ob-docs-head"><span className="ob-field-l">Tes documents</span><small className="ob-muted">facultatif · PDF · 3 au plus · 3 Mo chacun</small></div>
            {documents.map((d) => (
              <div className="ob-doc" key={d.id}>
                <span className="ob-doc-ico" aria-hidden="true">PDF</span>
                <span className="ob-doc-nom">{d.nom}</span>
                <span className="ob-doc-taille">{d.taille ? `${Math.max(1, Math.round(d.taille / 1024))} Ko` : ''}</span>
                <button type="button" className="ob-lien" onClick={() => supprimerDoc(d.id)} disabled={!!enCours}>Retirer</button>
              </div>
            ))}
            {envoiDoc ? <div className="ob-doc"><span className="ob-spin" aria-hidden="true" /><span className="ob-doc-nom">Envoi de {envoiDoc}…</span></div> : null}
            {documents.length < 3 ? (
              <label className={'ob-doc-ajout' + (enCours ? ' off' : '')}>
                <input type="file" accept="application/pdf" disabled={!!enCours} onChange={(e) => { const f = e.target.files && e.target.files[0]; e.target.value = ''; if (f) ajouterDocument(f); }} />
                + Ajouter une plaquette, une brochure ou une grille tarifaire
              </label>
            ) : null}
          </div>
          <p className="ob-skip">Pas de site ? <button type="button" className="ob-lien" onClick={suivant}>Passe cette étape</button>, tu rempliras à la main.</p>
        </>
      );
      case 'entreprise': return (
        <>
          <Champ label="Le nom de ton entreprise"><input className="ob-input" value={fiche.client} onChange={(e) => maj('client', e.target.value)} autoFocus /></Champ>
          <Champ label="Ce que tu fais, en deux phrases"><textarea className="ob-input" rows={3} value={fiche.description} onChange={(e) => maj('description', e.target.value)} /><Suggestions liste={suggestions.description} onChoisir={(v) => maj('description', v)} /></Champ>
        </>
      );
      case 'signataire': return (
        <>
          <Champ label="Prénom, nom, rôle, et pourquoi on l'écoute" aide="Exemple : Julia Levy, fondatrice de Boom Maker, elle industrialise la prospection des TPE.">
            <input className="ob-input" value={fiche.signataire} onChange={(e) => maj('signataire', e.target.value)} autoFocus />
          </Champ>
          <Champ label="Tu ou vous ?"><Choix options={REGISTRES} valeur={fiche.registre} onChange={(v) => maj('registre', v)} /></Champ>
        </>
      );
      case 'madlib': return (
        <>
          <div className="ob-madlib">
            <span>Tu aides</span>
            <input className="ob-input" value={fiche.cible} onChange={(e) => maj('cible', e.target.value)} placeholder="les cabinets dentaires" autoFocus />
            <span>à</span>
            <input className="ob-input" value={fiche.promesse} onChange={(e) => maj('promesse', e.target.value)} placeholder="remplir leur agenda sans secrétariat" />
          </div>
          <Champ label="Dans quelle zone ?" aide="France, une région, une ville, un pays."><input className="ob-input" value={fiche.zone} onChange={(e) => maj('zone', e.target.value)} /></Champ>
        </>
      );
      case 'offres': return (
        <Champ label="Une offre par ligne : nom = ce que le client obtient">
          <textarea className="ob-input" rows={6} value={fiche.offres} onChange={(e) => maj('offres', e.target.value)} placeholder={"Prospection B2B = des rendez-vous avec les décideurs qui achètent ce que tu vends\nContenu = des articles publiés chaque semaine sans y passer de temps"} autoFocus />
        </Champ>
      );
      case 'preuves': return (
        <>
          <Champ label="Les chiffres autorisés" aide="Un par ligne, avec sa formulation exacte. Vide = aucun chiffre sur ton entreprise dans les messages."><textarea className="ob-input" rows={4} value={fiche.chiffres} onChange={(e) => maj('chiffres', e.target.value)} placeholder={"Plus de 100 restaurants en Europe\nFood cost à 21,5 %"} /></Champ>
          <Champ label="Les cas clients citables" aide="Le cas avec ses chiffres exacts, et pour qui on peut le citer."><textarea className="ob-input" rows={4} value={fiche.cas} onChange={(e) => maj('cas', e.target.value)} placeholder="Un réseau de 40 agences : 126 conversations en trois mois. Citable pour les réseaux et franchiseurs." /></Champ>
          <Champ label="Tes prix, si tu veux qu'ils puissent être cités" aide="Un prix par ligne, avec sa condition. Vide = les messages ne parlent jamais de prix."><textarea className="ob-input" rows={3} value={fiche.prix} onChange={(e) => maj('prix', e.target.value)} placeholder={"Pack prospection : 390 € HT par mois, sans engagement"} /></Champ>
        </>
      );
      case 'meilleur': return (
        <>
          <Champ label="Ton meilleur client, en une phrase"><textarea className="ob-input" rows={3} value={fiche.meilleur_client} onChange={(e) => maj('meilleur_client', e.target.value)} placeholder="Un domaine viticole de 30 personnes qui renouvelle ses étiquettes à chaque millésime et travaille avec deux imprimeurs." autoFocus /><Suggestions liste={suggestions.meilleur_client} onChoisir={(v) => maj('meilleur_client', v)} /></Champ>
          <Champ label="Sa taille"><Choix options={TAILLES} valeur={fiche.taille} onChange={(v) => maj('taille', v)} /></Champ>
        </>
      );
      case 'besoin': return (
        <>
          <Champ label="Ce qui te manque"><input className="ob-input" value={fiche.manque} onChange={(e) => maj('manque', e.target.value)} placeholder="Des rendez-vous avec de nouveaux acheteurs" autoFocus /><Suggestions liste={suggestions.manque} onChoisir={(v) => maj('manque', v)} /></Champ>
          <Champ label="Où ça coince"><Choix options={COINCE} valeur={fiche.coince} onChange={(v) => maj('coince', v)} /></Champ>
        </>
      );
      case 'decideur': return (
        <>
          <Champ label="Qui signe le chèque chez tes clients" aide="La fonction précise et le type d'entreprise."><textarea className="ob-input" rows={2} value={fiche.qui_decide} onChange={(e) => maj('qui_decide', e.target.value)} autoFocus /></Champ>
          <Champ label="Ce qui déclenche l'achat, et dans quelle fenêtre"><textarea className="ob-input" rows={2} value={fiche.declencheur} onChange={(e) => maj('declencheur', e.target.value)} /></Champ>
        </>
      );
      case 'cibles': {
        const manque = ['cible', 'promesse', 'zone', 'meilleur_client', 'taille', 'manque', 'coince'].filter((k) => !fiche[k]);
        return (
          <>
            {!cartes.length ? (
              <div className="ob-actions">
                <button className="btn btn-glow" type="button" onClick={() => calculerCibles(false)} disabled={!!enCours || manque.length > 0}>{enCours === 'cibles' ? 'Calcul en cours…' : 'Calculer mes cibles'} <ArrowUpRight /></button>
                {manque.length ? <small className="ob-muted">Il manque des réponses aux écrans précédents.</small> : null}
              </div>
            ) : null}
            {enCours === 'cibles' ? <Attente etapes={['On relit tout ce que tu as dit…', 'On cherche qui achète vraiment ce que tu vends…', 'On compte les entreprises en France…', 'Encore un instant, on classe les cercles…']} /> : null}
            {ciblesInfo?.contexte ? <div className="ob-note">{ciblesInfo.contexte}{ciblesInfo.b2c ? <><br /><b>Levier B2B :</b> {ciblesInfo.b2c}</> : null}</div> : null}
            <div className="ob-cercles">
              {cartes.map((c, i) => (
                <div className={'ob-cercle' + (c.actif === false ? ' off' : '')} key={c.cle || i}>
                  <label className="ob-cercle-head">
                    <input type="checkbox" checked={c.actif !== false} onChange={(e) => majCarte(i, 'actif', e.target.checked)} />
                    <span className="ob-cercle-nom">{c.cible || `Cible ${i + 1}`}</span>
                    {c.vivier != null ? <span className="ob-cercle-n">{c.vivier_plafonne ? 'plus de ' : ''}{nombre(c.vivier)} entreprises</span> : null}
                  </label>
                  {c.pourquoi ? <p className="ob-cercle-p">{c.pourquoi}</p> : null}
                  {admin ? (
                    <details className="ob-details"><summary>Détails techniques</summary>
                      <div className="ob-grid2">
                        <Champ label="Nom"><input className="ob-input" value={c.cible || ''} onChange={(e) => majCarte(i, 'cible', e.target.value)} /></Champ>
                        <Champ label="Codes NAF, un par ligne"><textarea className="ob-input" rows={3} value={Array.isArray(c.naf) ? c.naf.join('\n') : (c.naf || '')} onChange={(e) => majCarte(i, 'naf', e.target.value)} /></Champ>
                        <Champ label="Effectif (min-max)"><input className="ob-input" value={c.effectif || ''} onChange={(e) => majCarte(i, 'effectif', e.target.value)} /></Champ>
                        <Champ label="Zone"><input className="ob-input" value={c.zone || ''} onChange={(e) => majCarte(i, 'zone', e.target.value)} /></Champ>
                        <Champ label="Priorité"><input className="ob-input" value={c.priorite || ''} onChange={(e) => majCarte(i, 'priorite', e.target.value)} /></Champ>
                        <Champ label="Qui décide"><textarea className="ob-input" rows={2} value={c.qui_decide || ''} onChange={(e) => majCarte(i, 'qui_decide', e.target.value)} /></Champ>
                        <Champ label="Déclencheur et fenêtre"><textarea className="ob-input" rows={2} value={c.declencheur || ''} onChange={(e) => majCarte(i, 'declencheur', e.target.value)} /></Champ>
                        <Champ label="Douleur à nommer"><textarea className="ob-input" rows={2} value={c.douleur || ''} onChange={(e) => majCarte(i, 'douleur', e.target.value)} /></Champ>
                        <Champ label="Angles"><textarea className="ob-input" rows={2} value={c.angle || ''} onChange={(e) => majCarte(i, 'angle', e.target.value)} /></Champ>
                        <Champ label="Preuve à citer"><textarea className="ob-input" rows={2} value={c.preuve || ''} onChange={(e) => majCarte(i, 'preuve', e.target.value)} /></Champ>
                        <Champ label="Objection n°1"><input className="ob-input" value={c.objection || ''} onChange={(e) => majCarte(i, 'objection', e.target.value)} /></Champ>
                        <Champ label="Réponse à l'objection"><input className="ob-input" value={c.reponse_objection || ''} onChange={(e) => majCarte(i, 'reponse_objection', e.target.value)} /></Champ>
                      </div>
                    </details>
                  ) : null}
                </div>
              ))}
            </div>
            {cartes.length ? (
              <div className="ob-actions">
                {admin ? <button className="ob-btn-ghost" type="button" onClick={() => setCartes((cs) => [...cs, { cle: `cible-${cs.length + 1}-${Date.now()}`, cible: '', naf: [], effectif: '1-250', zone: fiche.zone, priorite: `P${Math.min(cs.length + 1, 3)}`, actif: true }])}>Ajouter une cible</button> : null}
                {admin ? <button className="ob-btn-ghost" type="button" onClick={() => calculerCibles(true)} disabled={!!enCours}>Recalculer</button> : null}
                {ciblesInfo?.questions?.length && admin ? <small className="ob-muted">À vérifier avec le client : {ciblesInfo.questions.join(' · ')}</small> : null}
              </div>
            ) : null}
          </>
        );
      }
      case 'objectif': return (
        <>
          <Champ label="L'objectif"><Choix options={OBJECTIFS} valeur={fiche.objectif} onChange={(v) => maj('objectif', v)} /></Champ>
          <Champ label="Ce que le prospect obtient en acceptant" aide="Concret, vérifiable, dans ses mots. Une liste de décideurs identifiés pour lui, un calendrier de son marché, un point précis à trancher.">
            <textarea className="ob-input" rows={3} value={fiche.apport} onChange={(e) => maj('apport', e.target.value)} placeholder="La liste des category managers laitiers qui négocient cet automne, et par lesquels commencer." />
            <Suggestions liste={suggestions.apport} onChoisir={(v) => maj('apport', v)} />
          </Champ>
        </>
      );
      case 'canal': return <Choix options={CANAUX} valeur={fiche.canal} onChange={(v) => maj('canal', v)} />;
      case 'mots': return (
        <div className="ob-grid2">
          <Champ label="À utiliser"><textarea className="ob-input" rows={8} value={fiche.vocabulaire} onChange={(e) => maj('vocabulaire', e.target.value)} autoFocus /></Champ>
          <Champ label="À bannir" aide="Ce qui grille un message auprès de ta cible."><textarea className="ob-input" rows={8} value={fiche.bannis} onChange={(e) => maj('bannis', e.target.value)} /></Champ>
        </div>
      );
      case 'voix': return (
        <Champ label="Ton ton, en quelques phrases"><textarea className="ob-input" rows={6} value={fiche.voix} onChange={(e) => maj('voix', e.target.value)} placeholder={"Je tutoie. Phrases courtes, pas de jargon, un peu d'humour sec. Je dis « boîte », jamais « entreprise ». Je ne promets jamais de résultat sans condition."} autoFocus /></Champ>
      );
      case 'interdits': return (
        <Champ label="Une formule ou un sujet par ligne"><textarea className="ob-input" rows={5} value={fiche.interdits} onChange={(e) => maj('interdits', e.target.value)} placeholder={"tu n'as pas le temps de prospecter\nnotre solution innovante"} autoFocus /><Suggestions liste={suggestions.interdits} ajouter onChoisir={(v) => maj('interdits', [fiche.interdits, v].filter(Boolean).join('\n'))} /></Champ>
      );
      case 'fin': {
        const actives = cartes.filter((c) => c.actif !== false);
        return (
          <>
            <div className="ob-recap">
              <div><span className="es-k">Entreprise</span><p>{fiche.client || '…'}</p></div>
              <div><span className="es-k">Tu aides</span><p>{fiche.cible || '…'} à {fiche.promesse || '…'}</p></div>
              <div><span className="es-k">Cibles</span><p>{actives.map((c) => c.cible).filter(Boolean).join(' · ') || 'à calculer'}</p></div>
              <div><span className="es-k">Objectif</span><p>{fiche.objectif} · {CANAUX.find((c) => c[0] === fiche.canal)?.[1] || fiche.canal}</p></div>
            </div>
            {!admin ? (
              <>
                <p className="ob-intro">La suite se fait avec Julia, en visio : on écrit tes trois premières séquences sur de vrais prospects, sous tes yeux, et on lance ton premier lot. Tu reviens ici ensuite pour le valider.</p>
                <div className="ob-actions">
                  {compte?.visio ? <span className="es-statut go">Visio réservée</span> : <a className="btn btn-glow" href={CALENDLY_OB} target="_blank" rel="noopener">Réserver ma visio avec Julia <ArrowUpRight /></a>}
                  <button className="ob-btn-ghost" type="button" onClick={onSortie}>Retour à mon espace</button>
                </div>
              </>
            ) : (
              <p className="ob-intro">Fin de la partie client. Les chapitres 6 et 7 sont à toi : l'épreuve du réel en visio, puis le lancement.</p>
            )}
          </>
        );
      }
      case 'epreuve': {
        const actives = cartes.filter((c) => c.actif !== false);
        const carte = actives[carteTest];
        return (
          <>
            {actives.length > 1 ? <Champ label="Cible à tester"><Choix options={actives.map((c, i) => [String(i), c.cible || `Cible ${i + 1}`])} valeur={String(carteTest)} onChange={(v) => setCarteTest(Number(v))} /></Champ> : null}
            <div className="ob-actions">
              <button className="btn btn-glow" type="button" onClick={trouver} disabled={!!enCours || !carte}>{enCours === 'prospects' ? 'Recherche…' : (prospects.length ? 'Trois autres prospects' : 'Trouver trois prospects')} <ArrowUpRight /></button>
              {!carte ? <small className="ob-muted">Aucune cible active : reviens au chapitre 3.</small> : null}
            </div>
            {enCours === 'prospects' ? <Attente texte="Basile cherche des sociétés avec un site, puis leurs dirigeants." /> : null}
            <div className="ob-cercles">
              {prospects.map((p, i) => {
                const t = tests[i];
                return (
                  <div className="ob-cercle" key={p.societe + i}>
                    <div className="ob-cercle-head"><span className="ob-cercle-nom">{p.prenom} {p.nom} · {p.societe}</span>{t ? <Score note={t.note} /> : null}</div>
                    <p className="ob-cercle-p">{p.poste} · {p.naf_label || p.naf} · {p.effectif || '?'} pers · {p.ville} · <a className="ob-lien" href={p.site} target="_blank" rel="noreferrer">site</a>{p.linkedin ? <> · <a className="ob-lien" href={p.linkedin} target="_blank" rel="noreferrer">LinkedIn</a></> : null}</p>
                    {!t ? <div className="ob-actions"><button className="btn btn-glow" type="button" onClick={() => ecrirePour(i)} disabled={!!enCours}>{enCours === `sequence-${i}` ? 'Écriture…' : 'Écrire la séquence'} <ArrowUpRight /></button></div> : null}
                    {enCours === `sequence-${i}` ? <Attente etapes={['On lit son site…', 'On diagnostique sa mécanique d\'acquisition…', 'On écrit…']} /> : null}
                    {t ? (
                      <>
                        <div className="ob-note"><b>Diagnostic :</b> {t.diagnostic?.qui_decide} <br /><b>Déclencheur :</b> {t.diagnostic?.declencheur_achat} <br /><b>Difficulté :</b> {t.diagnostic?.difficulte_structurelle}<br /><b>Canal :</b> {t.canal}{t.corrige?.length ? <> · corrigé : {t.corrige.join(', ')} (avant {Number(t.note_avant).toFixed(1)})</> : null}</div>
                        {t.constats?.length ? <ul className="ob-constats">{t.constats.map((c, k) => <li key={k} data-g={c.gravite}>{c.texte}</li>)}</ul> : <div className="ob-ok">Aucun constat mécanique.</div>}
                        <div className="ob-messages">
                          {CHAMPS_SEQ.map(([c, l, j, objet]) => (
                            <div className={'ob-msg' + (!String(t.sequence[c] || '').trim() ? ' vide' : '')} key={c}>
                              <div className="ob-msg-head"><b>{l}</b><span>{j}{!objet && t.sequence[c] ? ` · ${motsOb(t.sequence[c])} mots` : ''}</span></div>
                              <textarea className="ob-input" rows={objet ? 1 : 4} value={t.sequence[c] || ''} onChange={(e) => majMessage(i, c, e.target.value)} />
                            </div>
                          ))}
                        </div>
                        <div className="ob-actions">
                          <button className="ob-btn-ghost" type="button" onClick={() => corrigerPour(i)} disabled={!!enCours || !t.constats?.length}>{enCours === `corriger-${i}` ? 'Correction…' : 'Corriger les constats'}</button>
                          <button className="ob-btn-ghost" type="button" onClick={() => ecrirePour(i)} disabled={!!enCours}>Réécrire</button>
                          <button className="btn btn-glow" type="button" onClick={() => validerExemple(i)}>{exemples.some((e) => e.societe === p.societe) ? 'Gardée comme exemple' : 'Garder comme exemple de voix'} <ArrowUpRight /></button>
                        </div>
                      </>
                    ) : null}
                  </div>
                );
              })}
            </div>
            {exemples.length ? <div className="ob-ok">{exemples.length} séquence(s) gardée(s) : {exemples.map((e) => e.societe).join(', ')}.</div> : null}
          </>
        );
      }
      case 'lancement': return (
        <>
          <div className="ob-recap">
            <div><span className="es-k">Client</span><p>{fiche.client} · {fiche.signataire}</p></div>
            <div><span className="es-k">Base Airtable</span><p>{compte?.baseAirtable || 'pas encore : duplique la base modèle et colle son identifiant dans la fiche admin'}</p></div>
            <div><span className="es-k">Cibles</span><p>{cartes.filter((c) => c.actif !== false).map((c) => c.cible).filter(Boolean).join(' · ') || 'aucune'}</p></div>
            <div><span className="es-k">Exemples de voix</span><p>{exemples.length} séquence(s)</p></div>
          </div>
          {debloque ? <div className="ob-ok">Étape 6 débloquée le {new Date(compte.etape6Le).toLocaleString('fr-FR')}. Statut du compte : {compte.statut}.</div> : null}
          <div className="ob-actions">
            <button className="btn btn-glow" type="button" onClick={debloquer} disabled={!!enCours || !compte?.baseAirtable || !fiche.client}>{enCours === 'debloquer' ? 'Lancement…' : (debloque ? 'Relancer un premier lot' : 'Enregistrer et lancer le premier lot')} <ArrowUpRight /></button>
            <button className="ob-btn-ghost" type="button" onClick={() => run('enregistrer', async () => { await apiOb('enregistrer', corps({})); setInfo('Fiche, cartes et exemples réécrits dans la base.'); })} disabled={!!enCours || !compte?.baseAirtable}>Réenregistrer sans lancer</button>
          </div>
          {lanceDetail?.premierLot ? <p className="ob-muted">{lanceDetail.premierLot.ok ? 'Le workflow « Premier lot » tourne sur GitHub : dix prospects, puis qualification, écriture et relecture. Les séquences arrivent en « À valider ».' : `Workflow non lancé : ${lanceDetail.premierLot.raison}`}</p> : null}
        </>
      );
      default: return null;
    }
  };

  const texteSauvegarde = { en_cours: 'Enregistrement…', ok: 'Enregistré', erreur: 'Enregistrement impossible, vérifie ta connexion' }[sauvegarde] || '';
  const estDernier = idx === ecransVisibles.length - 1;

  return (
    <div className="ob">
      <div className="ob-top">
        <button type="button" className="ob-lien" onClick={onSortie}>← Mon espace</button>
        <span className={'ob-save ' + sauvegarde}>{texteSauvegarde}</span>
      </div>
      <div className="ob-progress" aria-label="Progression">
        {chapitresVisibles.map((c) => {
          const premier = ecransVisibles.find((e) => e.ch === c.n);
          const fait = c.n < ecran.ch;
          return (
            <button key={c.n} type="button" className={'ob-chap' + (c.n === ecran.ch ? ' on' : '') + (fait ? ' done' : '')} onClick={() => premier && aller(premier.id)} title={c.titre}>
              <span className="ob-chap-n">{c.n}</span><span className="ob-chap-t">{c.titre}</span>
            </button>
          );
        })}
      </div>
      <div className="ob-card">
        <span className="es-k">{chapitre.titre} · {posDansChapitre + 1}/{ecransDuChapitre.length}{ecran.optionnel ? ' · facultatif' : ''}</span>
        <h2 className="ob-titre">{ecran.titre}</h2>
        {ecran.aide ? <p className="ob-intro">{ecran.aide}</p> : null}
        {erreur ? <div className="es-alerte">{erreur}</div> : null}
        {info && !erreur ? <div className="es-note">{info}</div> : null}
        <div className="ob-body">{contenu()}</div>
        {ecran.id !== 'fin' ? (
          <div className="ob-nav">
            <button type="button" className="ob-btn-ghost" onClick={precedent} disabled={idx === 0}>Retour</button>
            {!estDernier ? <button type="button" className="btn btn-glow" onClick={suivant} disabled={!peutContinuer || (!!enCours && enCours !== 'lecture')}>{ecran.optionnel && !peutContinuer ? 'Passer' : 'Continuer'} <ArrowUpRight /></button> : null}
          </div>
        ) : null}
      </div>
    </div>
  );
}

window.BMOnboarding = { Onboarding, ECRANS, CHAPITRES, FICHE_VIDE };
