/* Console partenaire — coquille du design (dev-shell.jsx) : barre latérale claire, bascule Sandbox /
   Production, recherche, aide, notifications (tirées des vraies données), menu du compte, bandeau sandbox,
   coquille mobile (barre du bas + « Plus »). Utilitaires partagés par les pages. */
const AppCtx = React.createContext(null);
const useApp = () => React.useContext(AppCtx);
const NAV = [
  [null, [['dash','Tableau de bord','squares-four'],['ops','Opérations','list-checks'],['bal','Solde et dépôts','wallet'],['conso','Consommation','chart-line-up']]],
  ['Développeurs', [['keys','Clés d\'API','key'],['logs','Journal des requêtes','terminal-window'],['hooks','Webhooks','webhooks-logo'],['docs','Documentation','book-open-text'],['devset','Paramètres développeur','code']]],
  ['Compte', [['prod','Entreprise','buildings'],['team','Équipe','users-three'],['settings','Paramètres','gear-six'],['help','Aide','lifebuoy']]],
];
const PARENT = {op:'ops'};
// Anciennes adresses de la console (#/tableau, #/operations…) → nouvelles
const ALIAS = {tableau:'dash', operations:'ops', operation:'op', solde:'bal', cles:'keys', journal:'logs', webhooks:'hooks', parametres:'settings', equipe:'team', dossier:'prod', aide:'help', consommation:'conso', documentation:'docs'};

// Utilitaires communs aux pages
const COULEURS_E = ['#E3001B','#E8731A','#1C5FAF','#6D3FD0','#2F8F6E','#0A6A83','#A8620A','#113025'];
const couleurDe = (t) => { let h = 0; for(const ch of String(t||'')) h = (h*31 + ch.charCodeAt(0)) >>> 0; return COULEURS_E[h % COULEURS_E.length]; };
const sigle = (n) => (n||'?').split(/\s+/).filter(w=>/^[A-Za-zÀ-ÿ0-9]/.test(w)).slice(0,2).map(w=>w[0]).join('').toUpperCase() || '?';
function CoLogo({s=56, nom, logo}){ return logo ? <img src={logo} alt="" style={{width:s, height:s, borderRadius:s*.3, objectFit:'cover', background:'#fff', border:`1px solid ${V.line}`, flex:'none'}}/>
  : <span style={{display:'grid', placeItems:'center', width:s, height:s, borderRadius:s*.3, background:couleurDe(nom), color:'#fff', fontFamily:V.disp, fontWeight:800, fontSize:s*.3, letterSpacing:'-0.02em', flex:'none'}}>{sigle(nom)}</span>; }
