// FOOTPASS Messenger v2 · Jeu d'états système réutilisable
//   FPState        · composant d'état (icône + titre + sous-texte + action), décliné par contexte
//   FPSkeleton     · squelettes de chargement (silhouettes du contenu à venir)
//   FPOfflineBanner· bandeau hors-ligne + file d'attente
//   FPSysLayer     · couche qui applique un état sur un écran réel (forcée via Tweaks), sans toucher son code
//   StatesGallery  · galerie qui montre tous les états + note de portage React Native
// Câblé dans le menu (via home.jsx) ; forçable depuis le panneau Tweaks (via main.jsx).

// ── Icônes d'état (formes simples, trait 1.7 · cohérent HMI/FPIcon) ──
const StI = {
  Bubble: ({ s = 26, c = 'currentColor' }) => (
    <svg width={s} height={s} viewBox="0 0 24 24" fill="none"><path d="M12 4c4.7 0 8.4 3.1 8.4 7.2S16.7 18.4 12 18.4c-.9 0-1.7-.1-2.5-.3L5.4 20l.9-3.4C4.6 15.3 3.6 13.4 3.6 11.2 3.6 7.1 7.3 4 12 4z" stroke={c} strokeWidth="1.7" strokeLinejoin="round"></path></svg>
  ),
  Link: ({ s = 26, c = 'currentColor' }) => (
    <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M9.5 14.5l5-5"></path><path d="M10.8 7.6l1.5-1.5a3.6 3.6 0 015.1 5.1l-1.5 1.5"></path><path d="M13.2 16.4l-1.5 1.5a3.6 3.6 0 01-5.1-5.1l1.5-1.5"></path></svg>
  ),
  Vault: ({ s = 26, c = 'currentColor' }) => (
    <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.7" strokeLinejoin="round"><rect x="3.2" y="4" width="17.6" height="14" rx="2.6"></rect><circle cx="10.5" cy="11" r="3"></circle><path d="M10.5 8v-.8M10.5 14v.8M13.5 11h.8M6.9 11h.8" strokeLinecap="round"></path><path d="M17 8.5v5" strokeLinecap="round"></path></svg>
  ),
  CloudOff: ({ s = 26, c = 'currentColor' }) => (
    <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M7.2 18h9a3.5 3.5 0 00.6-6.95A5 5 0 007.6 9.2"></path><path d="M7.2 18A3.9 3.9 0 016.5 10.4"></path><path d="M3 3l18 18"></path></svg>
  ),
  Alert: ({ s = 26, c = 'currentColor' }) => (
    <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M12 3.6L21.2 19H2.8L12 3.6z"></path><path d="M12 10v4.2"></path><path d="M12 17.4h.01"></path></svg>
  ),
  Lock: ({ s = 26, c = 'currentColor' }) => (
    <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.7" strokeLinejoin="round"><rect x="4.5" y="10.2" width="15" height="10.3" rx="2.4"></rect><path d="M7.8 10.2V7.6a4.2 4.2 0 018.4 0v2.6"></path><circle cx="12" cy="15.3" r="1.4" fill={c} stroke="none"></circle></svg>
  ),
  EyeOff: ({ s = 26, c = 'currentColor' }) => (
    <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M10.7 6.2A8.6 8.6 0 0112 6.1c5.4 0 8.7 5.9 8.7 5.9a15 15 0 01-2.4 3.1"></path><path d="M6 7.5C3.4 9.2 2.3 12 2.3 12s3.3 5.9 9.7 5.9a8.9 8.9 0 003.8-.8"></path><path d="M9.8 9.9a3 3 0 004.3 4.2"></path><path d="M3 3l18 18"></path></svg>
  ),
  Refresh: ({ s = 15, c = 'currentColor' }) => (
    <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M20 8a8 8 0 10.7 6"></path><path d="M20 3.5V8h-4.5"></path></svg>
  ),
  Info: ({ s = 18, c = 'currentColor' }) => (
    <svg width={s} height={s} viewBox="0 0 18 18" fill="none" stroke={c} strokeWidth="1.5"><circle cx="9" cy="9" r="7"></circle><path d="M9 8.2v4.2" strokeLinecap="round"></path><path d="M9 5.7h.01" strokeLinecap="round"></path></svg>
  ),
  Swiss: ({ s = 18, c = 'currentColor' }) => (
    <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.7"><rect x="4" y="4" width="16" height="16" rx="4.5"></rect><path d="M12 8.4v7.2M8.4 12h7.2" strokeWidth="2.1" strokeLinecap="round"></path></svg>
  ),
  Ledger: ({ s = 18, c = 'currentColor' }) => (
    <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.7" strokeLinejoin="round"><rect x="4.5" y="3.2" width="15" height="17.6" rx="2.3"></rect><path d="M8 8h8M8 11.5h8M8 15h5" strokeLinecap="round"></path></svg>
  ),
  FaceID: ({ s = 15, c = 'currentColor' }) => (
    <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M4 8V6a2 2 0 012-2h2M16 4h2a2 2 0 012 2v2M20 16v2a2 2 0 01-2 2h-2M8 20H6a2 2 0 01-2-2v-2"></path><path d="M9 10v1M15 10v1M12 9.5v3.2l-1 .8"></path><path d="M9.3 15c.8.7 1.7 1 2.7 1s1.9-.3 2.7-1"></path></svg>
  ),
};

