/* Commun aux deux consoles : marque (logo v2), étapes du dossier, dates, appels d'API, données, toasts,
   thème clair/sombre. S'appuie sur kit.jsx (V, I, Badge…). */
const LOGO_URL = '../commun/logos/multiservice-v2.svg';
function Logo({s=32}){ return <span style={{display:'grid', placeItems:'center', width:s, height:s, borderRadius:s*.32, background:V.deep, flex:'none'}}><img src={LOGO_URL} alt="" style={{width:s, height:s, borderRadius:s*.32, objectFit:'cover'}}/></span>; }
function Brand({sub='Dev', light, s=16}){ return <span style={{display:'flex', alignItems:'center', gap:10}}><Logo/><span style={{display:'inline-flex', alignItems:'center', gap:6}}>
  <span style={{fontFamily:V.disp, fontWeight:800, fontSize:s, letterSpacing:'-0.03em', color:light?'#fff':V.ink}}>LeMultiservice</span>
  <span style={{padding:'2px 7px 3px', borderRadius:7, background:light?'rgba(159,224,198,.16)':'#113025', color:'#9FE0C6', fontFamily:V.body, fontWeight:800, fontSize:s*0.62, letterSpacing:'0.04em', lineHeight:1.2}}>{sub}</span></span></span>; }
const STAGES = {draft:['Brouillon','mute','pencil-simple'], submitted:['Soumis','info','paper-plane-tilt'], review:['En revue','info','magnifying-glass'], info:['Informations demandées','warn','warning'], approved:['Approuvé','ok','seal-check'], refused:['Refusé','bad','x-circle'], modification:['Modification en revue','info','pencil-simple']};
const StageB = ({st}) => { const s = STAGES[st] || STAGES.draft; return <Badge tone={s[1]} icon={s[2]}>{s[0]}</Badge>; };
const ASSUREURS = {askia:['Askia','../commun/logos/askia.png'], providence:['La Providence','../commun/logos/providence.png']};

// Icône de service style iOS (dev-illus.js), repli sur la tuile Phosphor
function SvcArt({k, s=40}){ const svg = window.lmsIcon && window.lmsIcon(k, s);
  return svg ? <span aria-hidden="true" style={{display:'block', width:s, height:s, lineHeight:0, flex:'none'}} dangerouslySetInnerHTML={{__html:svg}}></span> : <SvcIcon k={k} s={s}/>; }

// Dates : « 25/09/2026 21:20 », « il y a 12 min »
const D = (iso, heure=true) => { if(!iso) return '—'; const d = new Date(iso); if(isNaN(d)) return String(iso);
  return d.toLocaleDateString('fr-FR', {day:'2-digit', month:'2-digit', year:'numeric'}) + (heure ? ' ' + d.toLocaleTimeString('fr-FR', {hour:'2-digit', minute:'2-digit'}) : ''); };
const DC = (iso) => { if(!iso) return '—'; const d = new Date(iso); return d.toLocaleDateString('fr-FR', {day:'2-digit', month:'2-digit'}) + ' ' + d.toLocaleTimeString('fr-FR', {hour:'2-digit', minute:'2-digit'}); };
const Depuis = (iso) => { if(!iso) return '—'; const m = Math.round((Date.now() - new Date(iso)) / 60000);
  if(m < 1) return 'à l\'instant'; if(m < 60) return `${m} min`; const h = Math.round(m/60); if(h < 48) return `${h} h`; return `${Math.round(h/24)} j`; };

// Appels d'API : jeton en sessionStorage, erreurs {erreur:{code, message}} levées en Error(code, message)
function creerApi(base, cleJeton){
  const lire = () => { try { return sessionStorage.getItem(cleJeton); } catch(e){ return null; } };
  const ecrire = (j) => { try { j ? sessionStorage.setItem(cleJeton, j) : sessionStorage.removeItem(cleJeton); } catch(e){} };
  async function api(methode, route, corps, options={}){
    const jeton = lire(); const multipart = corps instanceof FormData;
    const r = await fetch(`${base}/${route}`, {method:methode, headers:{...(multipart||!corps ? {} : {'Content-Type':'application/json'}), ...(jeton ? {Authorization:`Bearer ${jeton}`} : {})}, body:corps ? (multipart ? corps : JSON.stringify(corps)) : undefined});
    const j = await r.json().catch(() => null);
    if(!r.ok){ const e = new Error(j?.erreur?.message || j?.message || `Erreur ${r.status}`); e.code = j?.erreur?.code || `http_${r.status}`; e.status = r.status;
      if(r.status === 401 && !options.sansDeconnexion && jeton){ ecrire(null); window.dispatchEvent(new Event('lms-deconnexion')); }
      throw e; }
    return j;
  }
  return {api, lireJeton:lire, ecrireJeton:ecrire};
}

