/* ============ Genius — CONNEXIONS PUBLICITAIRES Meta Ads / Google Ads (20/09/2026) ============
   AdsConnections : Réglages → connexion OAuth officielle par espace, sélection des comptes, statut, déconnexion.
   AdsPanel       : onglet « Publicité » d'une campagne → lecture (dépenses, impressions, clics, conversions, coût/résultat, période, devise,
                    fraîcheur, définition des conversions — Meta et Google côte à côte, jamais additionnés), rattachement, suggestions
                    (faits / hypothèses), suivi & UTM, brouillons d'annonces, publication contrôlée (récapitulatif + validation, création EN PAUSE).
   AdDraftEditor  : brouillon Meta ou Google avec les champs et limites réels de chaque plateforme.
   Secrets et jetons côté serveur uniquement. */
const adPost = (action, body) => fetch(GAPI() + '?action=' + action, { method: 'POST', headers: GHEAD(), body: JSON.stringify(Object.assign({ tenant: window.geniusTenant() }, body)) }).then(r => r.json());
const AD_STATUS = { local_draft: ['Brouillon local', 'muted'], creating: ['Création…', 'run'], created_paused: ['Créée sur la plateforme · en pause', 'wait'], in_review: ['En examen', 'wait'], active: ['Active', 'ok'], error: ['Erreur', 'err'] };
const adFmtN = n => (n == null || isNaN(Number(n))) ? '—' : Number(n).toLocaleString('fr-FR', { maximumFractionDigits: 1 });
const adFmtMoney = (n, cur) => (n == null || isNaN(Number(n))) ? '—' : Number(n).toLocaleString('fr-FR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + ' ' + (cur === 'EUR' ? '€' : (cur || ''));
const adLen = (s, max) => <span className={'ad-len' + (String(s || '').length > max ? ' over' : '')}>{String(s || '').length}/{max}</span>;
function AdModeBadge({ mode }) { if (mode === 'mock') return <span className="st-mode st-mode-test" title="Connexions et chiffres simulés, aucune plateforme réelle.">Mode test · données simulées</span>; if (mode === 'live') return <span className="st-mode st-mode-live">Plateformes réelles</span>; return <span className="st-mode st-mode-off">Applications non configurées</span>; }

/* ---------- Réglages : connexions par espace ---------- */
function AdsConnections() {
  const [st, setSt] = React.useState(null); const [err, setErr] = React.useState(''); const [busy, setBusy] = React.useState('');
  const load = React.useCallback(() => adPost('ads_status', {}).then(j => { if (j && j.ok) setSt(j); else setErr((j && j.error) || 'erreur'); }).catch(() => setErr('réseau')), []);
  const popupRef = React.useRef(null);
  React.useEffect(() => { load(); const onMsg = e => { if (!e.data || !e.data.genius_ads) return; let apiOrigin = ''; try { apiOrigin = new URL(window.GENIUS_API || '', location.href).origin; } catch (_) {} if (e.origin !== apiOrigin || (popupRef.current && e.source !== popupRef.current)) return;   /* (audit C-A27) origine + fenêtre vérifiées */ load(); if (e.data.genius_ads === 'error') setErr(e.data.message || 'connexion refusée'); }; window.addEventListener('message', onMsg); return () => window.removeEventListener('message', onMsg); }, [load]);
  const connect = p => { setBusy(p); setErr(''); const w = window.open('about:blank', 'genius_ads_oauth', 'width=560,height=720'); popupRef.current = w;   /* (audit C-A28) ouverte pendant le clic, naviguée ensuite */
    adPost('ads_oauth_start', { platform: p }).then(j => { if (!j || !j.ok) { if (w) w.close(); setErr((j && (j.message || j.error)) || 'connexion impossible'); return; } if (j.connected) { if (w) w.close(); load(); return; } if (w) w.location.href = j.url; else setErr('Autorisez les fenêtres surgissantes pour vous connecter.'); }).catch(() => { if (w) w.close(); setErr('réseau'); }).finally(() => setBusy('')); };
  const disconnect = p => { if (!window.confirm('Déconnecter cette plateforme de cet espace ? Les rattachements et brouillons restent, mais plus aucune donnée ne sera lue.')) return; adPost('ads_disconnect', { platform: p }).then(load); };
  const refresh = p => { setBusy(p + ':r'); adPost('ads_refresh_accounts', { platform: p }).then(j => { if (j && !j.ok) setErr(j.error || 'vérification impossible'); load(); }).catch(() => setErr('réseau')).finally(() => setBusy('')); };
  const select = (p, ids) => { setSt(x => x ? { ...x, platforms: x.platforms.map(q => q.platform === p ? { ...q, selected: ids } : q) } : x); adPost('ads_select_accounts', { platform: p, selected: ids }).then(load).catch(() => setErr('réseau')); };   /* (audit C-A29) */
  if (err && !st) return <div className="err-txt">Connexions non chargées : {err}</div>;
  if (!st) return <Skeleton rows={3} />;
  return <div className="ads-conn">
    <div className="sec-sub" style={{ marginBottom: 10 }}>{st.message || 'Chaque espace de travail connecte ses propres comptes. Les jetons restent sur le serveur, chiffrés.'} <AdModeBadge mode={st.mode} /></div>
    {err && <div className="err-txt" style={{ marginBottom: 10 }}>{err}</div>}
    {st.platforms.map(p => { const conn = p.status === 'connected'; return <div key={p.platform} className={'ads-plat' + (conn ? ' on' : '')}>
      <div className="ads-plat-h">
        <div><div className="ads-plat-t">{p.label}</div><div className="sec-sub">{conn ? <>Connecté{p.user ? ' · ' + p.user : ''}{p.connected_at ? ' · le ' + new Date(p.connected_at).toLocaleDateString('fr-FR') : ''}{p.expires_at ? ' · autorisation jusqu’au ' + new Date(p.expires_at).toLocaleDateString('fr-FR') : ''}{p.mock ? ' · simulé' : ''}</> : p.status === 'expired' ? <span style={{ color: 'var(--red)' }}>Autorisation expirée ou révoquée — reconnectez-vous.</span> : p.status === 'error' ? <span style={{ color: 'var(--red)' }}>{p.error || 'erreur'}</span> : 'Non connecté'}</div></div>
        <div className="head-actions">{conn ? <><Btn size="sm" kind="ghost" icon="refresh" onClick={() => refresh(p.platform)} disabled={busy === p.platform + ':r'}>Vérifier</Btn><Btn size="sm" kind="ghost" onClick={() => disconnect(p.platform)}>Déconnecter</Btn></> : <Btn size="sm" icon="link" onClick={() => connect(p.platform)} disabled={!p.app_configured || busy === p.platform}>{p.status === 'expired' ? 'Reconnecter' : 'Connecter'}</Btn>}</div>
      </div>
      {!p.app_configured && <div className="st-notice"><Icon name="alert" size={14} /> <span>{st.requirements[p.platform]}{st.native ? ' Renseignez l’application ci-dessous (identifiants serveur).' : ''}</span></div>}
      {conn && <div className="ads-accts">
        <div className="sec-sub" style={{ marginBottom: 6 }}>Comptes autorisés dans Genius ({p.selected.length}/{p.accounts.length}) — seuls les comptes cochés sont lus et utilisables.</div>
        {!p.accounts.length && <div className="sec-sub">Aucun compte publicitaire accessible avec cette autorisation.{p.error ? ' ' + p.error : ''}</div>}
        {p.accounts.map(a => { const on = p.selected.includes(a.id); return <label key={a.id} className={'ads-acct' + (on ? ' on' : '')}><input type="checkbox" checked={on} onChange={e => select(p.platform, e.target.checked ? [...p.selected, a.id] : p.selected.filter(x => x !== a.id))} /><span className="ads-acct-n">{a.name}</span><span className="ads-acct-m">{a.id}{a.currency ? ' · ' + a.currency : ''}{a.timezone ? ' · ' + a.timezone : ''}{a.status && a.status !== 'active' ? ' · ' + a.status : ''}{a.manager ? ' · compte administrateur' : ''}{a.test ? ' · compte test' : ''}</span></label>; })}
        <div className="sec-sub" style={{ marginTop: 6 }}>Autorisations accordées : {(p.scopes || []).join(', ') || '—'}{p.checked_at ? ' · vérifié le ' + new Date(p.checked_at).toLocaleString('fr-FR', { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' }) : ''}</div>
      </div>}
    </div>; })}
    {st.native && <AdsAppsConfig onChange={load} />}
  </div>;
}
/* identifiants d'application (Groupe Verlaine uniquement) : Meta app_id/secret, client OAuth Google, mode */
function AdsAppsConfig({ onChange }) {
  const [c, setC] = React.useState(null); const [form, setForm] = React.useState({ meta: {}, google: {} }); const [msg, setMsg] = React.useState('');
  const load = () => adPost('ads_config', {}).then(j => { if (j && j.ok) setC(j); });
  React.useEffect(() => { load(); }, []);
  if (!c) return null;
  const save = (set) => adPost('ads_config', { set }).then(j => { if (j && j.ok) { setC(j); setForm({ meta: {}, google: {} }); setMsg('Enregistré.'); setTimeout(() => setMsg(''), 2500); onChange && onChange(); } else setMsg((j && j.error) || 'erreur'); });
  return <details className="st-adv ads-apps"><summary>Applications publicitaires (identifiants serveur, Groupe Verlaine)</summary>
    <div className="sec-sub">URL de redirection OAuth à déclarer dans les deux consoles : <code>{c.redirect_uri}</code></div>
    <div className="st-set-row"><span>Mode</span><div className="chips">{[['live', 'Plateformes réelles'], ['mock', 'Mode test (simulé)'], ['off', 'Désactivé']].map(([k, l]) => <button key={k} type="button" className={'chip' + (c.mode === k ? ' on' : '')} onClick={() => save({ mode: k })}>{l}</button>)}</div></div>
    <div className="ads-apps-grid">
      <div><div className="sec-title" style={{ fontSize: 14 }}>Meta (Graph {c.meta.version})</div><div className="sec-sub">Permissions demandées : {c.meta.scopes.join(', ')}. App Review + vérification d’entreprise nécessaires pour les comptes d’autres entreprises.</div>
        <StField label="App ID" value={form.meta.app_id != null ? form.meta.app_id : c.meta.app_id} onChange={v => setForm(f => ({ ...f, meta: { ...f.meta, app_id: v } }))} />
        <StField type="password" autoComplete="new-password" label={'App secret' + (c.meta.has_secret ? ' (déjà enregistré)' : '')} value={form.meta.app_secret || ''} onChange={v => setForm(f => ({ ...f, meta: { ...f.meta, app_secret: v } }))} placeholder={c.meta.has_secret ? '••••••••' : ''} />
        <Btn size="sm" kind="ghost" onClick={() => save({ meta: { app_id: form.meta.app_id != null ? form.meta.app_id : c.meta.app_id, app_secret: form.meta.app_secret || 'GNSKEEP' } })}>Enregistrer Meta</Btn></div>
      <div><div className="sec-title" style={{ fontSize: 14 }}>Google Ads (API {c.google.version})</div><div className="sec-sub">Client OAuth d’un projet Google Cloud inscrit à l’API Google Ads (Explorer → Basic après vérification de marque). Scope : {c.google.scope}. Developer token facultatif depuis le 09/09/2026.</div>
        <StField label="Client ID" value={form.google.client_id != null ? form.google.client_id : c.google.client_id} onChange={v => setForm(f => ({ ...f, google: { ...f.google, client_id: v } }))} placeholder={c.google.client_id ? '' : 'xxxx.apps.googleusercontent.com'} />
        <StField type="password" autoComplete="new-password" label={'Client secret' + (c.google.has_secret ? ' (déjà enregistré)' : '')} value={form.google.client_secret || ''} onChange={v => setForm(f => ({ ...f, google: { ...f.google, client_secret: v } }))} placeholder={c.google.has_secret ? '••••••••' : ''} />
        <StField label="Compte administrateur (login-customer-id, facultatif)" value={form.google.login_customer_id != null ? form.google.login_customer_id : c.google.login_customer_id} onChange={v => setForm(f => ({ ...f, google: { ...f.google, login_customer_id: v } }))} />
        <Btn size="sm" kind="ghost" onClick={() => save({ google: { client_id: form.google.client_id != null ? form.google.client_id : 'GNSKEEP', client_secret: form.google.client_secret || 'GNSKEEP', login_customer_id: form.google.login_customer_id != null ? form.google.login_customer_id : c.google.login_customer_id } })}>Enregistrer Google</Btn></div>
    </div>
    {msg && <div className="ok-txt">{msg}</div>}
  </details>;
}

/* ---------- Brouillon d'annonce : champs et limites réels par plateforme ---------- */
function AdDraftEditor({ campaignId, draft, specs, status, onChange, onClose, onPublished }) {
  const [d, setD] = React.useState(draft); const [busy, setBusy] = React.useState(''); const [err, setErr] = React.useState(''); const [recap, setRecap] = React.useState(null);
  const conn = (status && status.platforms || []).find(p => p.platform === d.platform) || {}; const accounts = (conn.accounts || []).filter(a => (conn.selected || []).includes(a.id));
  const locked = !['local_draft', 'error'].includes(d.status);
  /* (audit C-A5) : état local immédiat, patches accumulés, une seule écriture toutes les 600 ms, sérialisée ; la publication attend le flush */
  const pend = React.useRef({}); const timer = React.useRef(null); const chain = React.useRef(Promise.resolve()); const [saveState, setSaveState] = React.useState('');
  const flush = React.useCallback(() => { const patch = pend.current; pend.current = {}; if (!Object.keys(patch).length) return chain.current; chain.current = chain.current.then(() => { setSaveState('saving'); return adPost('ads_draft_save', { campaign_id: campaignId, draft_id: d.id, draft: patch }).then(j => { if (j && j.ok) { setSaveState('saved'); onChange && onChange(j.drafts); } else { setSaveState('error'); setErr((j && j.error) || 'sauvegarde impossible'); } }).catch(() => { setSaveState('error'); setErr('réseau'); }); }); return chain.current; }, [campaignId, d.id]);
  const save = (patch) => { setD(x => ({ ...x, ...patch })); Object.assign(pend.current, patch); clearTimeout(timer.current); timer.current = setTimeout(flush, 600); };
  React.useEffect(() => () => { clearTimeout(timer.current); flush(); }, [flush]);
  const askPublish = () => { setBusy('recap'); setErr(''); clearTimeout(timer.current); flush().then(() => adPost('ads_publish', { campaign_id: campaignId, draft_id: d.id })).then(j => { if (j && j.ok && j.recap) setRecap(j.recap); else setErr((j && j.error) || 'récapitulatif impossible'); }).catch(() => setErr('réseau')).finally(() => setBusy('')); };
  const publish = () => { setBusy('publish'); adPost('ads_publish', { campaign_id: campaignId, draft_id: d.id, confirmed: true }).then(j => { setBusy(''); setRecap(null); if (j && j.ok) { setD(j.draft); onPublished && onPublished(j.draft); } else { setErr((j && j.error) || 'création impossible'); if (j && j.draft) setD(j.draft); } }).catch(() => { setBusy(''); setErr('réseau'); }); };
  const [lbl, tone] = AD_STATUS[d.status] || [d.status, 'muted']; const sp = specs || {};
  const listField = (label, key, arr, max, minCount, maxCount, path) => <div className="st-field"><span>{label} <small>{arr.length}/{maxCount} · ≤ {max} car.{minCount ? ' · min ' + minCount : ''}</small></span>{arr.map((v, i) => <div key={i} className="ad-list-row"><input value={v} disabled={locked} maxLength={max + 20} onChange={e => { const a = arr.slice(); a[i] = e.target.value; path(a); }} />{adLen(v, max)}{!locked && <button type="button" className="attr-x" onClick={() => path(arr.filter((_, j) => j !== i))}>×</button>}</div>)}{!locked && arr.length < maxCount && <button type="button" className="st-add" onClick={() => path([...arr, ''])}><Icon name="plus" size={13} /> Ajouter</button>}</div>;
  return <div className="card ad-editor">
    <div className="list-head" style={{ alignItems: 'center' }}><div><div className="sec-title">{d.platform === 'meta' ? 'Annonce Meta Ads' : 'Campagne Google Ads'} <span className={'st-ver-s ' + tone} style={{ display: 'inline', padding: '2px 8px', background: 'var(--surface-2)', borderRadius: 999 }}>{lbl}</span></div><div className="sec-sub">{d.platform === 'meta' ? 'Objectif, texte, bouton et destination — placements selon le média.' : 'Groupe d’assets (Demand Gen / Performance Max) + annonce Search responsive — structures propres à Google.'}{d.remote && d.remote.mock ? ' · création simulée' : ''}</div></div><div className="head-actions">{onClose && <Btn size="sm" kind="ghost" onClick={onClose}>Fermer</Btn>}</div></div>
    {d.error && <div className="err-txt">{d.error}</div>}
    {(d.warnings || []).map((w, i) => <div key={i} className="st-notice"><Icon name="alert" size={14} /> <span>{w}</span></div>)}
    <div className="ad-grid">
      <div className="ad-col">
        {d.platform === 'meta' ? <>
          <div className="st-set-row"><span>Objectif</span><div className="chips">{Object.entries(sp.objectives || {}).filter(([k]) => ['OUTCOME_LEADS', 'OUTCOME_TRAFFIC'].includes(k)).map(([k, l]) => <button key={k} type="button" className={'chip' + (d.objective === k ? ' on' : '')} disabled={locked} onClick={() => save({ objective: k })}>{l}</button>)}</div></div>
          <div className="st-field"><span>Texte principal {adLen(d.creative.primary_text, 125)}</span><textarea rows={3} disabled={locked} value={d.creative.primary_text || ''} onChange={e => save({ creative: { ...d.creative, primary_text: e.target.value } })} /></div>
          <div className="st-field"><span>Titre {adLen(d.creative.headline, 40)}</span><input disabled={locked} value={d.creative.headline || ''} onChange={e => save({ creative: { ...d.creative, headline: e.target.value } })} /></div>
          <div className="st-field"><span>Description {adLen(d.creative.description, 30)}</span><input disabled={locked} value={d.creative.description || ''} onChange={e => save({ creative: { ...d.creative, description: e.target.value } })} /></div>
          <div className="st-set-row"><span>Bouton</span><div className="chips">{(sp.fields && sp.fields.cta && sp.fields.cta.enum || []).map(k => <button key={k} type="button" className={'chip' + (d.creative.cta === k ? ' on' : '')} disabled={locked} onClick={() => save({ creative: { ...d.creative, cta: k } })}>{({ LEARN_MORE: 'En savoir plus', GET_QUOTE: 'Obtenir un devis', SIGN_UP: 'S’inscrire', CONTACT_US: 'Nous contacter', BOOK_NOW: 'Réserver', APPLY_NOW: 'Postuler' })[k] || k}</button>)}</div></div>
          <div className="st-field"><span>Destination (avec UTM Genius)</span><input disabled={locked} value={d.creative.link || ''} onChange={e => save({ creative: { ...d.creative, link: e.target.value } })} placeholder="https://…" /></div>
          {d.alternatives && (d.alternatives.primary_text || []).length > 0 && <details className="st-adv"><summary>Variantes proposées</summary>{(d.alternatives.primary_text || []).map((t, i) => <button key={'p' + i} type="button" className="ad-alt" disabled={locked} onClick={() => save({ creative: { ...d.creative, primary_text: t } })}>{t}</button>)}{(d.alternatives.headline || []).map((t, i) => <button key={'h' + i} type="button" className="ad-alt" disabled={locked} onClick={() => save({ creative: { ...d.creative, headline: t } })}><b>{t}</b></button>)}</details>}
        </> : <>
          <div className="st-set-row"><span>Type</span><div className="chips">{Object.entries(sp.types || {}).filter(([k]) => k !== 'SEARCH').map(([k, l]) => <button key={k} type="button" className={'chip' + (d.campaign_type === k ? ' on' : '')} disabled={locked} onClick={() => save({ campaign_type: k })}>{l}</button>)}</div></div>
          {listField('Titres', 'headlines', d.assets.headlines || [], 30, 3, 5, a => save({ assets: { ...d.assets, headlines: a } }))}
          {listField('Titres longs', 'long_headlines', d.assets.long_headlines || [], 90, 1, 5, a => save({ assets: { ...d.assets, long_headlines: a } }))}
          {listField('Descriptions', 'descriptions', d.assets.descriptions || [], 90, 2, 5, a => save({ assets: { ...d.assets, descriptions: a } }))}
          <div className="st-field"><span>Nom de l’entreprise {adLen(d.assets.business_name, 25)}</span><input disabled={locked} value={d.assets.business_name || ''} onChange={e => save({ assets: { ...d.assets, business_name: e.target.value } })} /></div>
          <div className="st-field"><span>URL finale (avec UTM Genius)</span><input disabled={locked} value={d.assets.final_url || ''} onChange={e => save({ assets: { ...d.assets, final_url: e.target.value }, search: { ...d.search, final_url: e.target.value } })} placeholder="https://…" /></div>
          {(d.assets.videos || []).length > 0 && <div className="st-field"><span>URL YouTube de la vidéo (obligatoire pour Google)</span><input disabled={locked} value={d.youtube_url || ''} onChange={e => save({ youtube_url: e.target.value })} placeholder="https://www.youtube.com/watch?v=…" /></div>}
          <details className="st-adv"><summary>Annonce Search responsive (texte seul, sans média)</summary>
            {listField('Titres Search', 'search_headlines', d.search.headlines || [], 30, 3, 15, a => save({ search: { ...d.search, headlines: a } }))}
            {listField('Descriptions Search', 'search_descriptions', d.search.descriptions || [], 90, 2, 4, a => save({ search: { ...d.search, descriptions: a } }))}
            <div className="st-field"><span>Mots-clés suggérés</span><div className="chips">{(d.search.keywords || []).map((k, i) => <span key={i} className="chip">{k}</span>)}</div></div>
          </details>
        </>}
      </div>
      <div className="ad-col ad-side">
        <div className="st-field"><span>Médias du Studio</span>{(d.media || []).length ? <div className="st-refpick">{d.media.map((m, i) => <span key={i} className="st-ref on" title={(m.kind === 'video' ? 'vidéo' : 'image') + (m.ratio ? ' · ' + m.ratio : '')}><img src={m.kind === 'video' ? (m.poster || '') : m.url} alt="" /></span>)}</div> : <span className="sec-sub">Aucun média : préparez depuis le Studio créatif.</span>}
          {d.platform === 'meta' && sp.placements && <div className="sec-sub" style={{ marginTop: 6 }}>Placements : {(d.placements || []).map(p => (sp.placements[p] || {}).label || p).join(', ') || '—'}. Fil : 1:1 / 4:5 · Stories/Reels : 9:16 (adapter le format dans le Studio, jamais par déformation).</div>}
          {d.platform === 'google' && sp.assets && <div className="sec-sub" style={{ marginTop: 6 }}>Images attendues : {sp.assets.images.ratios.join(', ')} ({sp.assets.images.note}). {sp.assets.videos.note}</div>}
        </div>
        <div className="st-field"><span>Compte</span><select disabled={locked} value={d.account || ''} onChange={e => save({ account: e.target.value })}><option value="">— choisir un compte autorisé —</option>{accounts.map(a => <option key={a.id} value={a.id}>{a.name} ({a.currency || '?'})</option>)}</select>{!accounts.length && <span className="sec-sub">Aucun compte autorisé : Réglages → Connexions publicitaires.</span>}</div>
        {d.platform === 'meta' && <div className="st-field"><span>Page Facebook (page_id) — obligatoire pour créer l’annonce</span><input disabled={locked} value={d.page_id || ''} onChange={e => save({ page_id: e.target.value.replace(/\D/g, '') })} placeholder="ID numérique de la page qui signe l’annonce" /></div>}
        <div className="st-field"><span>Campagne existante (facultatif, Meta)</span>{d.platform === 'meta' ? <input disabled={locked} value={d.existing_campaign_id || ''} onChange={e => save({ existing_campaign_id: e.target.value })} placeholder="ID de campagne Meta pour y ajouter l’ensemble" /> : <span className="sec-sub">Google : nouvelle campagne (en pause) ; l’ajout à une campagne existante se fait sur la plateforme.</span>}</div>
        <div className="st-field"><span>Budget quotidien (saisi par vous, jamais modifié par Genius)</span><input type="number" min="1" step="1" disabled={locked} value={d.platform === 'meta' ? (d.adset.daily_budget || '') : (d.budget.daily || '')} onChange={e => d.platform === 'meta' ? save({ adset: { ...d.adset, daily_budget: Number(e.target.value) || null } }) : save({ budget: { ...d.budget, daily: Number(e.target.value) || null } })} placeholder="ex. 30" /></div>
        <div className="ad-dates"><div className="st-field"><span>Début</span><input type="date" disabled={locked} value={(d.platform === 'meta' ? d.adset.start : d.budget.start) || ''} onChange={e => d.platform === 'meta' ? save({ adset: { ...d.adset, start: e.target.value || null } }) : save({ budget: { ...d.budget, start: e.target.value || null } })} /></div><div className="st-field"><span>Fin</span><input type="date" disabled={locked} value={(d.platform === 'meta' ? d.adset.end : d.budget.end) || ''} onChange={e => d.platform === 'meta' ? save({ adset: { ...d.adset, end: e.target.value || null } }) : save({ budget: { ...d.budget, end: e.target.value || null } })} /></div></div>
        {d.platform === 'meta' && <div className="st-field"><span>Audience</span><div className="ad-dates"><input disabled={locked} value={d.adset.audience.geo || ''} onChange={e => save({ adset: { ...d.adset, audience: { ...d.adset.audience, geo: e.target.value } } })} placeholder="Zone (ex. France)" /><input type="number" disabled={locked} value={d.adset.audience.age_min || 25} onChange={e => save({ adset: { ...d.adset, audience: { ...d.adset.audience, age_min: Number(e.target.value) } } })} /><input type="number" disabled={locked} value={d.adset.audience.age_max || 65} onChange={e => save({ adset: { ...d.adset, audience: { ...d.adset.audience, age_max: Number(e.target.value) } } })} /></div><span className="sec-sub">Aucun contact CRM n’est transmis à la plateforme.</span></div>}
        {d.remote && <div className="ad-remote"><div className="sec-sub">Sur la plateforme : campagne {d.remote.campaign_id}{d.remote.adset_id ? ' · ensemble ' + d.remote.adset_id : ''}{d.remote.ad_id ? ' · annonce ' + d.remote.ad_id : ''}{d.remote_status ? ' · statut ' + d.remote_status : ''}</div>{d.published_at && <div className="sec-sub">Créée le {new Date(d.published_at).toLocaleString('fr-FR')} — en pause, aucune diffusion tant que vous ne l’activez pas sur la plateforme.</div>}</div>}
        {err && <div className="err-txt">{err}</div>}
        <div className="head-actions" style={{ justifyContent: 'flex-end' }}>
          {!locked && <Btn kind="ghost" size="sm" onClick={() => { if (window.confirm('Supprimer ce brouillon ?')) adPost('ads_draft_save', { campaign_id: campaignId, draft_id: d.id, remove: true }).then(j => { if (j && j.ok) { onChange && onChange(j.drafts); onClose && onClose(); } }); }}>Supprimer</Btn>}
          {!locked && <span className="sec-sub">{saveState === 'saving' ? 'Enregistrement…' : saveState === 'saved' ? 'Brouillon enregistré' : saveState === 'error' ? 'Non enregistré' : ''}</span>}
          {!locked && <Btn size="sm" icon="send" onClick={askPublish} disabled={busy !== ''}>{busy === 'recap' ? 'Préparation…' : 'Créer sur la plateforme (en pause)'}</Btn>}
          {locked && d.status !== 'creating' && <Btn size="sm" kind="ghost" icon="refresh" onClick={() => adPost('ads_draft_status', { campaign_id: campaignId, draft_id: d.id }).then(j => { if (j && j.ok) setD(j.draft); })}>Actualiser le statut</Btn>}
        </div>
      </div>
    </div>
    {recap && <div className="st-modal-bg" onClick={() => setRecap(null)}><div className="st-modal" onClick={e => e.stopPropagation()}>
      <div className="sec-title">Créer cette annonce sur la plateforme ?</div><div className="sec-sub">Elle sera créée <b>en pause</b> : aucune diffusion et aucune dépense tant que vous ne l’activez pas vous-même sur la plateforme.</div>
      <div className="st-modal-rows">{[['Plateforme', recap.platform], ['Compte', recap.account], ['Campagne', recap.campaign], ['Créations', recap.creatives], ['Destination', recap.destination], ['Audience', recap.audience], recap.page ? ['Page', recap.page] : null, ['Budget', recap.budget], ['Dates', recap.dates], ['Suivi', recap.tracking], ['Après création', recap.state_after]].filter(Boolean).map(([k, v]) => <div key={k}><span>{k}</span><b className={/NON DÉFINI/.test(String(v)) ? 'unknown' : ''}>{v}</b></div>)}</div>
      {status && status.mode === 'mock' && <div className="st-notice" style={{ margin: '10px 0 0' }}><Icon name="alert" size={14} /> <span>Mode test : rien n’est créé sur une vraie plateforme.</span></div>}
      <div className="st-modal-a"><Btn kind="ghost" onClick={() => setRecap(null)}>Annuler</Btn><Btn onClick={publish} disabled={busy === 'publish'} icon="send">{busy === 'publish' ? 'Création…' : 'Je valide, créer en pause'}</Btn></div>
    </div></div>}
  </div>;
}

/* (audit C-A40) composant stable (hors rendu du parent) : plus de remontage des tableaux à chaque état */
function AdsTable({ p, acc, links, onToggle }) { const d = acc.data; if (!d) return <div className="err-txt">{acc.error || 'pas de données'}</div>; return <div className="ads-tbl-wrap"><table className="ads-tbl"><thead><tr><th></th><th>Campagne</th><th>Statut</th><th>Dépense</th><th>Impr.</th><th>Clics</th><th>CTR</th><th>Résultats</th><th>Coût / résultat</th></tr></thead><tbody>{d.rows.map(r => { const on = links.some(l => l.platform === p && l.campaign_id === r.id); return <tr key={r.id} className={on ? 'on' : ''}><td><input type="checkbox" checked={on} onChange={() => onToggle(p, acc, r)} title="Rattacher à cette campagne Genius" /></td><td><b>{r.name}</b>{r.objective ? <div className="sec-sub">{r.objective}</div> : r.type ? <div className="sec-sub">{r.type}</div> : null}</td><td>{r.status}</td><td>{adFmtMoney(r.spend, d.currency)}</td><td>{adFmtN(r.impressions)}</td><td>{adFmtN(r.clicks)}</td><td>{r.ctr != null ? Number(r.ctr).toFixed(2).replace('.', ',') + ' %' : '—'}</td><td>{r.results != null ? adFmtN(r.results) : '—'}{r.result_label ? <div className="sec-sub" title={r.result_label}>{r.result_label.split(' (')[0]}</div> : null}</td><td>{adFmtMoney(r.cost_per_result, d.currency)}</td></tr>; })}{!d.rows.length && <tr><td colSpan={9} className="sec-sub">Aucune campagne sur la période.</td></tr>}</tbody></table>{d.truncated && <div className="sec-sub">Liste tronquée (plus de 5 pages) : totaux incomplets.</div>}</div>; }
/* ---------- Onglet « Publicité » d'une campagne ---------- */
function AdsPanel({ campaignId, campaign }) {
  const [st, setSt] = React.useState(null); const [data, setData] = React.useState(null); const [period, setPeriod] = React.useState('7d'); const [err, setErr] = React.useState('');
  const [links, setLinks] = React.useState([]); const [drafts, setDrafts] = React.useState([]); const [sugg, setSugg] = React.useState(null); const [track, setTrack] = React.useState(null); const [busy, setBusy] = React.useState(''); const [edit, setEdit] = React.useState(null);
  React.useEffect(() => { adPost('ads_status', {}).then(j => { if (j && j.ok) setSt(j); }); adPost('ads_drafts', { campaign_id: campaignId }).then(j => { if (j && j.ok) { setDrafts(j.drafts || []); setLinks(j.links || []); } }); adPost('ads_tracking_check', { campaign_id: campaignId }).then(j => { if (j && j.ok) setTrack(j); }); }, [campaignId]);
  const periodGen = React.useRef(0);
  React.useEffect(() => { const g = ++periodGen.current; setData(null); setSugg(null); adPost('ads_campaigns', { period }).then(j => { if (g !== periodGen.current) return; if (j && j.ok) setData(j); else setErr((j && j.error) || 'lecture impossible'); }).catch(() => { if (g === periodGen.current) setErr('réseau'); }); }, [period]);   /* (audit C-A30) */
  React.useEffect(() => { setSugg(null); }, [links.length]);
  const connected = st && st.platforms.some(p => p.status === 'connected');
  const toggleLink = (p, acc, row) => { const on = links.some(l => l.platform === p && l.campaign_id === row.id); const n = on ? links.filter(l => !(l.platform === p && l.campaign_id === row.id)) : [...links, { platform: p, account: acc.id, campaign_id: row.id, name: row.name }]; setLinks(n); adPost('ads_link', { campaign_id: campaignId, links: n }); };
  const suggest = () => { setBusy('sugg'); setSugg(null); adPost('ads_insights_suggest', { campaign_id: campaignId, period }).then(j => { setBusy(''); if (j && j.ok) setSugg(j); else setErr((j && (j.detail || j.error)) || 'suggestions impossibles'); }).catch(() => { setBusy(''); setErr('réseau'); }); };
  const Table = ({ p, acc }) => <AdsTable p={p} acc={acc} links={links} onToggle={toggleLink} />;
  return <div className="ads-panel">
    {!st ? <Skeleton rows={2} /> : <>
      {!connected && <div className="card"><EmptyState title="Aucune plateforme publicitaire connectée" sub={st.mode === 'off' ? 'Les applications Meta / Google ne sont pas configurées côté serveur.' : 'Connectez Meta Ads ou Google Ads pour cet espace dans Réglages → Connexions publicitaires.'} /></div>}
      {connected && <>
        <div className="list-head" style={{ alignItems: 'center' }}><div><div className="sec-title">Résultats publicitaires</div><div className="sec-sub">{data ? 'Du ' + data.period.since + ' au ' + data.period.until : 'Période…'} <AdModeBadge mode={st.mode} /></div></div><div className="chips">{[['7d', '7 jours'], ['14d', '14 jours'], ['30d', '30 jours'], ['90d', '90 jours']].map(([k, l]) => <button key={k} type="button" className={'chip' + (period === k ? ' on' : '')} onClick={() => setPeriod(k)}>{l}</button>)}</div></div>
        {err && <div className="err-txt">{err}</div>}
        {!data && <Skeleton rows={3} />}
        {data && ['meta', 'google'].map(p => { const v = data.platforms[p]; if (!v || v.status !== 'connected') return null; return <div key={p} className="card ads-plat-card"><div className="sec-title" style={{ fontSize: 15 }}>{p === 'meta' ? 'Meta Ads' : 'Google Ads'}</div>
          {v.accounts.map(acc => <div key={acc.id} className="ads-acc-block"><div className="sec-sub" style={{ marginBottom: 6 }}><b style={{ color: 'var(--ink)' }}>{acc.name}</b> · {acc.currency || '—'}{acc.timezone ? ' · ' + acc.timezone : ''}{acc.data ? ' · lu le ' + new Date(acc.data.fetched_at).toLocaleString('fr-FR', { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' }) : ''}</div><Table p={p} acc={acc} />{acc.data && <div className="ads-fresh"><div>{acc.data.freshness}</div><div>{acc.data.conversion_definition}</div></div>}</div>)}
        </div>; })}
        {data && <div className="st-notice"><Icon name="alert" size={14} /> <span>{data.note}</span></div>}
        <div className="card"><div className="list-head" style={{ alignItems: 'center' }}><div><div className="sec-title">Ce que les résultats suggèrent</div><div className="sec-sub">{links.length ? links.length + ' campagne(s) rattachée(s) : ' + links.map(l => l.name).join(', ') : 'Cochez les campagnes publicitaires qui correspondent à cette campagne Genius.'}</div></div><Btn size="sm" icon="bolt" onClick={suggest} disabled={!links.length || busy === 'sugg'}>{busy === 'sugg' ? 'Analyse…' : 'Analyser'}</Btn></div>
          {busy === 'sugg' && <CampaignGenerationLoader variant="compact" title="Genius lit les résultats…" sub="Faits observés d’un côté, hypothèses de l’autre." />}
          {sugg && <div className="ads-sugg"><div><div className="ads-sugg-t">Faits observés ({sugg.period.since} → {sugg.period.until})</div><ul>{sugg.facts.map((x, i) => <li key={i}>{x}</li>)}</ul></div><div><div className="ads-sugg-t">Hypothèses (à vérifier)</div><ul className="hyp">{sugg.hypotheses.map((x, i) => <li key={i}>{x}</li>)}</ul></div><div><div className="ads-sugg-t">Nouvelles accroches</div><ul>{sugg.headlines.map((x, i) => <li key={i}>{x}</li>)}</ul></div><div><div className="ads-sugg-t">Variantes</div>{sugg.variants.map((v, i) => <div key={i} className="ads-var"><b>{v.angle}</b><div>{v.primary_text}</div><div className="sec-sub">{v.why}</div></div>)}</div>{sugg.caution && <div className="sec-sub" style={{ gridColumn: '1/-1' }}>{sugg.caution}</div>}</div>}
        </div>
      </>}
      <div className="card"><div className="sec-title">Suivi et mesure</div>{!track ? <Skeleton rows={2} /> : <ul className="ads-track">{track.notes.map((n, i) => <li key={i}>{n}</li>)}</ul>}</div>
      <div className="card"><div className="list-head" style={{ alignItems: 'center' }}><div><div className="sec-title">Annonces préparées</div><div className="sec-sub">Brouillons dans Genius, puis création en pause sur la plateforme après votre validation.</div></div></div>
        {!drafts.length && <div className="sec-sub">Aucune annonce préparée. Depuis le Studio créatif, choisissez « Préparer pour Meta Ads » ou « Préparer pour Google Ads ».</div>}
        {drafts.map(d => { const [lbl, tone] = AD_STATUS[d.status] || [d.status, 'muted']; return <div key={d.id} className="ads-draft-row"><span className={'st-ver-s ' + tone}>{lbl}</span><b>{d.platform === 'meta' ? 'Meta' : 'Google'}</b><span>{d.platform === 'meta' ? d.creative.headline : (d.assets.headlines || [])[0]}</span><span className="sec-sub">{new Date(d.created_at).toLocaleDateString('fr-FR')}</span><Btn size="sm" kind="ghost" onClick={() => setEdit(d)}>{['local_draft', 'error'].includes(d.status) ? 'Modifier / publier' : 'Voir'}</Btn></div>; })}
      </div>
      {edit && <AdDraftEditor key={campaignId + ':' + edit.id} campaignId={campaignId} draft={edit} specs={st.specs[edit.platform]} status={st} onChange={ds => setDrafts(ds)} onClose={() => setEdit(null)} onPublished={d => setDrafts(ds => ds.map(x => x.id === d.id ? d : x))} />}
    </>}
  </div>;
}
Object.assign(window, { AdsConnections, AdsPanel, AdDraftEditor, adPost });