// ═══════ COMPOSANT D'ÉTAT · FPState ═══════
// props : variant (empty|error|unauthorized|locked) · tone · icon (composant) ·
//         title · text · primary {label,onClick,tone} · secondary {label,onClick} · note (node)
function FPState({ variant = 'empty', tone, icon, title, text, primary, secondary, note }) {
  const Icon = icon || (variant === 'error' ? StI.Alert : variant === 'locked' ? StI.Lock : variant === 'unauthorized' ? StI.EyeOff : StI.Info);
  const t = tone || (variant === 'error' ? 'danger' : variant === 'locked' ? 'gold' : variant === 'unauthorized' ? 'neutral' : 'accent');
  const role = variant === 'error' ? 'alert' : 'status';
  return (
    <div className="fp-state" role={role} aria-live={variant === 'error' ? 'assertive' : 'polite'}>
      <span className={`fp-state-ic ${t === 'neutral' ? '' : 't-' + t}`}><Icon /></span>
      <div className="fp-state-title">{window.TX ? TX(title) : title}</div>
      {text ? <div className="fp-state-text">{window.TX ? TX(text) : text}</div> : null}
      {(primary || secondary) ? (
        <div className="fp-state-acts">
          {primary ? (
            <button className={`pg-btn ${primary.tone === 'plain' ? '' : 'primary'}`} onClick={primary.onClick}>
              {primary.icon ? <primary.icon s={15} /> : null}{window.TX ? TX(primary.label) : primary.label}
            </button>
          ) : null}
          {secondary ? <button className="fp-state-link" onClick={secondary.onClick}>{window.TX ? TX(secondary.label) : secondary.label}</button> : null}
        </div>
      ) : null}
      {note || null}
    </div>
  );
}

const FPStateNote = ({ children }) => (
  <span className="fp-state-note"><FPIcon.Shield s={10} /> {children}</span>
);