function ErreurCarte({e, recharger}){ return <Card><EmptyS icon="warning-octagon" tone="bad" title="Chargement impossible" sub={e?.message} action={recharger && <Btn kind="secondary" icon="arrow-clockwise" onClick={recharger}>Réessayer</Btn>}/></Card>; }
function Assureur({a}){ const x = ASSUREURS[a]; if(!x) return <span style={{color:V.ink2}}>{a || '—'}</span>;
  return <span style={{display:'flex', alignItems:'center', gap:7, minWidth:0}}><img src={x[1]} alt="" style={{width:22, height:22, borderRadius:7, objectFit:'contain', background:'#fff', border:`1px solid ${V.line}`, flex:'none'}}/><span style={{whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis'}}>{x[0]}</span></span>; }
const pct = (x, d=1) => `${Number(x||0).toLocaleString('fr-FR', {maximumFractionDigits:d})} %`;
const dateCourte = (iso) => { if(!iso) return '—'; const d = new Date(iso.length === 10 ? iso + 'T12:00:00' : iso); return isNaN(d) ? iso : d.toLocaleDateString('fr-FR'); };
const jourIso = (d) => { const x = new Date(d); return `${x.getFullYear()}-${String(x.getMonth()+1).padStart(2,'0')}-${String(x.getDate()).padStart(2,'0')}`; };
const heureOuJour = (iso) => { if(!iso) return '—'; const d = new Date(iso), a = new Date(); return d.toDateString() === a.toDateString() ? d.toLocaleTimeString('fr-FR', {hour:'2-digit', minute:'2-digit'}) : d.toLocaleDateString('fr-FR', {day:'2-digit', month:'2-digit'}); };
const Ellipse = ({children, style}) => <span style={{minWidth:0, whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis', ...style}}>{children}</span>;

// Notifications : solde bas, dépôts récents, incidents en cours, dossier à corriger
function useNotifs(mode, tick){
  const [n] = useDonnees(async () => { if(!mode) return [];
    const [s, dep, st] = await Promise.all([api('GET', `solde?mode=${mode}`).catch(()=>null), api('GET', `depots?mode=${mode}`).catch(()=>null), api('GET', 'statut').catch(()=>null)]);
    const l = []; const il14 = Date.now() - 14*864e5;
    if(s?.solde_bas) l.push({icon:'warning', ton:'warn', titre:'Solde bas', texte:`Il reste ${fmt(s.solde)} F CFA, sous votre seuil de ${fmt(s.seuil_alerte)}.`, quand:null, page:'bal'});
    (st?.incidents||[]).filter(x=>x.statut==='en_cours').forEach(x=>l.push({icon:'warning-diamond', ton:x.gravite==='indisponible'?'bad':'warn', titre:x.titre, texte:x.message, quand:x.cree_le, page:'help'}));
    (dep?.donnees||[]).filter(x=>new Date(x.cree_le) > il14).slice(0,4).forEach(x=>l.push({icon:x.statut==='valide'?'check-circle':x.statut==='rejete'?'x-circle':'hourglass', ton:x.statut==='valide'?'ok':x.statut==='rejete'?'bad':'info',
      titre:x.statut==='valide'?'Dépôt validé':x.statut==='rejete'?'Dépôt rejeté':'Dépôt en attente', texte:`${x.id} · ${fmt(x.montant_credite || x.montant_declare)} F CFA${x.statut==='rejete' && (x.motif_detail||x.motif_rejet) ? ` · ${x.motif_detail||x.motif_rejet}` : ''}`, quand:x.traite_le || x.cree_le, page:'bal'}));
    return l;
  }, [mode, tick]);
  return n || [];
}

function Sidebar({ouvert, fermer}){
  const {page, go, moi, dossier} = useApp(); const cur = PARENT[page]||page; const stage = dossier?.stage;
  return <aside style={{width:240, flex:'none', height:'100%', background:V.card, borderRight:`1px solid ${V.line}`, display:'flex', flexDirection:'column'}}>
    <div style={{height:64, flex:'none', display:'flex', alignItems:'center', padding:'0 18px', borderBottom:`1px solid ${V.line}`}}><Brand/></div>
    <nav aria-label="Navigation principale" style={{flex:1, overflowY:'auto', padding:'14px 10px', display:'flex', flexDirection:'column', gap:16}}>
      {NAV.map(([t, items],gi)=><div key={gi} style={{display:'flex', flexDirection:'column', gap:2}}>
        {t && <Lbl style={{padding:'0 12px 6px', fontSize:10.5}}>{t}</Lbl>}
        {items.map(n=>{ const on = cur===n[0];
          return <button key={n[0]} onClick={()=>go(n[0])} className="lb ln" aria-current={on?'page':undefined} style={{display:'flex', alignItems:'center', gap:11, height:40, padding:'0 12px', border:0, borderRadius:14, cursor:'pointer', textAlign:'left',
            background:on?V.mint:'transparent', color:on?V.accD:V.ink2, fontFamily:V.body, fontWeight:700, fontSize:13.5}}>
            <I n={n[2]} s={19} fill={on}/><span style={{flex:1}}>{n[1]}</span>
            {n[0]==='prod' && !moi.entreprise.production_ouverte && <span title="Production pas encore ouverte" style={{width:7, height:7, borderRadius:99, background:stage==='info'||stage==='refused'?'var(--bad)':'var(--warnDot)'}}></span>}</button>; })}
      </div>)}
    </nav>
    <div style={{padding:14, borderTop:`1px solid ${V.line}`}}>
      <button className="lb lr" onClick={()=>go('prod')} style={{width:'100%', padding:12, borderRadius:18, border:0, background:V.sub, display:'flex', alignItems:'center', gap:10, cursor:'pointer', textAlign:'left', fontFamily:V.body}}>
        <CoLogo s={32} nom={moi.entreprise.nom} logo={dossier?.logo?.url}/>
        <span style={{minWidth:0}}><span style={{display:'block', fontWeight:700, fontSize:12.5, color:V.ink, whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis'}}>{moi.entreprise.nom}</span><span style={{display:'block', fontSize:11.5, color:V.ink3}}>Partenaire · {moi.entreprise.pays}</span></span></button>
    </div>
  </aside>;
}
function EnvSwitch({compact}){
  const {mode, setMode, moi, go} = useApp(); const locked = !moi.entreprise.production_ouverte;
  const opt = (k, l) => { const on = mode===k;
    return <button role="radio" aria-checked={on} className="lb" title={k==='live' && locked ? 'Production verrouillée : complétez le dossier Entreprise' : undefined} onClick={()=>{ if(k==='live' && locked){ go('prod'); return; } setMode(k); }}
      style={{height:compact?30:34, padding:compact?'0 10px':'0 14px', borderRadius:11, border:0, cursor:'pointer', display:'flex', alignItems:'center', gap:7, whiteSpace:'nowrap', fontFamily:V.body, fontWeight:700, fontSize:compact?12:13,
        background:on?(k==='test'?V.warnBg:V.mint):'transparent', color:on?(k==='test'?V.warn:V.accD):V.ink3}}>
      {k==='live' && locked ? <I n="lock-simple" s={14}/> : <span style={{width:8, height:8, borderRadius:99, background:k==='test'?'var(--warnDot)':'#2F8F6E'}}></span>}{l}</button>; };
  return <div role="radiogroup" aria-label="Environnement" style={{display:'flex', gap:3, padding:3, borderRadius:14, background:V.sub, border:`1px solid ${V.line}`, flex:'none'}}>{opt('test','Sandbox')}{opt('live','Production')}</div>;
}
function SandboxBar(){ const {mode} = useApp(); if(mode!=='test') return null;
  return <div role="status" style={{flex:'none', display:'flex', alignItems:'center', justifyContent:'center', gap:8, minHeight:32, padding:'0 12px', background:V.warnBg, color:V.warn, fontWeight:700, fontSize:12.5, borderBottom:`1px solid ${V.line}`, textAlign:'center'}}>
    <I n="flask" s={15} fill/>Mode test — aucune opération réelle, aucun débit réel</div>; }

function ListeNotifs({l, go, fermer}){
  if(!l.length) return <div style={{padding:'18px 8px', textAlign:'center'}}><I n="bell-simple-slash" s={24} c={V.ink3}/><span style={{display:'block', marginTop:6, fontWeight:700, fontSize:13, color:V.ink}}>Aucune notification</span><span style={{display:'block', fontSize:12, color:V.ink3, marginTop:2}}>Solde, dépôts et incidents s'afficheront ici.</span></div>;
  return l.map((n,i)=><button key={i} className="lb lr" onClick={()=>{ go(n.page); fermer && fermer(); }} style={{display:'flex', gap:10, width:'100%', padding:'10px 8px', border:0, borderTop:i?`1px solid ${V.line}`:'none', borderRadius:0, background:'none', cursor:'pointer', textAlign:'left', fontFamily:V.body}}>
    <I n={n.icon} s={18} c={TN[n.ton][1]} fill/><span style={{flex:1, minWidth:0}}><span style={{display:'block', fontWeight:700, fontSize:13, color:V.ink}}>{n.titre}</span><span style={{display:'block', fontSize:12, color:V.ink3, lineHeight:1.4, marginTop:2}}>{n.texte}</span>{n.quand && <span style={{display:'block', fontSize:11, color:V.ink3, marginTop:4}}>Il y a {Depuis(n.quand)}</span>}</span></button>);
}
function Recherche(){ const {go} = useApp(); const [q, setQ] = React.useState(''); const ref = React.useRef(null);
  React.useEffect(()=>{ const f = e => { if((e.ctrlKey||e.metaKey) && e.key.toLowerCase()==='k'){ e.preventDefault(); const i = ref.current && ref.current.querySelector('input'); i && i.focus(); } }; addEventListener('keydown', f); return ()=>removeEventListener('keydown', f); }, []);
  const chercher = (e) => { e.preventDefault(); const v = q.trim(); if(!v) return;
    if(/^req_/i.test(v)) go('logs', v); else if(/^ctr_/i.test(v)) go('op', v); else if(/^lms_(test|live)_/i.test(v)) go('keys'); else if(/^DEP-/i.test(v)) go('bal');
    else { try { sessionStorage.setItem('lms_q', v); } catch(x){} go('ops'); window.dispatchEvent(new Event('lms-recherche')); } setQ(''); };
  return <form ref={ref} onSubmit={chercher} role="search" style={{flex:'0 1 420px', minWidth:0, marginLeft:8}}><Inp value={q} onChange={e=>setQ(e.target.value)} aria-label="Rechercher" placeholder="Rechercher une référence, ctr_…, req_…" lead={<I n="magnifying-glass" s={17}/>} trail={<span style={{fontFamily:V.mono, fontSize:11, padding:'2px 6px', borderRadius:6, border:`1px solid ${V.line}`}}>{/Mac/.test(navigator.platform)?'⌘K':'Ctrl K'}</span>} style={{height:40, fontSize:13.5, background:V.sub, paddingRight:72}}/></form>; }

function Topbar(){
  const {go, dark, setDark, moi, quitter, notifs, notifNouv, marquerVu} = useApp(); const [menu, setMenu] = React.useState(null);
  const ref = React.useRef(null);
  React.useEffect(()=>{ if(!menu) return; const f = e => { if(ref.current && !ref.current.contains(e.target)) setMenu(null); }; document.addEventListener('mousedown', f); return ()=>document.removeEventListener('mousedown', f); }, [menu]);
  const ouvrir = k => { setMenu(menu===k?null:k); if(k==='notif') marquerVu(); };
  const ib = (n, l, k, dot) => <button className="lb" aria-label={l} aria-expanded={menu===k} onClick={()=>ouvrir(k)} style={{position:'relative', width:40, height:40, borderRadius:14, border:0, background:menu===k?V.sub:'transparent', display:'grid', placeItems:'center', cursor:'pointer', color:V.ink2}}>
    <I n={n} s={20}/>{dot && <span style={{position:'absolute', top:9, right:10, width:8, height:8, borderRadius:99, background:'var(--bad)', border:`2px solid ${V.card}`}}></span>}</button>;
  const drop = (children, w=280) => <div style={{position:'absolute', right:0, top:48, width:w, zIndex:60, background:V.card, border:`1px solid ${V.line}`, borderRadius:20, boxShadow:'0 16px 40px rgba(8,20,16,.14)', padding:6, animation:'lpop .15s ease', maxHeight:'70vh', overflowY:'auto'}}>{children}</div>;
  const mi = (icon, l, fn, danger) => <button key={l} className="lb lr" onClick={()=>{fn&&fn(); setMenu(null);}} style={{display:'flex', alignItems:'center', gap:10, width:'100%', height:40, padding:'0 12px', border:0, borderRadius:12, background:'none', cursor:'pointer', fontFamily:V.body, fontWeight:600, fontSize:13.5, color:danger?V.bad:V.ink}}><I n={icon} s={18} c={danger?V.bad:V.ink3}/>{l}</button>;
  const nom = `${moi.membre.prenom} ${moi.membre.nom}`;
  return <header ref={ref} style={{height:64, flex:'none', display:'flex', alignItems:'center', gap:12, padding:'0 24px', background:V.card, borderBottom:`1px solid ${V.line}`, position:'relative', zIndex:30}}>
    <EnvSwitch/>
    <Recherche/>
    <span style={{flex:1}}></span>
    <div style={{position:'relative', display:'flex', gap:2}}>
      {ib('question','Aide','help')}{ib('bell','Notifications','notif', notifNouv)}
      {menu==='help' && drop(<>{mi('book-open-text','Documentation',()=>go('docs'))}{mi('lifebuoy','Contacter le support',()=>go('help'))}{mi('pulse','Statut des services',()=>go('help'))}</>, 240)}
      {menu==='notif' && drop(<div style={{padding:6}}><Lbl style={{padding:'6px 8px 8px'}}>Notifications</Lbl><ListeNotifs l={notifs} go={go} fermer={()=>setMenu(null)}/></div>, 340)}
    </div>
    <span style={{width:1, height:28, background:V.line}}></span>
    <div style={{position:'relative'}}>
      <button className="lb" onClick={()=>setMenu(menu==='me'?null:'me')} aria-label="Menu du compte" aria-expanded={menu==='me'} style={{display:'flex', alignItems:'center', gap:10, height:44, padding:'0 8px 0 4px', borderRadius:14, border:0, background:menu==='me'?V.sub:'transparent', cursor:'pointer', fontFamily:V.body}}>
        <Av n={nom} s={34}/><span style={{textAlign:'left', lineHeight:1.2}}><span style={{display:'block', fontWeight:700, fontSize:13, color:V.ink, whiteSpace:'nowrap'}}>{nom}</span><span style={{display:'block', fontSize:11.5, color:V.ink3, whiteSpace:'nowrap'}}>{moi.entreprise.nom.split(' ')[0]} · {ROLES_M[moi.membre.role]}</span></span><I n="caret-down" s={14} c={V.ink3}/></button>
      {menu==='me' && drop(<>
        <div style={{padding:'10px 12px 12px', borderBottom:`1px solid ${V.line}`, marginBottom:4}}><span style={{display:'block', fontWeight:800, fontSize:13.5, color:V.ink}}>{nom}</span><span style={{display:'block', fontSize:12, color:V.ink3, wordBreak:'break-all'}}>{moi.membre.email}</span></div>
        {mi('buildings','Entreprise',()=>go('prod'))}{mi('users-three','Équipe',()=>go('team'))}{mi('shield-check','Sécurité',()=>go('settings'))}
        <div style={{display:'flex', alignItems:'center', gap:10, height:40, padding:'0 12px'}}><I n="moon" s={18} c={V.ink3}/><span style={{flex:1, fontWeight:600, fontSize:13.5, color:V.ink}}>Mode sombre</span><Toggle on={dark} set={setDark} label="Mode sombre"/></div>
        <div style={{height:1, background:V.line, margin:'4px 0'}}></div>{mi('sign-out','Se déconnecter',quitter,true)}</>)}
    </div>
  </header>;
}

const MNAV = [['dash','Accueil','house'],['ops','Opérations','list-checks'],['bal','Solde','wallet'],['keys','Clés','key'],['more','Plus','dots-three-outline']];
function MobileShell({children}){
  const {page, go, moi, dossier, dark, setDark, quitter, notifs, notifNouv, marquerVu} = useApp(); const [more, setMore] = React.useState(false), [nt, setNt] = React.useState(false); const cur = PARENT[page]||page;
  const inMain = MNAV.some(n=>n[0]===cur); const stage = dossier?.stage;
  return <div style={{display:'flex', flexDirection:'column', height:'100%', background:V.bg, position:'relative'}}>
    <header style={{height:56, flex:'none', display:'flex', alignItems:'center', gap:10, padding:'0 14px', background:V.card, borderBottom:`1px solid ${V.line}`}}>
      {page==='op' ? <Btn kind="ghost" sm icon="arrow-left" label="Retour" onClick={()=>go('ops')}/> : <Logo s={30}/>}
      <span style={{flex:1}}></span><EnvSwitch compact/>
      <span style={{position:'relative'}}><Btn kind="ghost" sm icon="bell" label="Notifications" onClick={()=>{ setNt(true); marquerVu(); }}/>{notifNouv && <span style={{position:'absolute', top:7, right:8, width:8, height:8, borderRadius:99, background:'var(--bad)', border:`2px solid ${V.card}`, pointerEvents:'none'}}></span>}</span></header>
    <SandboxBar/>
    <div style={{flex:1, minHeight:0, display:'flex'}}>{children}</div>
    <nav aria-label="Navigation" style={{position:'absolute', left:0, right:0, bottom:0, height:74, paddingBottom:14, display:'flex', background:V.card, borderTop:`1px solid ${V.line}`, zIndex:20}}>
      {MNAV.map(n=>{ const on = n[0]==='more' ? (more || !inMain) : cur===n[0];
        return <button key={n[0]} className="lb" aria-current={on && n[0]!=='more'?'page':undefined} onClick={()=>n[0]==='more'?setMore(true):go(n[0])} style={{flex:1, border:0, background:'none', display:'flex', flexDirection:'column', alignItems:'center', justifyContent:'center', gap:3, cursor:'pointer', color:on?V.accD:V.ink3, fontFamily:V.body, fontWeight:700, fontSize:11}}>
          <I n={n[2]} s={22} fill={on}/>{n[1]}</button>; })}
    </nav>
    {more && <Modal title="Plus" onClose={()=>setMore(false)}>
      <div style={{display:'grid', gridTemplateColumns:'1fr 1fr', gap:8}}>
        {[['conso','Consommation','chart-line-up'],['logs','Journal','terminal-window'],['hooks','Webhooks','webhooks-logo'],['docs','Documentation','book-open-text'],['devset','Paramètres dév.','code'],['prod','Entreprise','buildings'],['team','Équipe','users-three'],['settings','Paramètres','gear-six'],['help','Aide','lifebuoy']].map(n=>
          <button key={n[0]} className="lb" onClick={()=>{setMore(false); go(n[0]);}} style={{display:'flex', alignItems:'center', gap:10, height:52, padding:'0 14px', borderRadius:16, border:`1px solid ${cur===n[0]?V.acc:V.line}`, background:cur===n[0]?V.mint:V.card, cursor:'pointer', fontFamily:V.body, fontWeight:700, fontSize:13.5, color:V.ink, textAlign:'left'}}>
            <I n={n[2]} s={20} c={V.accD}/>{n[1]}{n[0]==='prod' && !moi.entreprise.production_ouverte && <span style={{marginLeft:'auto', width:7, height:7, borderRadius:99, background:stage==='info'||stage==='refused'?'var(--bad)':'var(--warnDot)'}}></span>}</button>)}</div>
      <div style={{display:'flex', alignItems:'center', gap:10, padding:'4px 2px'}}><Av n={`${moi.membre.prenom} ${moi.membre.nom}`} s={36}/><span style={{flex:1, minWidth:0}}><span style={{display:'block', fontWeight:700, fontSize:13.5}}>{moi.membre.prenom} {moi.membre.nom}</span><span style={{display:'block', fontSize:12, color:V.ink3, wordBreak:'break-all'}}>{moi.membre.email}</span></span></div>
      <div style={{display:'flex', alignItems:'center', gap:10, height:44, padding:'0 14px', borderRadius:16, background:V.sub}}><I n="moon" s={18} c={V.ink3}/><span style={{flex:1, fontWeight:600, fontSize:13.5}}>Mode sombre</span><Toggle on={dark} set={setDark} label="Mode sombre"/></div>
      <Btn kind="danger" icon="sign-out" full onClick={quitter}>Se déconnecter</Btn>
    </Modal>}
    {nt && <Modal title="Notifications" icon="bell" onClose={()=>setNt(false)}><div><ListeNotifs l={notifs} go={go} fermer={()=>setNt(false)}/></div></Modal>}
  </div>;
}
function DesktopShell({children}){
  return <div style={{display:'flex', height:'100%', background:V.bg}}>
    <Sidebar/><main style={{flex:1, minWidth:0, display:'flex', flexDirection:'column', position:'relative'}}><Topbar/><SandboxBar/><div style={{flex:1, minHeight:0, display:'flex', position:'relative'}}>{children}</div></main></div>;
}

Object.assign(window, {AppCtx, useApp, NAV, PARENT, ALIAS, couleurDe, sigle, CoLogo, ErreurCarte, Assureur, pct, dateCourte, jourIso, heureOuJour, Ellipse, useNotifs, Sidebar, EnvSwitch, SandboxBar, Topbar, MobileShell, DesktopShell, ListeNotifs});