// Données chargées : [valeur, recharger, {chargement, erreur}]
function useDonnees(charger, deps=[]){
  const [etat, setEtat] = React.useState({v:null, chargement:true, erreur:null});
  const tour = React.useRef(0);
  const recharger = React.useCallback(() => { const t = ++tour.current; setEtat(e => ({...e, chargement:true}));
    return Promise.resolve().then(charger).then(v => { if(t === tour.current) setEtat({v, chargement:false, erreur:null}); }, erreur => { if(t === tour.current) setEtat({v:null, chargement:false, erreur}); }); }, deps);
  React.useEffect(() => { recharger(); }, [recharger]);
  return [etat.v, recharger, etat];
}

// Toasts
const ToastCtx = React.createContext(() => {});
const useToast = () => React.useContext(ToastCtx);
function Toasts({children}){
  const [l, setL] = React.useState([]);
  const toast = React.useCallback((texte, ton='ok') => { const id = Math.random(); setL(x => [...x, {id, texte, ton}]); setTimeout(() => setL(x => x.filter(t => t.id !== id)), 4200); }, []);
  return <ToastCtx.Provider value={toast}>{children}<div className="toasts" role="status">{l.map(t => <div key={t.id} style={{display:'flex', alignItems:'center', gap:10, padding:'12px 16px', borderRadius:16, background:t.ton==='bad'?V.bad:'#113025', color:'#fff', fontWeight:700, fontSize:13.5, boxShadow:'0 12px 30px rgba(8,20,16,.25)', animation:'lpop .2s ease', maxWidth:420}}>
    <I n={t.ton==='bad'?'warning-circle':'check-circle'} s={18} fill c={t.ton==='bad'?'#fff':'#9FE0C6'}/>{t.texte}</div>)}</div></ToastCtx.Provider>;
}

// Thème : clair par défaut (design), choix mémorisé dans ce navigateur
function useTheme(){
  const [dark, setDark] = React.useState(() => { try { return localStorage.getItem('lms_theme') === 'dark'; } catch(e){ return false; } });
  React.useEffect(() => { document.documentElement.dataset.theme = dark ? 'dark' : 'light'; try { localStorage.setItem('lms_theme', dark ? 'dark' : 'light'); } catch(e){} }, [dark]);
  return [dark, setDark];
}

// Mobile (< 820 px) : les écrans du design passent en mise en page mobile
function useEstMobile(){ const q = () => window.matchMedia('(max-width: 819px)').matches; const [m, setM] = React.useState(q);
  React.useEffect(() => { const f = () => setM(q()); addEventListener('resize', f); return () => removeEventListener('resize', f); }, []); return m; }

// Route par le hash : #/page/param
function useRoute(defaut){ const lire = () => { const [page, ...reste] = location.hash.replace(/^#\/?/, '').split('/'); return {page:page || defaut, param:reste.length ? decodeURIComponent(reste.join('/')) : null}; };
  const [r, setR] = React.useState(lire); React.useEffect(() => { const f = () => setR(lire()); addEventListener('hashchange', f); return () => removeEventListener('hashchange', f); }, []); return r; }
const aller = (page, param) => { location.hash = `#/${page}${param != null ? '/' + encodeURIComponent(param) : ''}`; };

// Fichier base64 → ouverture dans un nouvel onglet (pièces, avis de virement)
function ouvrirFichier(f){ if(!f?.base64) return; const bin = atob(f.base64); const u8 = new Uint8Array(bin.length); for(let i=0;i<bin.length;i++) u8[i] = bin.charCodeAt(i);
  const url = URL.createObjectURL(new Blob([u8], {type:f.mime || 'application/octet-stream'})); window.open(url, '_blank', 'noopener'); setTimeout(() => URL.revokeObjectURL(url), 60000); }
const urlFichier = (f) => { if(!f?.base64) return null; return `data:${f.mime || 'application/octet-stream'};base64,${f.base64}`; };

// Écran de 2FA : QR (qrcodejs)
function QR({texte, s=176}){ const ref = React.useRef(null);
  React.useEffect(() => { if(!ref.current || !window.QRCode) return; ref.current.innerHTML = ''; new QRCode(ref.current, {text:texte, width:s, height:s, colorDark:'#0F1A16', colorLight:'#FFFFFF', correctLevel:QRCode.CorrectLevel.M}); }, [texte]);
  return <div ref={ref} style={{padding:12, background:'#fff', borderRadius:18, border:`1px solid ${V.line}`, width:s+24, height:s+24}}></div>; }

Object.assign(window, {Logo, Brand, STAGES, StageB, ASSUREURS, SvcArt, D, DC, Depuis, creerApi, useDonnees, ToastCtx, useToast, Toasts, useTheme, useEstMobile, useRoute, aller, ouvrirFichier, urlFichier, QR, LOGO_URL});