// ═══════ CHARGEMENT · squelettes ═══════
const SkBar = ({ w, h = 11, r, style }) => (
  <span className={`fp-skel-bar${r === 'c' ? ' circle' : ''}`} style={{ width: w, height: h, borderRadius: r === 'c' ? '50%' : undefined, ...style }} aria-hidden="true"></span>
);
function FPSkeleton({ kind = 'list', rows = 5 }) {
  const listRow = (i) => (
    <div className="fp-skel-row" key={i}>
      <SkBar w={40} h={40} r="c" />
      <span className="fp-skel-lines">
        <SkBar w={`${52 + (i % 3) * 12}%`} h={11} />
        <SkBar w={`${30 + (i % 2) * 10}%`} h={9} />
      </span>
      <SkBar w={26} h={9} />
    </div>
  );
  const vaultRow = (i) => (
    <div className="fp-skel-row" key={i}>
      <SkBar w={38} h={38} style={{ borderRadius: 11 }} />
      <span className="fp-skel-lines">
        <SkBar w={`${48 + (i % 3) * 13}%`} h={11} />
        <SkBar w={`${34 + (i % 2) * 8}%`} h={9} />
      </span>
      <SkBar w={46} h={18} style={{ borderRadius: 99 }} />
    </div>
  );
  const card = (i) => (
    <div className="fp-skel-card" key={i}>
      <SkBar w="46%" h={13} />
      <SkBar w="88%" h={10} />
      <SkBar w="62%" h={10} />
    </div>
  );
  const bubble = (i) => (
    <div className={`fp-skel-bub${i % 2 ? ' me' : ''}`} key={i}>
      <SkBar w={`${60 + (i % 3) * 30}px`} h={i % 2 ? 32 : 46} style={{ borderRadius: 16, display: 'block' }} />
    </div>
  );
  if (kind === 'chat') return <div className="fp-skel fp-skel-chat" aria-hidden="true">{[0, 1, 2, 3, 4].map(bubble)}</div>;
  if (kind === 'agenda' || kind === 'cards') return <div className="fp-skel" aria-hidden="true">{Array.from({ length: kind === 'agenda' ? 2 : 3 }).map((_, i) => card(i))}</div>;
  if (kind === 'home') return <div className="fp-skel" aria-hidden="true">{card(0)}{[0, 1, 2].map(listRow)}</div>;
  if (kind === 'vault') return <div className="fp-skel" aria-hidden="true"><div style={{ margin: '2px 0 14px' }}><SkBar w="55%" h={16} style={{ display: 'block', marginBottom: 8 }} /><SkBar w="80%" h={10} style={{ display: 'block' }} /></div>{[0, 1, 2, 3].map(vaultRow)}</div>;
  return <div className="fp-skel" aria-hidden="true">{Array.from({ length: rows }).map((_, i) => listRow(i))}</div>;
}

// ═══════ HORS-LIGNE · bandeau + file d'attente ═══════
function FPOfflineBanner({ queued = 0, onReconnect }) {
  return (
    <div className="fp-offline" role="status" aria-live="polite" onClick={onReconnect}>
      <span className="fp-offline-ic"><StI.CloudOff s={20} /></span>
      <span className="fp-offline-txt">
        <b>{TX('Hors ligne')}</b>
        <em>{TX('Vos messages partiront à la reconnexion.')}</em>
      </span>
      {queued > 0 ? (
        <span className="fp-offline-q"><span className="fp-dotpulse"></span>{queued} {TX('en attente')}</span>
      ) : null}
    </div>
  );
}

