/* Console partenaire — développeurs (design dev-pages-a.jsx, dev-pages-b.jsx, dev-kyc.jsx) : Clés d'API,
   Journal des requêtes, Webhooks (en service : pas de mention « bientôt »), Paramètres développeur. */
function KeyReveal({k, onClose}){
  const [ok, setOk] = React.useState(false);
  return <Modal title="Copiez votre clé maintenant" icon="key" onClose={null} foot={<Btn disabled={!ok} onClick={onClose}>Terminé</Btn>}>
    <Notice tone="warn" title="Elle ne sera plus jamais affichée">Conservez-la dans un coffre-fort de secrets. Si vous la perdez, révoquez-la et créez-en une nouvelle.</Notice>
    <div><Lbl>{k.nom}</Lbl><div style={{display:'flex', alignItems:'center', gap:10, marginTop:8, padding:'14px 14px 14px 16px', borderRadius:16, background:'#0E1F19'}}>
      <span style={{flex:1, minWidth:0, fontFamily:V.mono, fontSize:13, color:'#E4EEE9', wordBreak:'break-all', lineHeight:1.5}}>{k.cle}</span><CopyBtn text={k.cle} dark/></div></div>
    {k.ancienne_valable_jusqua && <Muted>L'ancienne clé reste valable jusqu'au <b style={{color:V.ink}}>{D(k.ancienne_valable_jusqua)}</b>.</Muted>}
    <Check on={ok} set={setOk}>J'ai copié ma clé et je l'ai enregistrée en lieu sûr.</Check>
  </Modal>;
}
const statutCle = (k) => k.statut === 'active' && k.expire_le ? 'expire24' : k.statut;
function Keys(){
  const A = useApp(); const toast = useToast(); const env = A.mode, locked = env==='live' && !A.moi.entreprise.production_ouverte; const dev = A.peut('dev');
  const [d, recharger, etat] = useDonnees(() => locked ? Promise.resolve({donnees:[]}) : api('GET', `cles?mode=${env}`), [env]);
  const [ips] = useDonnees(() => env==='live' && !locked ? api('GET', 'ips') : Promise.resolve(null), [env]);
  const [modal, setModal] = React.useState(null), [name, setName] = React.useState(''), [code, setCode] = React.useState(''), [reveal, setReveal] = React.useState(null), [load, setLoad] = React.useState(false), [err, setErr] = React.useState(null), [sv, setSv] = React.useState({assurance:true});
  const fermer = () => { setModal(null); setName(''); setCode(''); setErr(null); setSv({assurance:true}); };
  const agir = async (fn, ok) => { setLoad(true); setErr(null); try { const r = await fn(); fermer(); recharger(); A.rafraichir(); ok(r); } catch(e){ setErr(e); setCode(''); } setLoad(false); };
  const creer = () => agir(() => api('POST', 'cles', {nom:name.trim(), mode:env, services:Object.keys(sv).filter(x=>sv[x]), ...(env==='live' ? {code_2fa:code} : {})}), r=>setReveal({...r, nom:r.nom || name}));
  const tourner = (k) => agir(() => api('POST', `cles/${k.id}/rotation`, env==='live' ? {code_2fa:code} : {}), r=>setReveal({...r, nom:r.nom || k.nom}));
  const revoquer = (k) => agir(() => api('DELETE', `cles/${k.id}`), ()=>toast('Clé révoquée'));
  const liste = d?.donnees || [];
  if(locked) return <Page><PageHead title="Clés d'API" sub="Authentifiez vos appels avec Authorization: Bearer <clé>."/>
    <Card><EmptyS icon="lock-simple" title="Production verrouillée" sub="Les clés lms_live_… se créent une fois votre dossier validé par LeMultiservice. En attendant, travaillez avec vos clés sandbox." action={<div style={{display:'flex', gap:8, flexWrap:'wrap', justifyContent:'center'}}><Btn kind="secondary" onClick={()=>A.setMode('test')}>Voir mes clés sandbox</Btn><Btn icon="buildings" onClick={()=>A.go('prod')}>Compléter le profil entreprise</Btn></div>}/></Card></Page>;
  const SVL = Object.entries(SVCS); const ipsL = Array.isArray(ips?.donnees) ? ips.donnees : [];
  const exemple = liste.find(k=>k.statut==='active');
  return <Page>
    <PageHead title="Clés d'API" sub="Même URL de base et mêmes endpoints en sandbox et en production. Seule la clé change." actions={dev && <Btn icon="plus" onClick={()=>setModal({t:'new'})}>Créer une clé</Btn>}/>
    {!dev && <Notice tone="info">Votre rôle permet de consulter les clés. Seuls le propriétaire et les développeurs peuvent en créer ou en révoquer.</Notice>}
    {env==='live' && ips && <Notice tone={ipsL.length?'info':'warn'} icon="shield-check" title="IP autorisées en production" action={<Btn kind="secondary" sm onClick={()=>A.go('devset')}>Gérer</Btn>}>{ipsL.length ? <>Seules {ipsL.join(', ')} peuvent utiliser vos clés live. Tout autre appel renvoie <Mono>ip_non_autorisee</Mono>.</> : <>Aucune IP autorisée : tous les appels avec une clé live sont refusés (<Mono>ip_non_autorisee</Mono>).</>}</Notice>}
    {etat.erreur ? <ErreurCarte e={etat.erreur} recharger={recharger}/> :
    <Tbl chargement={!d} cols={[{k:'n',l:'Nom',w:'1.3fr'},{k:'p',l:'Préfixe',w:'1.1fr'},{k:'v',l:'Services',w:'1fr'},{k:'c',l:'Créée le',w:'.8fr',hideM:1},{k:'u',l:'Dernière utilisation',w:'1fr'},{k:'b',l:'Créée par',w:'1fr',hideM:1},{k:'s',l:'Statut',w:'1.1fr'},{k:'x',l:'',w:'88px',r:1}]}
      rows={liste.map(k=>{ const st = statutCle(k); return {n:<Ellipse style={{fontWeight:700}}>{k.nom}</Ellipse>, p:<Mono c={V.ink}>{k.prefixe}…</Mono>,
        v:(k.services||[]).length ? <span style={{display:'flex', gap:4, flexWrap:'wrap'}}>{k.services.filter(x=>SVCS[x]).map(x=><span key={x} title={SVCS[x].n} style={{display:'inline-grid', placeItems:'center', width:26, height:26, borderRadius:8, background:SVCS[x].bg}}><I n={SVCS[x].i} s={14} c={SVCS[x].c}/></span>)}</span> : <span title="Aucune restriction : tous les services" style={{fontSize:12.5, fontWeight:700, color:V.ink2}}>Tous</span>,
        c:<span style={{color:V.ink2}}>{dateCourte(k.cree_le)}</span>, u:<span style={{color:V.ink2}}>{k.derniere_utilisation ? `Il y a ${Depuis(k.derniere_utilisation)}` : 'Jamais'}</span>, b:<Ellipse style={{color:V.ink2}}>{k.cree_par || '—'}</Ellipse>,
        s:st==='expire24' ? <Badge tone="warn" icon="clock">Expire le {DC(k.expire_le)}</Badge> : ST[st] ? <StatusB st={st}/> : <Badge>{st}</Badge>,
        x:!dev || k.statut==='revoquee' ? null : <span style={{display:'flex', gap:4}}>{!k.expire_le && <Btn kind="ghost" sm icon="arrows-clockwise" label="Faire tourner la clé" onClick={()=>setModal({t:'rot', k})}/>}<Btn kind="ghost" sm icon="trash" label="Révoquer" onClick={()=>setModal({t:'rev', k})} style={{color:V.bad}}/></span>}; })}
      empty={<EmptyS icon="key" title={`Aucune clé ${env==='test'?'sandbox':'de production'}`} sub={env==='test'?'Créez votre première clé pour faire un appel en moins de 5 minutes.':'Créez votre première clé live pour commencer à vendre.'} action={dev && <Btn icon="plus" onClick={()=>setModal({t:'new'})}>Créer une clé</Btn>}/>}/>}
    <Card style={{background:V.sub}}><Lbl>Exemple d'appel</Lbl><div style={{marginTop:10}}><Code code={{cURL:`curl ${API_V1_BASE}/assurance/auto/catalogue \\\n  -H "Authorization: Bearer ${exemple ? exemple.prefixe+'…' : 'lms_'+env+'_…'}"`}} langs={false}/></div></Card>
    {modal?.t==='new' && <Modal title="Créer une clé" icon="key" onClose={fermer} foot={<><Btn kind="secondary" onClick={fermer}>Annuler</Btn><Btn disabled={!name.trim() || !Object.values(sv).some(Boolean) || (env==='live' && code.length<6) || load} onClick={creer}>{load?'Création…':'Créer la clé'}</Btn></>}>
      <Field label="Nom de la clé" hint="Pour la reconnaître, par exemple le nom du serveur ou de l'application."><Inp value={name} onChange={e=>setName(e.target.value)} placeholder="Serveur de préproduction" autoFocus/></Field>
      <Field label="Environnement"><div style={{display:'flex', alignItems:'center', gap:10, height:46, padding:'0 14px', borderRadius:16, background:env==='test'?V.warnBg:V.mint, color:env==='test'?V.warn:V.accD, fontWeight:700, fontSize:13.5}}><I n={env==='test'?'flask':'globe'} s={18}/>{env==='test'?'Sandbox · préfixe lms_test_':'Production · préfixe lms_live_'}</div></Field>
      <div><Lbl style={{marginBottom:6}}>Services autorisés</Lbl><Muted style={{fontSize:12.5, marginBottom:10}}>La clé ne pourra appeler que les services cochés. Les autres renvoient <Mono>cle_hors_portee</Mono>.</Muted>
        <div style={{display:'flex', flexDirection:'column', gap:8}}>{SVL.map(([k,s])=>{ const on = !!sv[k], dis = !s.on;
          return <label key={k} className={dis?undefined:'lc'} style={{position:'relative', display:'flex', alignItems:'center', gap:12, padding:'10px 14px', borderRadius:16, border:`1.5px solid ${on?s.c:V.line}`, background:on?s.bg:V.card, cursor:dis?'not-allowed':'pointer', opacity:dis?.55:1, transition:'all .2s'}}>
            <input type="checkbox" checked={on} disabled={dis} onChange={e=>setSv({...sv,[k]:e.target.checked})} style={{position:'absolute', opacity:0, width:1, height:1}}/>
            <span style={{width:20, height:20, borderRadius:7, flex:'none', display:'grid', placeItems:'center', border:`1.5px solid ${on?s.c:V.line}`, background:on?s.c:V.card}}>{on && <I n="check" s={13} c="#fff"/>}</span>
            <Tile icon={s.i} s={32} c={s.c} bg={on?V.card:s.bg}/><span style={{flex:1, fontWeight:700, fontSize:13.5, color:V.ink}}>{s.n}</span>{dis ? <Badge>Bientôt</Badge> : null}</label>; })}</div></div>
      {env==='live' && <Field label="Code de double authentification" hint="Requis pour toute clé de production."><OTP value={code} set={setCode} err={!!err}/></Field>}
      {err && <Notice tone="bad">{err.message}</Notice>}
    </Modal>}
    {modal?.t==='rev' && <Modal title="Révoquer cette clé ?" icon="trash" tone="bad" onClose={fermer} foot={<><Btn kind="secondary" onClick={fermer}>Annuler</Btn><Btn kind="danger" disabled={load} onClick={()=>revoquer(modal.k)}>{load?'Révocation…':'Révoquer maintenant'}</Btn></>}>
      <Muted>Tous les appels avec <Mono c={V.ink}>{modal.k.prefixe}…</Mono> seront refusés immédiatement avec l'erreur <Mono>cle_revoquee</Mono>. Cette action est définitive.</Muted>{err && <Notice tone="bad">{err.message}</Notice>}</Modal>}
    {modal?.t==='rot' && <Modal title="Faire tourner la clé" icon="arrows-clockwise" onClose={fermer} foot={<><Btn kind="secondary" onClick={fermer}>Annuler</Btn><Btn disabled={load || (env==='live' && code.length<6)} onClick={()=>tourner(modal.k)}>{load?'Génération…':'Générer la nouvelle clé'}</Btn></>}>
      <Muted>Une nouvelle clé remplace <Mono c={V.ink}>{modal.k.prefixe}…</Mono>. L'ancienne reste valable <b style={{color:V.ink}}>24 heures</b> pour vous laisser le temps de la déployer.</Muted>
      {env==='live' && <Field label="Code de double authentification"><OTP value={code} set={setCode} err={!!err}/></Field>}
      {err && <Notice tone="bad">{err.message}</Notice>}</Modal>}
    {reveal && <KeyReveal k={reveal} onClose={()=>setReveal(null)}/>}
  </Page>;
}

const PERIODES_J = [['heure','Dernière heure'],['jour','Aujourd\'hui'],['7j','7 derniers jours'],['30j','30 derniers jours']];
function Logs({param}){
  const A = useApp(), m = useMob();
  const [sel, setSel] = React.useState(param || null), [f, setF] = React.useState(0), [cle, setCle] = React.useState(''), [per, setPer] = React.useState('7j'), [q, setQ] = React.useState(''), [qd, setQd] = React.useState(''), [page, setPage] = React.useState(1);
  React.useEffect(()=>{ const t = setTimeout(()=>{ setQd(q); setPage(1); }, 350); return ()=>clearTimeout(t); }, [q]);
  const res = ['', 'succes', 'erreur'][f];
  const [d, recharger, etat] = useDonnees(() => api('GET', `journal?mode=${A.mode}&resultat=${res}&cle=${encodeURIComponent(cle)}&periode=${per}&q=${encodeURIComponent(qd)}&page=${page}`), [A.mode, res, cle, per, qd, page]);
  const [cles] = useDonnees(() => api('GET', `cles?mode=${A.mode}`).catch(()=>({donnees:[]})), [A.mode]);
  const liste = d?.donnees || [];
  const cols = [{k:'t',l:'Heure',w:'.8fr'},{k:'e',l:'Endpoint',w:'2.4fr'},{k:'c',l:'HTTP',w:'.5fr'},{k:'x',l:'Code d\'erreur',w:'1.2fr',hideM:1},{k:'d',l:'Durée',w:'.7fr',r:1,hideM:1},{k:'k',l:'Clé',w:'1fr',hideM:1}].filter(c=>!sel||m||['t','e','c','x'].includes(c.k));
  const detail = sel && <LogDetail id={sel}/>;
  return <Page max={1240}>
    <PageHead title="Journal des requêtes" sub="Chaque appel à l'API, avec sa requête et sa réponse. Données personnelles masquées."/>
    <div style={{display:'flex', gap:8, flexWrap:'wrap'}}><div style={{flex:'1 1 240px'}}><Inp value={q} onChange={e=>setQ(e.target.value)} placeholder="Endpoint, code d'erreur, IP, req_…" aria-label="Rechercher dans le journal" lead={<I n="magnifying-glass" s={17}/>} style={{height:40}}/></div>
      <Seg opts={['Tous','Succès','Erreurs']} i={f} set={k=>{setF(k); setPage(1);}}/>
      <div style={{width:m?'100%':190}}><Sel label="Clé" opts={[['','Toutes les clés'], ...(cles?.donnees||[]).map(k=>[k.prefixe, `${k.prefixe}… · ${k.nom}`])]} value={cle} onChange={v=>{setCle(v); setPage(1);}} style={{height:40}}/></div>
      <div style={{width:m?'100%':180}}><Sel label="Période" opts={PERIODES_J} value={per} onChange={v=>{setPer(v); setPage(1);}} style={{height:40}}/></div></div>
    <div style={{display:'grid', gridTemplateColumns:sel&&!m?'minmax(0,1fr) 440px':'minmax(0,1fr)', gap:16, alignItems:'start'}}>
      {etat.erreur ? <ErreurCarte e={etat.erreur} recharger={recharger}/> :
      <Tbl chargement={!d} cols={cols}
        rows={liste.map(r=>({t:<Mono>{heureOuJour(r.date) === new Date(r.date).toLocaleTimeString('fr-FR', {hour:'2-digit', minute:'2-digit'}) ? new Date(r.date).toLocaleTimeString('fr-FR') : DC(r.date)}</Mono>, e:<span style={{display:'flex', gap:6, minWidth:0}}><Method m={r.methode}/><Ellipse style={{fontFamily:V.mono, fontSize:12.5}}>{r.endpoint}</Ellipse></span>, c:<Http code={r.http}/>, x:r.code_erreur?<Mono c={V.bad}>{r.code_erreur}</Mono>:<span style={{color:V.ink3}}>—</span>, d:<Mono>{fmt(r.duree_ms)} ms</Mono>, k:<Mono>{r.cle || '—'}</Mono>}))}
        onRow={i=>setSel(liste[i].id)} empty={<EmptyS icon="terminal-window" title="Aucune requête" sub={qd||res||cle ? 'Aucun appel ne correspond à ces filtres.' : 'Les appels faits avec vos clés apparaîtront ici.'}/>} foot={d?.total > 0 && <Pager n={d.total} per={d.par_page||25} page={page} set={setPage}/>}/>}
      {sel && (m ? <Modal title="Détail de l'appel" onClose={()=>setSel(null)}>{detail}</Modal>
        : <Card style={{position:'sticky', top:0, display:'flex', flexDirection:'column', gap:14}}><div style={{display:'flex', alignItems:'center'}}><H s={16} style={{flex:1}}>Détail de l'appel</H><Btn kind="ghost" sm icon="x" label="Fermer" onClick={()=>setSel(null)}/></div>{detail}</Card>)}
    </div>
  </Page>;
}
function LogDetail({id}){
  const [l, recharger, etat] = useDonnees(() => api('GET', `journal/${encodeURIComponent(id)}`), [id]);
  if(etat.erreur) return <Notice tone="bad" title="Requête introuvable">{etat.erreur.message}</Notice>;
  if(!l) return <><Skel h={24}/><Skel h={150} r={16}/><Skel h={200} r={16}/></>;
  const vide = v => v == null || (typeof v === 'object' && !Object.keys(v).length) || v === '';
  return <><div style={{display:'flex', alignItems:'center', gap:8, flexWrap:'wrap'}}><Method m={l.methode}/><span style={{fontFamily:V.mono, fontSize:13, color:V.ink, wordBreak:'break-all'}}>{l.endpoint}</span><Http code={l.http}/></div>
    <div style={{padding:'4px 14px', borderRadius:16, background:V.sub}}><KV rows={[['Date',D(l.date)],['Durée',`${fmt(l.duree_ms)} ms`],['Clé',(l.cle||'—')+'…',null,1],['IP',l.ip||'—',null,1],['ID de requête',l.id,null,1],...(l.code_erreur?[['Code d\'erreur',l.code_erreur,V.bad,1]]:[])]}/></div>
    {l.methode!=='GET' && !vide(l.requete) && <div><Lbl style={{marginBottom:8}}>Requête</Lbl><Code code={JSON.stringify(l.requete, null, 2)} langs={false} h={200}/></div>}
    <div><Lbl style={{marginBottom:8}}>Réponse</Lbl><Code code={vide(l.reponse) ? '(corps vide)' : JSON.stringify(l.reponse, null, 2)} langs={false} h={240}/></div>
    {l.curl && <CopyCurl curl={l.curl}/>}</>;
}
function CopyCurl({curl}){ const [c, copy] = useCopy(); return <Btn kind="secondary" icon={c?'check':'terminal'} full onClick={()=>copy(curl)}>{c?'Copié':'Copier en cURL'}</Btn>; }

const EVENTS = [['contrat.actif','Le contrat est émis par l\'assureur'],['contrat.echec','L\'émission a échoué, solde non débité'],['contrat.a_verifier','L\'assureur n\'a pas encore confirmé'],['contrat.annule','Le contrat a été annulé'],['contrat.resilie','Le contrat a été résilié'],['depot.valide','Un dépôt a été crédité'],['solde.bas','Le solde est passé sous le seuil']];
const LIV_ST = {livre:['Livré','ok','check-circle'], a_envoyer:['En relance','warn','hourglass'], abandonne:['Abandonné','bad','x-circle']};
function Hooks(){
  const A = useApp(), m = useMob(); const toast = useToast(); const dev = A.peut('dev');
  const [d, recharger, etat] = useDonnees(() => api('GET', `webhooks?mode=${A.mode}`), [A.mode]);
  const [pg, setPg] = React.useState(1);
  const [hist, rH] = useDonnees(() => api('GET', `livraisons?mode=${A.mode}&par_page=10&page=${pg}`), [A.mode, pg]);
  const [modal, setModal] = React.useState(null), [secret, setSecret] = React.useState(null), [occ, setOcc] = React.useState(null), [err, setErr] = React.useState(null);
  const [url, setUrl] = React.useState(''), [name, setName] = React.useState(''), [ev, setEv] = React.useState({});
  const eps = d?.donnees || [];
  const open = e => { setModal(e||'new'); setErr(null); setUrl(e?e.url:'https://'); setName(e?e.nom||'':''); setEv(e?Object.fromEntries(e.evenements.map(x=>[x,true])):{'contrat.actif':true,'contrat.echec':true}); };
  const tout = () => { recharger(); rH(); A.rafraichir(); };
  const agir = async (cle, fn, ok) => { setOcc(cle); try { const r = await fn(); if(ok) ok(r); tout(); return r; } catch(e){ toast(e.message, 'bad'); } finally { setOcc(null); } };
  const save = async () => { setOcc('save'); setErr(null); const evs = Object.keys(ev).filter(k=>ev[k]);
    try { if(modal==='new'){ const r = await api('POST', 'webhooks', {mode:A.mode, url:url.trim(), nom:name.trim(), evenements:evs}); setSecret(r.secret); toast('URL ajoutée'); }
      else { await api('PATCH', `webhooks/${modal.id}`, {mode:A.mode, url:url.trim(), nom:name.trim(), evenements:evs}); toast('URL modifiée'); }
      setModal(null); tout(); } catch(e){ setErr(e); } setOcc(null); };
  const nomDe = id => (eps.find(x=>x.id===id)||{}).nom;
  return <Page>
    <PageHead title="Webhooks" sub="Recevez les événements en temps réel. Ajoutez une URL par système qui doit être prévenu." actions={dev && eps.length < 5 && <Btn icon="plus" onClick={()=>open()}>Ajouter une URL</Btn>}/>
    {etat.erreur ? <ErreurCarte e={etat.erreur} recharger={recharger}/> : !d ? <Card><Skel h={120}/></Card> : eps.length ? <div style={{display:'flex', flexDirection:'column', gap:12}}>{eps.map(e=><Card key={e.id} style={{display:'flex', flexDirection:'column', gap:14, opacity:e.actif?1:.7}}>
      <div style={{display:'flex', alignItems:'center', gap:12, flexWrap:'wrap'}}><Tile icon="webhooks-logo" s={40}/>
        <span style={{flex:1, minWidth:200}}><span style={{display:'block', fontWeight:800, fontSize:14}}>{e.nom || 'Sans nom'}</span><span style={{display:'block', fontFamily:V.mono, fontSize:12.5, color:V.ink2, wordBreak:'break-all'}}>{e.url}</span></span>
        <Badge tone={e.actif?'ok':'mute'} dot>{e.actif?'Actif':'Désactivé'}</Badge>{dev && <Toggle on={e.actif} set={v=>agir('t'+e.id, ()=>api('PATCH', `webhooks/${e.id}`, {mode:A.mode, actif:v}), ()=>toast(v?'Envoi rétabli':'Envoi coupé'))} label={`Activer ${e.nom||e.url}`}/>}</div>
      <div style={{display:'flex', gap:6, flexWrap:'wrap'}}>{e.evenements.map(x=><span key={x} style={{fontFamily:V.mono, fontSize:11.5, fontWeight:500, padding:'4px 9px', borderRadius:8, background:V.sub, color:V.ink2}}>{x}</span>)}</div>
      {e.echecs_consecutifs > 0 && <Notice tone="warn" title={`${e.echecs_consecutifs} échec${e.echecs_consecutifs>1?'s':''} consécutif${e.echecs_consecutifs>1?'s':''}`}>Votre URL ne répond pas en 2xx. Les envois sont relancés automatiquement ; vérifiez votre serveur.</Notice>}
      <div style={{display:'flex', alignItems:'center', gap:8, paddingTop:12, borderTop:`1px solid ${V.line}`, flexWrap:'wrap'}}>
        <span style={{fontSize:12.5, color:V.ink3, fontWeight:600}}>Secret <Mono c={V.ink2}>{e.secret_apercu}</Mono></span><span style={{fontSize:12.5, color:V.ink3, fontWeight:600}}>· {e.dernier_succes ? <>Dernier succès : <b style={{color:V.ink}}>il y a {Depuis(e.dernier_succes)}</b></> : 'Aucun envoi réussi'}</span><span style={{flex:1}}></span>
        {dev && <><Btn kind="ghost" sm icon={occ==='x'+e.id?'spinner':'paper-plane-tilt'} disabled={!e.actif || occ==='x'+e.id} onClick={()=>agir('x'+e.id, ()=>api('POST', `webhooks/${e.id}/test`, {mode:A.mode}), r=>r.statut==='livre' ? toast(`Test livré · HTTP ${r.http} · ${fmt(r.duree_ms)} ms`) : toast(`Échec du test${r.http?` · HTTP ${r.http}`:''}${r.erreur?` · ${r.erreur}`:''}`, 'bad'))}>{occ==='x'+e.id?'Envoi…':'Envoyer un test'}</Btn>
        <Btn kind="ghost" sm icon="pencil-simple" onClick={()=>open(e)}>Modifier</Btn><Btn kind="ghost" sm icon="arrows-clockwise" onClick={()=>setModal({secret:e})}>Régénérer le secret</Btn><Btn kind="ghost" sm icon="trash" label="Supprimer" style={{color:V.bad}} onClick={()=>setModal({del:e})}/></>}</div>
    </Card>)}</div>
    : <Card><EmptyS icon="webhooks-logo" title="Aucune URL de réception" sub="Ajoutez une URL pour recevoir contrat.actif, depot.valide, solde.bas… Vous pouvez en ajouter jusqu'à 5, une par système." action={dev && <Btn icon="plus" onClick={()=>open()}>Ajouter une URL</Btn>}/></Card>}
    <Notice tone="info" icon="seal-check">Chaque URL a son propre secret. Chaque envoi porte l'en-tête <Mono c={V.ink}>X-LMS-Signature: t=…,v1=…</Mono> (HMAC SHA-256 de <Mono c={V.ink}>t + "." + corps</Mono>) à vérifier avant traitement. Relances : 1 min, 5 min, 30 min, 2 h, 6 h, 24 h.</Notice>
    <Tbl chargement={!hist} head={<div style={{padding:'16px 20px 12px'}}><H s={16}>Historique des envois</H></div>} cols={[{k:'t',l:'Date',w:'.9fr'},{k:'e',l:'Événement',w:'1.3fr'},{k:'u',l:'URL',w:'1.4fr',hideM:1},{k:'s',l:'Statut',w:'1fr'},{k:'c',l:'Réponse',w:'.6fr',hideM:1},{k:'x',l:'',w:'120px',r:1}]}
      rows={(hist?.donnees||[]).map(h=>{ const s = LIV_ST[h.statut] || [h.statut,'mute','circle']; return {t:<Mono>{DC(h.cree_le)}</Mono>, e:<Mono c={V.ink}>{h.type}</Mono>, u:<Ellipse style={{fontSize:12.5, color:V.ink2}}>{nomDe(h.webhook) || h.url}</Ellipse>, s:<span style={{display:'flex', flexDirection:'column', gap:2}}><Badge tone={s[1]} icon={s[2]}>{s[0]}</Badge>{h.tentatives>1 && <span style={{fontSize:11, color:V.ink3}}>{h.tentatives} essais</span>}</span>, c:h.dernier_http ? <Http code={h.dernier_http}/> : <span style={{color:V.ink3}}>—</span>,
        x:dev && h.statut!=='livre' ? <Btn kind="secondary" sm icon="arrow-clockwise" disabled={occ==='r'+h.id} onClick={()=>agir('r'+h.id, ()=>api('POST', `livraisons/${h.id}/renvoyer`, {mode:A.mode}), r=>r.statut==='livre'?toast('Renvoyé et livré'):toast(`Échec${r.erreur?` : ${r.erreur}`:''}`, 'bad'))}>Renvoyer</Btn> : null}; })}
      empty={<EmptyS icon="clock-counter-clockwise" title="Aucun envoi pour l'instant" sub="Les envois apparaîtront ici dès qu'un événement touche une URL active."/>} foot={hist?.total > 10 && <Pager n={hist.total} per={10} page={pg} set={setPg}/>}/>
    {(modal==='new' || (modal && modal.id)) && <Modal title={modal==='new'?'Ajouter une URL de réception':'Modifier l\'URL'} icon="webhooks-logo" onClose={()=>setModal(null)} w={600} foot={<><Btn kind="secondary" onClick={()=>setModal(null)}>Annuler</Btn><Btn disabled={!/^https:\/\/.+\..+/.test(url.trim()) || !Object.values(ev).some(Boolean) || occ==='save'} onClick={save}>{occ==='save'?'Enregistrement…':modal==='new'?'Ajouter':'Enregistrer'}</Btn></>}>
      <Grid cols="1fr 1.4fr" mcols="1fr"><Field label="Nom" hint="Pour la reconnaître." opt><Inp value={name} onChange={e=>setName(e.target.value)} placeholder="Back-office assurance"/></Field>
        <Field label="URL de réception" hint="HTTPS obligatoire. Répondez 2xx en moins de 10 s." err={url && url!=='https://' && !url.startsWith('https://')?'L\'URL doit commencer par https://':null}><Inp value={url} onChange={e=>setUrl(e.target.value)} placeholder="https://votre-domaine.sn/webhooks/lms" mono/></Field></Grid>
      <div><Lbl style={{marginBottom:10}}>Événements à recevoir</Lbl><div style={{display:'grid', gridTemplateColumns:m?'1fr':'1fr 1fr', gap:10}}>{EVENTS.map(e=><Check key={e[0]} on={!!ev[e[0]]} set={v=>setEv({...ev,[e[0]]:v})}><span><Mono c={V.ink}>{e[0]}</Mono><span style={{display:'block', fontSize:12, fontWeight:500, color:V.ink3}}>{e[1]}</span></span></Check>)}</div></div>
      {err && <Notice tone="bad">{err.message}</Notice>}
    </Modal>}
    {modal && modal.del && <Modal title="Supprimer cette URL ?" icon="trash" tone="bad" onClose={()=>setModal(null)} foot={<><Btn kind="secondary" onClick={()=>setModal(null)}>Annuler</Btn><Btn kind="danger" disabled={!!occ} onClick={()=>agir('d', ()=>api('DELETE', `webhooks/${modal.del.id}?mode=${A.mode}`), ()=>{ setModal(null); toast('URL supprimée'); })}>Supprimer</Btn></>}><Muted><Mono c={V.ink}>{modal.del.url}</Mono> ne recevra plus aucun événement. Les envois en attente sont annulés.</Muted></Modal>}
    {modal && modal.secret && <Modal title="Régénérer le secret ?" icon="arrows-clockwise" tone="warn" onClose={()=>setModal(null)} foot={<><Btn kind="secondary" onClick={()=>setModal(null)}>Annuler</Btn><Btn disabled={!!occ} onClick={()=>agir('s', ()=>api('POST', `webhooks/${modal.secret.id}/secret`, {mode:A.mode}), r=>{ setModal(null); setSecret(r.secret); })}>Régénérer</Btn></>}><Muted>L'ancien secret cesse de fonctionner immédiatement : mettez à jour le serveur qui reçoit <Mono c={V.ink}>{modal.secret.url}</Mono>.</Muted></Modal>}
    {secret && <Modal title="Secret de signature" icon="key" onClose={null} foot={<Btn onClick={()=>setSecret(null)}>J'ai copié le secret</Btn>}>
      <Notice tone="warn" title="Affiché une seule fois">Enregistrez-le dans la configuration du serveur qui reçoit cette URL.</Notice>
      <div style={{display:'flex', alignItems:'center', gap:10, padding:'14px 16px', borderRadius:16, background:'#0E1F19'}}><span style={{flex:1, fontFamily:V.mono, fontSize:13, color:'#E4EEE9', wordBreak:'break-all'}}>{secret}</span><CopyBtn text={secret} dark/></div></Modal>}
  </Page>;
}

function DevSettings(){
  const A = useApp(); const toast = useToast(); const dev = A.peut('dev');
  const [ips, rI, {erreur:eI}] = useDonnees(() => api('GET', 'ips'), []);
  const [hk] = useDonnees(() => Promise.all([api('GET', 'webhooks?mode=test'), api('GET', 'webhooks?mode=live').catch(()=>({donnees:[]}))]), []);
  const [ip, setIp] = React.useState(''), [modal, setModal] = React.useState(null), [code, setCode] = React.useState(''), [err, setErr] = React.useState(null), [load, setLoad] = React.useState(false), [done, setDone] = React.useState(null);
  const liste = Array.isArray(ips?.donnees) ? ips.donnees : [];
  const ipValide = /^(\d{1,3}\.){3}\d{1,3}(\/\d{1,2})?$/.test(ip.trim()) || /^[0-9a-f:]+(\/\d{1,3})?$/i.test(ip.trim()) && ip.includes(':');
  const fermer = () => { setModal(null); setCode(''); setErr(null); };
  const ajouter = async () => { setLoad(true); setErr(null); try { await api('POST', 'ips', {ip:ip.trim(), code_2fa:code}); toast('IP autorisée'); setIp(''); fermer(); rI(); } catch(e){ setErr(e); setCode(''); } setLoad(false); };
  const retirer = async (x) => { setLoad(true); try { await api('DELETE', `ips?ip=${encodeURIComponent(x)}`); toast('IP retirée'); fermer(); rI(); } catch(e){ toast(e.message, 'bad'); } setLoad(false); };
  const reinit = async () => { setLoad(true); setErr(null); try { const r = await api('POST', 'sandbox/reinitialiser', {confirmer:true}); setDone(r); fermer(); A.rafraichir(); } catch(e){ setErr(e); } setLoad(false); };
  const nbHooks = hk ? hk[0].donnees.length + hk[1].donnees.length : null, actifs = hk ? [...hk[0].donnees, ...hk[1].donnees].filter(x=>x.actif).length : 0;
  return <Page>
    <PageHead title="Paramètres développeur" sub="Tout ce qui concerne l'intégration technique, séparé du profil entreprise."/>
    <Grid cols="minmax(0,1.2fr) minmax(0,1fr)">
      <Card style={{display:'flex', flexDirection:'column', gap:12}}><div style={{display:'flex', alignItems:'center', gap:10}}><I n="shield-check" s={20} c={V.accD}/><H s={16} style={{flex:1}}>IP autorisées en production</H>{ips && <Badge tone={liste.length?'ok':'warn'} dot>{liste.length} IP</Badge>}</div>
        <Muted>Les appels avec une clé live depuis une autre adresse sont refusés avec <Mono>ip_non_autorisee</Mono>. La sandbox accepte toutes les IP.</Muted>
        {eI ? <Notice tone="bad">{eI.message}</Notice> : !ips ? <Skel h={80} r={14}/> : <>
          {liste.map(x=><div key={x} style={{display:'flex', alignItems:'center', gap:10, padding:'10px 14px', borderRadius:14, background:V.sub}}><I n="globe-simple" s={17} c={V.ink3}/><Mono c={V.ink} s={13.5}>{x}</Mono><span style={{flex:1}}></span>{dev && <Btn kind="ghost" sm icon="x" label={`Retirer ${x}`} onClick={()=>setModal({retirer:x})}/>}</div>)}
          {!liste.length && <Notice tone="warn">Aucune IP : toutes vos clés live seront refusées.</Notice>}</>}
        {dev && <div style={{display:'flex', gap:8}}><div style={{flex:1, minWidth:0}}><Inp value={ip} onChange={e=>setIp(e.target.value)} placeholder="196.207.230.16 ou 196.207.230.0/28" aria-label="Adresse IP ou plage CIDR" mono/></div><Btn kind="secondary" icon="plus" disabled={!ipValide} onClick={()=>setModal({ajout:true})}>Ajouter</Btn></div>}
        <Muted style={{fontSize:12}}>Chaque ajout demande une confirmation par double authentification.</Muted></Card>
      <div style={{display:'flex', flexDirection:'column', gap:16, minWidth:0}}>
        <Card style={{display:'flex', flexDirection:'column', gap:12}}><div style={{display:'flex', alignItems:'center', gap:10}}><I n="plugs-connected" s={20} c={V.accD}/><H s={16}>Connexion à l'API</H></div>
          <div><Lbl>URL de base</Lbl><div style={{display:'flex', alignItems:'center', gap:8, marginTop:8, padding:'10px 12px', borderRadius:14, background:V.sub, flexWrap:'wrap'}}><span style={{fontFamily:V.mono, fontSize:12.5, color:V.ink, wordBreak:'break-all', minWidth:0}}>{API_V1_BASE}</span><span style={{flex:1}}></span><CopyBtn text={API_V1_BASE}/></div></div>
          <KV rows={[['Version','v1 · stable'],['Format','JSON · UTF-8'],['Authentification','Authorization: Bearer <clé>',null,1],['Erreurs','{ "erreur": { "code", "message" } }',null,1]]}/></Card>
        <Card onClick={()=>A.go('hooks')} style={{display:'flex', alignItems:'center', gap:12}}><Tile icon="webhooks-logo"/><span style={{flex:1, minWidth:0}}><span style={{display:'block', fontWeight:800, fontSize:14}}>Webhooks</span><span style={{display:'block', fontSize:12.5, color:V.ink3}}>{nbHooks==null ? 'Chargement…' : nbHooks ? `${nbHooks} URL de réception · sandbox et production` : 'Aucune URL configurée'}</span></span>{nbHooks!=null && (actifs ? <Badge tone="ok" dot>Actif</Badge> : <Badge tone="warn">À configurer</Badge>)}<I n="caret-right" s={16} c={V.ink3}/></Card>
      </div>
    </Grid>
    <Grid cols="1fr 1fr">
      <Card pad={0}>{[['Masquage des données personnelles','Noms, téléphones et pièces masqués dans le journal des requêtes.',<Badge tone="ok" icon="lock-simple">Toujours actif</Badge>],['IP en sandbox','Les clés lms_test_ sont acceptées depuis toute adresse.',<Badge tone="info">Toutes</Badge>],['Clés actives','10 clés actives au plus par environnement.',<Btn kind="ghost" sm iconR="arrow-right" onClick={()=>A.go('keys')}>Clés</Btn>]].map((r,i)=><div key={i} style={{display:'flex', alignItems:'center', gap:14, padding:'16px 20px', borderTop:i?`1px solid ${V.line}`:'none', flexWrap:'wrap'}}><span style={{flex:1, minWidth:180}}><span style={{display:'block', fontWeight:700, fontSize:13.5}}>{r[0]}</span><span style={{display:'block', fontSize:12.5, color:V.ink3, marginTop:2}}>{r[1]}</span></span>{r[2]}</div>)}</Card>
      <Card style={{display:'flex', flexDirection:'column', gap:12}}><div style={{display:'flex', alignItems:'center', gap:10}}><I n="flask" s={20} c={V.warn}/><H s={16}>Sandbox</H></div>
        <Muted>Remet le solde de test à son montant initial et supprime les contrats, dépôts et journaux de test. Vos clés sandbox et vos webhooks sont conservés.</Muted>
        {done && <Notice tone="ok" title="Sandbox réinitialisée">Solde de test : {fmt(done.solde)} F CFA. Supprimés : {fmt(done.supprimes?.contrats||0)} contrat(s), {fmt(done.supprimes?.depots||0)} dépôt(s), {fmt(done.supprimes?.requetes||0)} requête(s).</Notice>}
        <div style={{display:'flex', gap:8, flexWrap:'wrap'}}>{dev && <Btn kind="secondary" icon="arrow-counter-clockwise" onClick={()=>setModal({reset:true})}>Réinitialiser la sandbox</Btn>}<Btn kind="ghost" icon="book-open-text" onClick={()=>A.go('docs', 'sandbox')}>Valeurs de test</Btn></div></Card>
    </Grid>
    {modal?.ajout && <Modal title="Autoriser une IP" icon="shield-check" onClose={fermer} foot={<><Btn kind="secondary" onClick={fermer}>Annuler</Btn><Btn disabled={code.length<6 || load} onClick={ajouter}>{load?'Vérification…':'Autoriser'}</Btn></>}>
      <Muted>Les clés live seront acceptées depuis <Mono c={V.ink}>{ip.trim()}</Mono>. Confirmez avec le code de votre application d'authentification.</Muted>
      <OTP value={code} set={v=>{setCode(v); setErr(null);}} err={!!err}/>{err && <Notice tone="bad">{err.message}</Notice>}</Modal>}
    {modal?.retirer && <Modal title="Retirer cette IP ?" icon="x-circle" tone="bad" onClose={fermer} foot={<><Btn kind="secondary" onClick={fermer}>Annuler</Btn><Btn kind="danger" disabled={load} onClick={()=>retirer(modal.retirer)}>Retirer</Btn></>}><Muted>Les appels live depuis <Mono c={V.ink}>{modal.retirer}</Mono> seront refusés immédiatement.</Muted></Modal>}
    {modal?.reset && <Modal title="Réinitialiser la sandbox ?" icon="arrow-counter-clockwise" tone="warn" onClose={fermer} foot={<><Btn kind="secondary" onClick={fermer}>Annuler</Btn><Btn disabled={load} onClick={reinit}>{load?'Réinitialisation…':'Réinitialiser'}</Btn></>}><Muted>Cette action ne touche pas à la production. Elle est définitive pour les données de test.</Muted>{err && <Notice tone="bad">{err.message}</Notice>}</Modal>}
  </Page>;
}

Object.assign(window, {KeyReveal, Keys, Logs, LogDetail, CopyCurl, EVENTS, LIV_ST, Hooks, DevSettings});