// ═══════ Contenu par contexte (copie courte, traduisible) ═══════
const SYS_SUPPORTED = ['messages', 'agenda', 'calls', 'home', 'vault', 'mandates'];
const SYS_META = {
  messages: { title: 'Messages', skel: 'list', tabbed: true },
  agenda: { title: 'Agenda', skel: 'agenda', tabbed: true },
  calls: { title: 'Appels', skel: 'list', tabbed: true },
  home: { title: 'Accueil', skel: 'home', tabbed: true },
  vault: { title: 'Coffre-fort', skel: 'vault', tabbed: false },
  mandates: { title: 'Autorisations', skel: 'list', tabbed: false },
};
const suisseNote = <FPStateNote>{TX('Hébergé en Suisse · AES-256')}</FPStateNote>;
// (context, state) → props FPState. Repli générique si combinaison non spécifiée.
function sysCopy(ctx, state, h) {
  // h : { flow, clear, toast } · actions branchées sur les vrais flux
  const retry = { label: 'Réessayer', icon: StI.Refresh, onClick: () => { h.clear(); h.toast(TX('Reconnexion…')); } };
  const ask = { label: 'Demander l’accès', onClick: () => { h.toast(TX('Demande d’accès envoyée · vous serez notifié')); h.clear(); } };
  const unlock = { label: 'Déverrouiller avec Face ID', icon: StI.FaceID, onClick: () => h.clear() };
  const M = {
    messages: {
      empty: { icon: StI.Bubble, title: 'Aucune conversation', text: 'Vos échanges vérifiés s’afficheront ici. Écrivez à un membre ou invitez un contact par son Pass ID.', primary: { label: 'Nouveau message', onClick: () => h.flow('newmsg') } },
      error: { title: 'Chargement impossible', text: 'Vos conversations n’ont pas pu être chargées. Vérifiez votre connexion, puis réessayez.', primary: retry },
      unauthorized: { title: 'Conversation protégée', text: 'Vous n’avez pas accès à cet échange. Son propriétaire peut vous y autoriser · chaque accès est explicite et révocable.', primary: ask },
      locked: { title: 'Messages verrouillés', text: 'Déverrouillez l’application avec Face ID pour afficher vos conversations chiffrées.', primary: unlock },
    },
    agenda: {
      empty: { icon: HMI.Calendar, title: 'Aucun rendez-vous', text: 'Planifiez un appel, une visite médicale ou une signature. Tout reste synchronisé et vérifié.', primary: { label: 'Nouvel événement', onClick: () => h.flow('newevent') } },
      error: { title: 'Agenda indisponible', text: 'Vos événements n’ont pas pu être chargés pour le moment.', primary: retry },
    },
    calls: {
      empty: { icon: HMI.Phone, title: 'Aucun appel', text: 'Vos appels chiffrés de bout en bout apparaîtront ici. Lancez un appel vers un contact vérifié.', primary: { label: 'Nouvel appel', onClick: () => h.flow('newcall') } },
      error: { title: 'Journal indisponible', text: 'Votre journal d’appels n’a pas pu être chargé.', primary: retry },
    },
    home: {
      error: { title: 'Accueil indisponible', text: 'Impossible de charger votre tableau de bord. Vérifiez votre connexion.', primary: retry },
      empty: { icon: StI.Info, title: 'Bienvenue', text: 'Votre activité vérifiée s’affichera ici au fur et à mesure.', primary: { label: 'Explorer', onClick: () => h.clear() } },
    },
    vault: {
      empty: { icon: StI.Vault, title: 'Votre coffre est prêt', text: 'Déposez un premier document · contrat, bilan, pièce d’identité. Vous seul y accédez.', primary: { label: 'Ajouter au coffre', onClick: () => { h.toast(TX('Ouverture du coffre…')); h.clear(); } }, note: suisseNote },
      unauthorized: { title: 'Document non partagé', text: 'Son propriétaire ne vous a pas accordé l’accès. Vous pouvez lui en faire la demande · chaque partage est explicite et révocable.', primary: ask, secondary: { label: 'En savoir plus', onClick: () => h.toast(TX('Chaque document reste privé · l’accès se gère par personne')) } },
      locked: { icon: StI.Lock, title: 'Coffre verrouillé', text: 'Déverrouillez votre coffre avec Face ID pour révéler vos documents chiffrés.', primary: unlock, note: suisseNote },
      error: { title: 'Coffre indisponible', text: 'Vos documents n’ont pas pu être chargés. Réessayez dans un instant.', primary: retry },
    },
    mandates: {
      empty: { icon: StI.Link, title: 'Aucune autorisation', text: 'Autorisez un club ou un agent à vous représenter · ou demandez une autorisation. Chaque accord est bilatéral et révocable.', primary: { label: 'Créer une autorisation', onClick: () => h.flow('mandates-new') } },
      unauthorized: { title: 'Accès non autorisé', text: 'Cette autorisation ne vous concerne pas. Seules les parties liées peuvent la consulter.', primary: ask },
      error: { title: 'Autorisations indisponibles', text: 'Vos autorisations n’ont pas pu être chargées pour le moment.', primary: retry },
    },
  };
  const GEN = {
    empty: { title: 'Rien pour le moment', text: 'Le contenu apparaîtra ici dès qu’il sera disponible.' },
    error: { title: 'Une erreur est survenue', text: 'Réessayez dans un instant.', primary: retry },
    unauthorized: { title: 'Accès non autorisé', text: 'Vous n’avez pas l’autorisation d’ouvrir ce contenu.', primary: ask },
    locked: { title: 'Contenu protégé', text: 'Déverrouillez avec Face ID pour révéler ce contenu.', primary: unlock, note: suisseNote },
  };
  return ((M[ctx] || {})[state]) || GEN[state] || GEN.empty;
}

// ═══════ COUCHE D'ÉTAT sur écran réel ═══════
// Recouvre l'écran actif SOUS la barre d'état (54px). Sur un onglet, la tab bar reste
// visible/cliquable ; sur une sous-page, un bouton retour révèle l'écran réel.
// context : messages|agenda|calls|home|vault|mandates · state : none|loading|empty|offline|error|unauthorized|locked
function FPSysLayer({ context, state, onClear, onFlow, showToast }) {
  if (!state || state === 'none' || SYS_SUPPORTED.indexOf(context) < 0) return null;
  const meta = SYS_META[context] || { title: '', skel: 'list', tabbed: true };
  const h = { flow: (k) => { onClear(); onFlow(k); }, clear: onClear, toast: (m) => showToast && showToast(m) };

  // Hors-ligne : simple bandeau sous la barre d'état ; le contenu réel reste visible dessous
  if (state === 'offline') {
    return (
      <div className={`fp-sys offline ${meta.tabbed ? 'tabbed' : ''}`}>
        <FPOfflineBanner queued={2} onReconnect={() => { showToast && showToast(TX('Reconnexion…')); onClear(); }} />
      </div>
    );
  }

  const isLoading = state === 'loading';
  return (
    <div className={`fp-sys ${meta.tabbed ? 'tabbed' : ''}`} data-screen-label={meta.title}>
      {!meta.tabbed ? (
        <div className="fp-sys-head">
          <button className="pg-back" onClick={onClear} aria-label={TX('Retour')}><FPIcon.Back c="currentColor" /></button>
          <span className="fp-sys-title">{TX(meta.title)}</span>
        </div>
      ) : null}
      <div className={`fp-sys-body ${isLoading ? '' : 'center'}`}>
        {isLoading ? (
          <React.Fragment>
            <FPSkeleton kind={meta.skel} />
            <span className="fp-sys-badge" role="status" aria-label={TX('Chargement')}>{TX('Chargement…')}</span>
          </React.Fragment>
        ) : (
          <FPState variant={state} {...sysCopy(context, state, h)} />
        )}
      </div>
    </div>
  );
}

// ═══════ GALERIE D'ÉTATS + note de portage ═══════
const GL_ITEMS = [
  { key: 'loading', tag: 'FPSkeleton', label: 'Chargement', where: 'Tous les écrans à contenu', render: () => <div className="gl-frame-body"><FPSkeleton kind="list" rows={4} /></div> },
  { key: 'empty', tag: 'FPState · empty', label: 'Vide (premier usage)', where: 'Messages · Autorisations · Coffre', render: () => <div className="gl-frame-body center"><FPState variant="empty" icon={StI.Link} title="Aucune autorisation" text="Autorisez un club ou un agent à vous représenter. Chaque accord est bilatéral et révocable." primary={{ label: 'Créer une autorisation', onClick: () => {} }} /></div> },
  { key: 'offline', tag: 'FPOfflineBanner', label: 'Hors-ligne', where: 'Messages · Appels · Accueil', render: () => <div className="gl-frame-body"><FPOfflineBanner queued={2} /><div style={{ padding: '10px 14px', opacity: 0.5 }}><FPSkeleton kind="list" rows={3} /></div></div> },
  { key: 'error', tag: 'FPState · error', label: 'Erreur', where: 'Tous les écrans', render: () => <div className="gl-frame-body center"><FPState variant="error" title="Chargement impossible" text="Vos conversations n’ont pas pu être chargées. Vérifiez votre connexion." primary={{ label: 'Réessayer', icon: StI.Refresh, onClick: () => {} }} /></div> },
  { key: 'unauthorized', tag: 'FPState · unauthorized', label: 'Non autorisé', where: 'Autorisations · Accès documentaire', render: () => <div className="gl-frame-body center"><FPState variant="unauthorized" title="Document non partagé" text="Son propriétaire ne vous a pas accordé l’accès. Vous pouvez lui en faire la demande." primary={{ label: 'Demander l’accès', onClick: () => {} }} /></div> },
  { key: 'locked', tag: 'FPState · locked', label: 'Contenu verrouillé', where: 'Coffre-fort', render: () => <div className="gl-frame-body center"><FPState variant="locked" title="Coffre verrouillé" text="Déverrouillez votre coffre avec Face ID pour révéler vos documents chiffrés." primary={{ label: 'Déverrouiller avec Face ID', icon: StI.FaceID, onClick: () => {} }} note={<FPStateNote>Hébergé en Suisse · AES-256</FPStateNote>} /></div> },
];
function StatesGalleryPage({ onBack }) {
  return (
    <PgPage title="États système" onBack={onBack}>
      <p className="gl-intro">{TX('Un composant d’état unique · icône, titre, sous-texte, une action · décliné par contexte. Chaque état est traité avec le même soin qu’un écran plein.')}</p>
      {GL_ITEMS.map((it) => (
        <div className="gl-item" key={it.key}>
          <div className="gl-cap"><b>{TX(it.label)}</b><em>{it.tag}</em><span>· {TX(it.where)}</span></div>
          <div className="gl-frame">{it.render()}</div>
        </div>
      ))}
      <div className="hm-sechead"><span>{TX('Portage React Native')}</span></div>
      <div className="gl-handoff">
        <h4>{TX('Composants ajoutés')}</h4>
        <p><span className="gl-code">FPState</span> · <span className="gl-code">FPSkeleton</span> · <span className="gl-code">FPOfflineBanner</span> · <span className="gl-code">FPSysLayer</span> · <span className="gl-code">AboutPage</span> · <span className="gl-code">StatesGalleryPage</span> · {TX('tous sans dépendance externe, prêts pour NativeWind.')}</p>
        <ul>
          <li><b>FPState</b> · {TX('props : variant · tone · icon · title · text · primary · secondary · note. Aucune logique interne (présentation pure).')}</li>
          <li><b>FPSkeleton</b> · {TX('kind : list · chat · vault · agenda · home · cards. Shimmer coupé si prefers-reduced-motion.')}</li>
          <li><b>FPSysLayer</b> · {TX('applique un état sur l’écran actif (chargement/vide/hors-ligne/erreur/non autorisé/verrouillé) sans modifier son code.')}</li>
          <li><b>{TX('Câblage')}</b> · <span className="gl-code">home.jsx</span> {TX('(menu → À propos, couche d’état dans FPShell)')} · <span className="gl-code">main.jsx</span> {TX('(Tweaks « États système »).')}</li>
          <li><b>{TX('Styles')}</b> · <span className="gl-code">states.css</span>, {TX('tokens --acc / pg-* / hm-*, clair + sombre.')}</li>
        </ul>
      </div>
      <div className="hm-listfoot"><FPIcon.Shield s={10} /> {TX('Aperçu de référence · design system FOOTPASS v2')}</div>
    </PgPage>
  );
}

Object.assign(window, { StI, FPState, FPStateNote, FPSkeleton, FPOfflineBanner, FPSysLayer, StatesGalleryPage, SYS_SUPPORTED, SYS_META });
