// Account — Profil, paramètres, connecteurs, contenus personnels.
// Chaque ligne est pilotée par `/api/v1/account` : le serveur décide de ce qui
// est actif et, quand il refuse, il fournit la raison. Aucun état n'est inventé
// ici, faute de quoi l'écran promettrait des fonctions qui n'existent pas.

const AC_VT = window.VT || {};
const AC_INK = AC_VT.ink || '#0a0a0a';
const AC_SUB = AC_VT.sub || '#6b7280';
const AC_BG = AC_VT.paper || '#f6f5f1';
const AC_SURF = AC_VT.surface || '#ffffff';
const AC_HAIR = AC_VT.hair || 'rgba(10,10,10,0.06)';
const AC_HAIR2 = AC_VT.hair2 || 'rgba(10,10,10,0.10)';
const AC_OK = AC_VT.success || '#059669';
const AC_MED = AC_VT.medBlue || '#3d6b8f';
const AC_DANGER = AC_VT.danger || '#b91c1c';
const AC_MED_TINT = AC_VT.medBlueTint || 'rgba(61,107,143,0.10)';
const AC_OK_TINT = AC_VT.successTint || 'rgba(5,150,105,0.10)';

// Libellés alignés mot pour mot sur l'application native
// (`mobile/src/features/account/account-screen.tsx`) : deux formulations
// différentes pour la même raison serveur finiraient par diverger.
const AC_REASONS = {
  organization_admin_required: 'Réservé à un futur administrateur de l’organisation.',
  identity_provider_managed: 'Cette action est gérée par le fournisseur d’identité.',
  simulated_identity: 'Indisponible avec une identité de démonstration.',
  step_up_unavailable: 'Une vérification d’identité renforcée est indisponible.',
  provider_unavailable: 'Aucun fournisseur réel n’est configuré.',
  source_unavailable: 'Aucune source approuvée n’alimente cette information.',
  single_option_supported: 'Une seule option est prise en charge dans cette version.',
  feature_not_in_j6a: 'Cette action ne fait pas partie du périmètre J6a.',
  secure_export_storage_unavailable: 'Aucun stockage chiffré qualifié n’est disponible pour l’export.',
  retention_policy_unapproved: 'La politique de rétention et de fermeture n’est pas approuvée.',
  feature_not_in_j6b1: 'Cette action ne fait pas partie du périmètre J6b1.',
  role_not_supported: 'Votre accès ne permet pas cette action.',
};

function acReason(capability) {
  if (!capability || capability.enabled) return null;
  return AC_REASONS[capability.reason_code] || 'Action indisponible.';
}

const AC_ZONES = [
  'Europe/Paris', 'Europe/Brussels', 'Europe/Zurich', 'Europe/London',
  'America/Montreal', 'America/Guadeloupe', 'Indian/Reunion',
];

const AC_VERIFICATION = { verified: 'vérifié', synthetic: 'synthétique', unverified: 'non vérifié' };
const AC_SALUTATIONS = { doctor: 'Dr.', professor: 'Pr.', none: '' };
const AC_DOC_TYPES = ['CRO', 'CRC', 'CRH', 'NOTE', 'LETTRE'];

function acPlural(count, singular, plural) {
  return `${count} ${count > 1 ? (plural || `${singular}s`) : singular}`;
}

function acDate(value) {
  if (!value) return '—';
  return new Intl.DateTimeFormat('fr-FR', { day: '2-digit', month: '2-digit', year: 'numeric' })
    .format(new Date(value));
}

// ── Section row ──
function SettingRow({ icon, label, sub, right, onClick, danger = false, reason = null, role, checked }) {
  const actionable = Boolean(onClick) && !reason;
  const body = (
    <>
      <div style={{
        width: 36, height: 36, borderRadius: 12,
        background: danger ? 'rgba(185,28,28,0.06)' : 'rgba(10,10,10,0.04)',
        display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
        opacity: reason ? 0.55 : 1,
      }}>
        <svg width="18" height="18" viewBox="0 0 24 24" fill="none"
          stroke={danger ? AC_DANGER : AC_INK} strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round">{icon}</svg>
      </div>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{
          fontSize: 14, fontWeight: 600, letterSpacing: '-0.1px',
          color: danger ? AC_DANGER : AC_INK, opacity: reason ? 0.6 : 1,
        }}>{label}</div>
        {sub && <div style={{ fontSize: 12, fontWeight: 500, color: AC_SUB, marginTop: 2 }}>{sub}</div>}
        {reason && (
          <div style={{ fontSize: 11.5, fontWeight: 500, color: AC_SUB, marginTop: 3, lineHeight: 1.35 }}>
            {reason}
          </div>
        )}
      </div>
      {right || (actionable ? (
        <svg width="16" height="16" viewBox="0 0 24 24" fill="none"
          stroke={AC_SUB} strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round">
          <path d="M9 6l6 6-6 6"/>
        </svg>
      ) : null)}
    </>
  );
  const shared = {
    width: '100%', display: 'flex', alignItems: 'center', gap: 14,
    padding: '14px 16px', background: 'none', border: 'none',
    fontFamily: 'inherit', textAlign: 'left',
  };
  if (!actionable) {
    return (
      <div style={shared} role={role} aria-disabled={reason ? 'true' : undefined}
        aria-checked={role === 'switch' ? Boolean(checked) : undefined}
        aria-label={[label, sub, reason].filter(Boolean).join('. ')}>{body}</div>
    );
  }
  return (
    <button onClick={onClick} className="press" role={role}
      aria-checked={role === 'switch' ? Boolean(checked) : undefined}
      style={{ ...shared, cursor: 'pointer' }}>{body}</button>
  );
}

// ── Section group ──
function SettingGroup({ title, children }) {
  return (
    <div style={{ marginBottom: 20 }}>
      <div className="mono" style={{
        fontSize: 10, fontWeight: 600, color: AC_SUB,
        letterSpacing: '0.5px', textTransform: 'uppercase',
        padding: '0 20px 8px',
      }}>{title}</div>
      <div style={{
        background: AC_SURF, borderRadius: 16, margin: '0 16px',
        border: `1px solid ${AC_HAIR}`,
        overflow: 'hidden',
      }}>
        {React.Children.map(children, (child, i) => (
          <React.Fragment key={i}>
            {i > 0 && <div style={{ height: 1, background: AC_HAIR, marginLeft: 66 }} />}
            {child}
          </React.Fragment>
        ))}
      </div>
    </div>
  );
}

// ── Interrupteur purement visuel ──
// Il vit à l'intérieur du bouton de la ligne : en faire un `<button>` produirait
// un bouton imbriqué, invalide en HTML, et un clic déclencherait les deux
// gestionnaires — donc deux appels serveur concurrents. C'est la ligne qui porte
// le rôle `switch` et l'unique gestionnaire.
function Toggle({ on, disabled = false }) {
  return (
    <div aria-hidden="true" style={{
      width: 44, height: 26, borderRadius: 12, padding: 2,
      background: on ? AC_INK : 'rgba(10,10,10,0.12)',
      display: 'flex', alignItems: 'center',
      justifyContent: on ? 'flex-end' : 'flex-start',
      transition: 'background .2s ease', opacity: disabled ? 0.5 : 1,
      flexShrink: 0,
    }}>
      <div style={{
        width: 22, height: 22, borderRadius: '50%', background: '#fff',
        boxShadow: '0 1px 3px rgba(10,10,10,0.15)',
      }} />
    </div>
  );
}

// ── Coque de feuille modale ──
function AcSheet({ title, subtitle, onClose, children, footer }) {
  return (
    <>
      <div onClick={onClose} style={{
        position: 'absolute', inset: 0, background: 'rgba(0,0,0,0.35)', zIndex: 200,
        animation: 'acFade .2s ease',
      }} />
      <div role="dialog" aria-modal="true" aria-label={title} style={{
        position: 'absolute', left: 16, right: 16, top: '50%', transform: 'translateY(-50%)',
        zIndex: 201, background: AC_SURF, borderRadius: AC_VT.rXl2 || 22,
        padding: '20px 20px 18px', boxShadow: AC_VT.shadowLg,
        maxHeight: '82%', display: 'flex', flexDirection: 'column',
        animation: 'acPop .22s cubic-bezier(.2,.7,.2,1)',
      }}>
        <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 12 }}>
          <div style={{ minWidth: 0 }}>
            <div style={{ fontSize: 16, fontWeight: 700, color: AC_INK, letterSpacing: '-0.2px' }}>{title}</div>
            {subtitle && (
              <div style={{ fontSize: 12.5, fontWeight: 500, color: AC_SUB, marginTop: 4, lineHeight: 1.45 }}>
                {subtitle}
              </div>
            )}
          </div>
          <button onClick={onClose} className="press" aria-label="Fermer" style={{
            background: 'none', border: 'none', cursor: 'pointer', padding: 0, flexShrink: 0, marginTop: 2,
          }}>
            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke={AC_SUB}
              strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round">
              <path d="M18 6L6 18M6 6l12 12" />
            </svg>
          </button>
        </div>
        <div className="vasco-scroll" style={{ flex: 1, overflowY: 'auto', marginTop: 16 }}>{children}</div>
        {footer && <div style={{ display: 'flex', gap: 8, marginTop: 16 }}>{footer}</div>}
        <style>{`
          @keyframes acFade { from { opacity: 0; } to { opacity: 1; } }
          @keyframes acPop {
            from { opacity: 0; transform: translateY(-46%) scale(.97); }
            to   { opacity: 1; transform: translateY(-50%) scale(1); }
          }
        `}</style>
      </div>
    </>
  );
}

function AcField({ label, hint, children }) {
  return (
    <label style={{ display: 'block', marginBottom: 14 }}>
      <div style={{ fontSize: 12, fontWeight: 650, color: AC_INK, marginBottom: 6 }}>{label}</div>
      {children}
      {hint && <div style={{ fontSize: 11, fontWeight: 500, color: AC_SUB, marginTop: 4 }}>{hint}</div>}
    </label>
  );
}

const acInputStyle = {
  width: '100%', height: 44, padding: '0 12px', borderRadius: AC_VT.rMd || 12,
  border: `1px solid ${AC_HAIR2}`, background: AC_SURF, outline: 'none',
  fontFamily: 'inherit', fontSize: 14, fontWeight: 500, color: AC_INK,
};

function AcButton({ children, onClick, variant = 'ghost', disabled = false, busy = false }) {
  const solid = variant === 'solid';
  const danger = variant === 'danger';
  return (
    <button onClick={onClick} disabled={disabled || busy} className="press" style={{
      flex: 1, height: AC_VT.hLg || 48, borderRadius: AC_VT.rLg || 16,
      background: danger ? AC_DANGER : solid ? AC_INK : 'none',
      border: solid || danger ? 'none' : `1px solid ${AC_HAIR2}`,
      fontFamily: 'inherit', fontSize: 14, fontWeight: 700,
      color: solid || danger ? '#fff' : AC_INK,
      cursor: disabled || busy ? 'default' : 'pointer',
      opacity: disabled ? 0.5 : 1,
      display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
    }}>
      {busy && <VascoSpinner size={15} color="#fff" thickness={2}
        style={{ borderColor: 'rgba(255,255,255,0.3)', borderTopColor: solid || danger ? '#fff' : AC_INK }} />}
      {children}
    </button>
  );
}

// ═══════════════════════════════════════════════════════════
// Édition du profil — PATCH /api/v1/account/profile
// ═══════════════════════════════════════════════════════════
function ProfileEditorSheet({ profile, busy, onClose, onSave }) {
  const [salutation, setSalutation] = React.useState(profile.salutation);
  const [givenName, setGivenName] = React.useState(profile.given_name);
  const [familyName, setFamilyName] = React.useState(profile.family_name);
  const [phone, setPhone] = React.useState(profile.phone || '');
  const [specialty, setSpecialty] = React.useState(profile.specialty || '');
  const [timeZone, setTimeZone] = React.useState(profile.time_zone);

  const zones = AC_ZONES.includes(profile.time_zone) ? AC_ZONES : [profile.time_zone].concat(AC_ZONES);
  const trimmedPhone = phone.trim();
  // Le serveur impose E.164 : autant refuser avant l'appel plutôt que
  // d'afficher un 422 générique.
  const phoneValid = trimmedPhone === '' || /^\+[1-9][0-9]{7,14}$/.test(trimmedPhone);
  // Ce que la feuille modifierait réellement. Calculé au rendu plutôt qu'à
  // l'envoi : un formulaire inchangé garde son bouton inerte, au lieu de
  // l'accepter puis d'annoncer en bas de l'écran qu'il n'y avait rien à faire.
  const changes = {};
  if (salutation !== profile.salutation) changes.salutation = salutation;
  if (givenName.trim() !== profile.given_name) changes.given_name = givenName.trim();
  if (familyName.trim() !== profile.family_name) changes.family_name = familyName.trim();
  if (trimmedPhone !== (profile.phone || '')) changes.phone = trimmedPhone;
  if (specialty.trim() !== (profile.specialty || '')) changes.specialty = specialty.trim();
  if (timeZone !== profile.time_zone) changes.time_zone = timeZone;

  const valid = Boolean(givenName.trim() && familyName.trim() && phoneValid)
    && Object.keys(changes).length > 0;

  const submit = () => {
    if (!valid || busy) return;
    onSave(changes);
  };

  return (
    <AcSheet
      title="Informations personnelles"
      subtitle="Ces valeurs sont enregistrées sur le serveur et réutilisées par les autres écrans."
      onClose={onClose}
      footer={<>
        <AcButton onClick={onClose}>Annuler</AcButton>
        <AcButton variant="solid" onClick={submit} disabled={!valid} busy={busy}>
          {busy ? 'Enregistrement…' : 'Enregistrer'}
        </AcButton>
      </>}>
      <AcField label="Civilité">
        <select value={salutation} onChange={(e) => setSalutation(e.target.value)} style={acInputStyle}>
          <option value="doctor">Docteur</option>
          <option value="professor">Professeur</option>
          <option value="none">Aucune</option>
        </select>
      </AcField>
      <AcField label="Prénom">
        <input value={givenName} onChange={(e) => setGivenName(e.target.value)} maxLength={120} style={acInputStyle} />
      </AcField>
      <AcField label="Nom">
        <input value={familyName} onChange={(e) => setFamilyName(e.target.value)} maxLength={120} style={acInputStyle} />
      </AcField>
      <AcField label="Téléphone" hint="Format international, par exemple +33612345678. Laisser vide pour retirer.">
        <input value={phone} onChange={(e) => setPhone(e.target.value)} inputMode="tel" style={{
          ...acInputStyle,
          borderColor: phoneValid ? AC_HAIR2 : AC_DANGER,
        }} />
      </AcField>
      <AcField label="Spécialité" hint="Laisser vide pour retirer.">
        <input value={specialty} onChange={(e) => setSpecialty(e.target.value)} maxLength={120} style={acInputStyle} />
      </AcField>
      <AcField label="Fuseau horaire">
        <select value={timeZone} onChange={(e) => setTimeZone(e.target.value)} style={acInputStyle}>
          {zones.map((zone) => <option key={zone} value={zone}>{zone}</option>)}
        </select>
      </AcField>
    </AcSheet>
  );
}

// ═══════════════════════════════════════════════════════════
// Fiche du cabinet — GET / PUT /api/v1/clinic
// ═══════════════════════════════════════════════════════════
// La feuille est montée sur la fiche déjà lue : `version` vaut 0 tant qu'aucune
// n'existe, et l'enregistrement la crée. Le parent la remonte (via `key`) sur
// chaque version reçue, pour que les champs repartent des valeurs du serveur
// après un conflit.
function ClinicSheet({ clinic, organizationName, organizationReason, busy, error, onClose, onSave }) {
  const [name, setName] = React.useState(clinic.name || '');
  const [addressLine, setAddressLine] = React.useState(clinic.address_line || '');
  const [postalCode, setPostalCode] = React.useState(clinic.postal_code || '');
  const [city, setCity] = React.useState(clinic.city || '');
  const [phone, setPhone] = React.useState(clinic.phone || '');
  const [email, setEmail] = React.useState(clinic.email || '');

  const fields = {
    name: name.trim(),
    address_line: addressLine.trim(),
    postal_code: postalCode.trim(),
    city: city.trim(),
    phone: phone.trim(),
    email: email.trim(),
  };
  // Un PUT remplace la fiche entière : le bouton reste inerte tant que rien ne
  // change, plutôt que d'accepter un envoi qui n'écrirait rien de nouveau.
  const changed = Object.keys(fields).some((key) => fields[key] !== (clinic[key] || ''));

  const submit = () => {
    if (!changed || busy) return;
    onSave(fields);
  };

  return (
    <AcSheet
      title="Ma clinique"
      subtitle="Adresse et coordonnées du cabinet, enregistrées sur le serveur pour toute l’organisation."
      onClose={onClose}
      footer={<>
        <AcButton onClick={onClose}>Annuler</AcButton>
        <AcButton variant="solid" onClick={submit} disabled={!changed} busy={busy}>
          {busy ? 'Enregistrement…' : 'Enregistrer'}
        </AcButton>
      </>}>
      {error && (
        <div role="alert" style={{
          background: 'rgba(185,28,28,0.06)', borderRadius: 12, padding: '10px 12px',
          marginBottom: 14, fontSize: 12.5, fontWeight: 600, color: AC_DANGER, lineHeight: 1.45,
        }}>{error}</div>
      )}
      <AcField label="Nom du cabinet" hint="Laisser vide pour retirer.">
        <input value={name} onChange={(e) => setName(e.target.value)} maxLength={160} style={acInputStyle} />
      </AcField>
      <AcField label="Adresse">
        <input value={addressLine} onChange={(e) => setAddressLine(e.target.value)} maxLength={200} style={acInputStyle} />
      </AcField>
      <AcField label="Code postal">
        <input value={postalCode} onChange={(e) => setPostalCode(e.target.value)} maxLength={16} style={acInputStyle} />
      </AcField>
      <AcField label="Ville">
        <input value={city} onChange={(e) => setCity(e.target.value)} maxLength={120} style={acInputStyle} />
      </AcField>
      <AcField label="Téléphone">
        <input value={phone} onChange={(e) => setPhone(e.target.value)} inputMode="tel" maxLength={24} style={acInputStyle} />
      </AcField>
      <AcField label="Email">
        <input value={email} onChange={(e) => setEmail(e.target.value)} inputMode="email" maxLength={254} style={acInputStyle} />
      </AcField>

      {/* L'organisation reste ce que le serveur en dit : la fiche du cabinet ne
          la renomme pas, et la raison du refus est celle de `edit_clinic`. */}
      <AcField label="Organisation" hint={organizationReason || undefined}>
        <input value={organizationName} readOnly disabled
          style={{ ...acInputStyle, background: 'rgba(10,10,10,0.04)', color: AC_SUB }} />
      </AcField>

      {/* La sous-ligne d'origine annonce un logo. Il n'est pas construit : le
          dire ici vaut mieux qu'un bouton qui ne téléverserait rien. */}
      <div style={{
        border: `1px solid ${AC_HAIR}`, borderRadius: AC_VT.rMd || 12,
        padding: '12px 14px', marginBottom: 4, background: 'rgba(10,10,10,0.02)',
      }}>
        <div style={{ fontSize: 12, fontWeight: 650, color: AC_INK, opacity: 0.6 }}>Logo</div>
        <div style={{ fontSize: 11.5, fontWeight: 500, color: AC_SUB, marginTop: 4, lineHeight: 1.4 }}>
          Indisponible : téléversement d’image non construit.
        </div>
      </div>
    </AcSheet>
  );
}

// ═══════════════════════════════════════════════════════════
// Déconnexion de tous les appareils — POST /api/v1/auth/logout-all
// ═══════════════════════════════════════════════════════════
function LogoutAllSheet({ busy, onCancel, onConfirm }) {
  return (
    <AcSheet
      title="Déconnecter tous les appareils ?"
      subtitle="Toutes les sessions actives sont révoquées, y compris celle-ci. Vous reviendrez au Login."
      onClose={onCancel}
      footer={<>
        <AcButton onClick={onCancel}>Annuler</AcButton>
        <AcButton variant="danger" onClick={onConfirm} busy={busy}>
          {busy ? 'Révocation…' : 'Tout déconnecter'}
        </AcButton>
      </>}>
      <div style={{ fontSize: 13, fontWeight: 500, color: AC_SUB, lineHeight: 1.5 }}>
        Cette action est immédiate et ne peut pas être annulée. Elle n'efface aucune donnée clinique :
        elle met seulement fin aux sessions ouvertes.
      </div>
    </AcSheet>
  );
}

// ═══════════════════════════════════════════════════════════
// Contenus personnels — prompts et modèles de documents
// ═══════════════════════════════════════════════════════════
function ContentEditorSheet({ kind, item, busy, onClose, onSave }) {
  const isPrompt = kind === 'prompts';
  const [name, setName] = React.useState(item?.name || '');
  const [description, setDescription] = React.useState(item?.description || '');
  const [documentType, setDocumentType] = React.useState(item?.document_type || 'CRO');
  const [content, setContent] = React.useState(item?.content || '');
  const maxContent = isPrompt ? 8000 : 20000;
  const valid = name.trim() && content.trim() && content.length <= maxContent;

  const submit = () => {
    if (!valid || busy) return;
    if (isPrompt) {
      onSave({ name: name.trim(), description: description.trim() || null, content });
      return;
    }
    onSave({ name: name.trim(), document_type: documentType, content });
  };

  return (
    <AcSheet
      title={item ? 'Modifier' : isPrompt ? 'Nouveau prompt' : 'Nouveau modèle'}
      subtitle={isPrompt
        ? 'Un prompt personnel est réutilisable dans vos conversations Assistant.'
        : 'Un modèle sert de trame à un document clinique. Il ne remplit rien automatiquement.'}
      onClose={onClose}
      footer={<>
        <AcButton onClick={onClose}>Annuler</AcButton>
        <AcButton variant="solid" onClick={submit} disabled={!valid} busy={busy}>
          {busy ? 'Enregistrement…' : 'Enregistrer'}
        </AcButton>
      </>}>
      <AcField label="Nom">
        <input value={name} onChange={(e) => setName(e.target.value)} maxLength={120} style={acInputStyle} />
      </AcField>
      {isPrompt ? (
        <AcField label="Description" hint="Facultative.">
          <input value={description} onChange={(e) => setDescription(e.target.value)}
            maxLength={500} style={acInputStyle} />
        </AcField>
      ) : (
        <AcField label="Type de document">
          <select value={documentType} onChange={(e) => setDocumentType(e.target.value)} style={acInputStyle}>
            {AC_DOC_TYPES.map((type) => <option key={type} value={type}>{type}</option>)}
          </select>
        </AcField>
      )}
      <AcField label="Contenu" hint={`${content.length} / ${maxContent} caractères`}>
        <textarea value={content} onChange={(e) => setContent(e.target.value)} rows={8} style={{
          ...acInputStyle, height: 'auto', padding: '10px 12px', resize: 'vertical', lineHeight: 1.5,
          borderColor: content.length > maxContent ? AC_DANGER : AC_HAIR2,
        }} />
      </AcField>
    </AcSheet>
  );
}

function ContentScreen({ kind, accent, runtime, onBack, onChanged }) {
  const isPrompt = kind === 'prompts';
  const resource = isPrompt ? runtime.prompts : runtime.templates;
  const [contentStatus, setContentStatus] = React.useState('active');
  const [items, setItems] = React.useState(null);
  const [error, setError] = React.useState(null);
  const [busy, setBusy] = React.useState(false);
  const [editing, setEditing] = React.useState(null);

  const load = React.useCallback(async (nextStatus) => {
    setError(null);
    try {
      const response = await resource.list(nextStatus || contentStatus);
      setItems(response.items);
    } catch (requestError) {
      setItems([]);
      setError(requestError?.message || 'Chargement impossible');
    }
  }, [resource, contentStatus]);

  React.useEffect(() => { void load(); }, [load]);

  const save = async (payload) => {
    setBusy(true);
    try {
      if (editing && editing.id) await resource.update(editing.id, editing.version, payload);
      else await resource.create(payload);
      // La liste rechargée porte l'élément enregistré : c'est la preuve, et
      // elle reste à l'écran.
      setEditing(null);
      await load();
      if (onChanged) onChanged();
    } catch (requestError) {
      showToast(requestError?.message || 'Enregistrement impossible', 'error');
      // Un conflit de version rend le brouillon inutilisable tel quel : la
      // liste rechargée porte les versions à jour.
      if (requestError?.status === 409) { setEditing(null); await load(); }
    } finally {
      setBusy(false);
    }
  };

  const toggleState = async (item) => {
    setBusy(true);
    try {
      await resource.setState(item.id, item.version, item.status === 'active' ? 'archived' : 'active');
      // L'élément change de section dans la liste, archivé ou restauré.
      await load();
      if (onChanged) onChanged();
    } catch (requestError) {
      showToast(requestError?.message || 'Action impossible', 'error');
      if (requestError?.status === 409) await load();
    } finally {
      setBusy(false);
    }
  };

  // Ouvrir l'éditeur d'un élément existant exige son contenu complet : la
  // liste ne renvoie qu'un résumé avec la longueur.
  const openExisting = async (item) => {
    setBusy(true);
    try {
      setEditing(await resource.get(item.id));
    } catch (requestError) {
      showToast(requestError?.message || 'Ouverture impossible', 'error');
    } finally {
      setBusy(false);
    }
  };

  return (
    <div style={{
      position: 'absolute', inset: 0, zIndex: 100, background: AC_BG, color: AC_INK,
      display: 'flex', flexDirection: 'column',
      fontFamily: "'Inter Tight', -apple-system, system-ui, sans-serif",
    }}>
      <div style={{ padding: '62px 16px 8px', display: 'flex', alignItems: 'center', gap: 12 }}>
        <button onClick={onBack} className="press" aria-label="Retour" style={{
          width: 34, height: 34, borderRadius: 12, background: AC_SURF, border: `1px solid ${AC_HAIR}`,
          display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', padding: 0,
        }}>
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke={AC_INK}
            strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M15 6l-6 6 6 6"/></svg>
        </button>
        <div style={{ flex: 1, fontSize: 16, fontWeight: 700, letterSpacing: '-0.3px' }}>
          {isPrompt ? 'Prompts personnalisés' : 'Modèles de documents'}
        </div>
        <button onClick={() => setEditing({})} className="press" aria-label="Créer" style={{
          height: 34, padding: '0 14px', borderRadius: 12, background: AC_INK, border: 'none',
          color: '#fff', fontFamily: 'inherit', fontSize: 13, fontWeight: 700, cursor: 'pointer',
        }}>Nouveau</button>
      </div>

      <div style={{ padding: '4px 16px 12px', display: 'flex', gap: 0 }}>
        <div style={{ display: 'flex', background: 'rgba(10,10,10,0.04)', borderRadius: 12, padding: 2 }}>
          {[['active', 'Actifs'], ['archived', 'Archivés']].map(([value, label]) => (
            <button key={value} className="press"
              onClick={() => { setContentStatus(value); setItems(null); void load(value); }}
              style={{
                padding: '6px 14px', borderRadius: 8, border: 'none',
                background: contentStatus === value ? AC_SURF : 'transparent',
                boxShadow: contentStatus === value ? '0 1px 3px rgba(10,10,10,0.08)' : 'none',
                fontFamily: 'inherit', fontSize: 12, fontWeight: 650,
                color: contentStatus === value ? AC_INK : AC_SUB, cursor: 'pointer',
              }}>{label}</button>
          ))}
        </div>
      </div>

      <div className="vasco-scroll" style={{ flex: 1, overflowY: 'auto', padding: '0 16px 24px' }}>
        {error && (
          <div role="alert" style={{
            background: 'rgba(185,28,28,0.06)', borderRadius: 14, padding: '12px 14px', marginBottom: 12,
            fontSize: 12.5, fontWeight: 600, color: AC_DANGER,
          }}>{error}</div>
        )}
        {items === null && (
          <div style={{ display: 'flex', justifyContent: 'center', padding: '32px 0' }}>
            <VascoSpinner size={22} color={accent} />
          </div>
        )}
        {items !== null && items.length === 0 && !error && (
          <div style={{ textAlign: 'center', padding: '40px 20px', color: AC_SUB }}>
            <div style={{ fontSize: 14, fontWeight: 650, color: AC_INK }}>
              {contentStatus === 'active' ? 'Aucun élément actif' : 'Aucun élément archivé'}
            </div>
            <div style={{ fontSize: 12.5, fontWeight: 500, marginTop: 6, lineHeight: 1.45 }}>
              {contentStatus === 'active'
                ? 'Utilisez « Nouveau » pour en créer un. Il sera enregistré sur le serveur.'
                : 'Les éléments archivés restent récupérables ici.'}
            </div>
          </div>
        )}
        {(items || []).map((item) => (
          <div key={item.id} style={{
            background: AC_SURF, borderRadius: 16, border: `1px solid ${AC_HAIR}`,
            padding: '14px 16px', marginBottom: 10,
          }}>
            <div style={{ display: 'flex', alignItems: 'flex-start', gap: 10 }}>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontSize: 14, fontWeight: 650, color: AC_INK }}>{item.name}</div>
                <div style={{ fontSize: 12, fontWeight: 500, color: AC_SUB, marginTop: 3, lineHeight: 1.4 }}>
                  {isPrompt
                    ? (item.description || 'Sans description')
                    : `Type ${item.document_type}`}
                </div>
                <div className="mono" style={{ fontSize: 10.5, fontWeight: 600, color: AC_SUB, marginTop: 6 }}>
                  {item.content_length} caractères · v{item.version} · modifié le {acDate(item.updated_at)}
                </div>
              </div>
              {item.status === 'archived' && (
                <span className="mono" style={{
                  fontSize: 9.5, fontWeight: 700, padding: '3px 8px', borderRadius: 999,
                  background: 'rgba(10,10,10,0.05)', color: AC_SUB, flexShrink: 0,
                }}>ARCHIVÉ</span>
              )}
            </div>
            <div style={{ display: 'flex', gap: 8, marginTop: 12 }}>
              <button onClick={() => void openExisting(item)} disabled={busy} className="press" style={{
                flex: 1, height: 38, borderRadius: 12, background: 'none', border: `1px solid ${AC_HAIR2}`,
                fontFamily: 'inherit', fontSize: 13, fontWeight: 650, color: AC_INK,
                cursor: busy ? 'default' : 'pointer',
              }}>Modifier</button>
              <button onClick={() => void toggleState(item)} disabled={busy} className="press" style={{
                flex: 1, height: 38, borderRadius: 12, background: 'none', border: `1px solid ${AC_HAIR2}`,
                fontFamily: 'inherit', fontSize: 13, fontWeight: 650,
                color: item.status === 'active' ? AC_DANGER : AC_MED,
                cursor: busy ? 'default' : 'pointer',
              }}>{item.status === 'active' ? 'Archiver' : 'Restaurer'}</button>
            </div>
          </div>
        ))}
      </div>

      {editing && (
        <ContentEditorSheet
          kind={kind}
          item={editing.id ? editing : null}
          busy={busy}
          onClose={() => setEditing(null)}
          onSave={save}
        />
      )}
    </div>
  );
}

// ═══════════════════════════════════════════════════════════
// Statistiques — GET /api/v1/account/activity
// ═══════════════════════════════════════════════════════════
const AC_PERIODS = [['week', 'Sem'], ['month', 'Mois'], ['year', 'An']];
const AC_DOC_LABELS = { CRO: 'Opératoire', CRC: 'Consultation', CRH: 'Hospitalisation', IMG: 'Imagerie', EXAM: 'Examen' };
const AC_APPT_LABELS = {
  consultation: 'Consultation', procedure: 'Intervention', emergency: 'Urgence',
  follow_up: 'Suivi', imaging: 'Imagerie', other: 'Autre',
};
const AC_SERIES_COLORS = ['#3b82f6', AC_OK, '#f59e0b', '#ef4444', '#8b5cf6', AC_SUB];

function acSeriesLabels(points, period, timeZone) {
  const format = (date, options) => new Intl.DateTimeFormat('fr-FR', { ...options, timeZone }).format(date);
  return points.map((point) => {
    const date = new Date(point.from);
    if (period === 'week') return format(date, { weekday: 'short' }).toUpperCase().slice(0, 3);
    if (period === 'year') return format(date, { month: 'short' }).replace('.', '').toUpperCase().slice(0, 3);
    return format(date, { day: 'numeric' });
  });
}

function AcLineChart({ data, color, labels, height = 90 }) {
  const w = 280;
  const pad = 20;
  const max = Math.max.apply(null, data.concat([1]));
  const points = data.map((value, index) => ({
    x: pad + (data.length === 1 ? (w - pad * 2) / 2 : (index / (data.length - 1)) * (w - pad * 2)),
    y: pad + (1 - value / max) * (height - pad * 2),
  }));
  const line = points.map((point, index) => `${index === 0 ? 'M' : 'L'} ${point.x} ${point.y}`).join(' ');
  const area = `${line} L ${points[points.length - 1].x} ${height - pad} L ${points[0].x} ${height - pad} Z`;
  // Au-delà d'une dizaine de seaux, toutes les étiquettes se chevauchent.
  const step = Math.ceil(labels.length / 7);
  return (
    <svg viewBox={`0 0 ${w} ${height + 20}`} style={{ width: '100%', height: 'auto' }}>
      <path d={area} fill={color} opacity="0.15" />
      <path d={line} fill="none" stroke={color} strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" />
      {points.map((point, index) => (data.length > 14 ? null : (
        <circle key={index} cx={point.x} cy={point.y} r="3.5" fill="#fff" stroke={color} strokeWidth="1.7" />
      )))}
      {labels.map((label, index) => (index % step === 0 ? (
        <text key={index} x={points[index].x} y={height + 12} textAnchor="middle" fontSize="10"
          fontWeight="600" fill={AC_SUB} fontFamily="'JetBrains Mono', monospace">{label}</text>
      ) : null))}
    </svg>
  );
}

function AcDistribution({ title, entries, total }) {
  return (
    <div style={{ marginTop: 4 }}>
      <div className="mono" style={{
        fontSize: 10, fontWeight: 600, color: AC_SUB, letterSpacing: '0.4px',
        textTransform: 'uppercase', marginBottom: 8,
      }}>{title}</div>
      {total === 0 && (
        <div style={{ fontSize: 12.5, fontWeight: 500, color: AC_SUB, padding: '4px 0' }}>
          Aucune donnée sur la période.
        </div>
      )}
      {total > 0 && entries.map(([label, value], index) => {
        const percent = Math.round((value / total) * 100);
        return (
          <div key={label} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '6px 0' }}>
            <div style={{ width: 92, fontSize: 12, fontWeight: 500, color: AC_INK, flexShrink: 0 }}>{label}</div>
            <div style={{ flex: 1, height: 8, borderRadius: 4, background: 'rgba(10,10,10,0.04)' }}>
              <div style={{
                width: `${percent}%`, height: '100%', borderRadius: 4,
                background: AC_SERIES_COLORS[index % AC_SERIES_COLORS.length],
              }} />
            </div>
            <div className="mono" style={{
              fontSize: 11, fontWeight: 600, color: AC_SUB, width: 62,
              textAlign: 'right', whiteSpace: 'nowrap',
            }}>{value} · {percent}%</div>
          </div>
        );
      })}
    </div>
  );
}

function StatsScreen({ accent, runtime, capabilities, onBack }) {
  const [period, setPeriod] = React.useState('month');
  const [activity, setActivity] = React.useState(null);
  const [error, setError] = React.useState(null);

  React.useEffect(() => {
    let cancelled = false;
    setActivity(null);
    setError(null);
    (async () => {
      try {
        const response = await runtime.activity(period);
        if (!cancelled) setActivity(response);
      } catch (requestError) {
        if (!cancelled) setError(requestError?.message || 'Activité indisponible');
      }
    })();
    return () => { cancelled = true; };
  }, [runtime, period]);

  const kpiCards = activity ? [
    ['Patients vus', activity.kpis.patients_seen],
    ['Rendez-vous', activity.kpis.appointments],
    ['Comptes rendus', activity.kpis.clinical_reports],
    ['Documents', activity.kpis.documents],
    ['Conversations', activity.kpis.conversations],
    ['Jours actifs', activity.kpis.active_days],
  ] : [];

  const rangeLabel = activity
    ? `${acDate(activity.current_range.from)} – ${acDate(activity.current_range.to)}`
    : '';

  return (
    <div style={{
      position: 'absolute', inset: 0, zIndex: 100, background: AC_BG, color: AC_INK,
      display: 'flex', flexDirection: 'column',
      fontFamily: "'Inter Tight', -apple-system, system-ui, sans-serif",
    }}>
      <div style={{ padding: '62px 16px 8px', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
        <button onClick={onBack} className="press" aria-label="Retour" style={{
          width: 34, height: 34, borderRadius: 12, background: AC_SURF, border: `1px solid ${AC_HAIR}`,
          display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', padding: 0,
        }}>
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke={AC_INK}
            strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M15 6l-6 6 6 6"/></svg>
        </button>
        <div style={{ fontSize: 16, fontWeight: 700, letterSpacing: '-0.3px' }}>Statistiques</div>
        <div style={{ display: 'flex', background: 'rgba(10,10,10,0.04)', borderRadius: 12, padding: 2 }}>
          {AC_PERIODS.map(([value, label]) => (
            <button key={value} onClick={() => setPeriod(value)} className="press" style={{
              padding: '5px 10px', borderRadius: 8, border: 'none',
              background: period === value ? AC_SURF : 'transparent',
              boxShadow: period === value ? '0 1px 3px rgba(10,10,10,0.08)' : 'none',
              fontFamily: "'JetBrains Mono', monospace", fontSize: 9, fontWeight: 600,
              color: period === value ? AC_INK : AC_SUB, cursor: 'pointer',
              letterSpacing: '0.3px', textTransform: 'uppercase',
            }}>{label}</button>
          ))}
        </div>
      </div>

      <div className="vasco-scroll" style={{ flex: 1, overflowY: 'auto', padding: '12px 16px 24px' }}>
        <div style={{ marginBottom: 16 }}>
          <div style={{ fontSize: 22, fontWeight: 800, letterSpacing: '-0.5px' }}>Activité</div>
          <div style={{ fontSize: 13, fontWeight: 500, color: AC_SUB, marginTop: 2 }}>
            {rangeLabel || 'Calculée sur vos données réelles'}
          </div>
        </div>

        {error && (
          <div role="alert" style={{
            background: 'rgba(185,28,28,0.06)', borderRadius: 14, padding: '12px 14px', marginBottom: 12,
            fontSize: 12.5, fontWeight: 600, color: AC_DANGER,
          }}>{error}</div>
        )}

        {!activity && !error && (
          <div style={{ display: 'flex', justifyContent: 'center', padding: '40px 0' }}>
            <VascoSpinner size={22} color={accent} />
          </div>
        )}

        {activity && activity.data_state === 'empty' && (
          <div style={{
            background: AC_SURF, borderRadius: 16, border: `1px solid ${AC_HAIR}`,
            padding: '24px 16px', textAlign: 'center', marginBottom: 16,
          }}>
            <div style={{ fontSize: 14, fontWeight: 650 }}>Aucune activité sur la période</div>
            <div style={{ fontSize: 12.5, fontWeight: 500, color: AC_SUB, marginTop: 6, lineHeight: 1.45 }}>
              Les compteurs restent à zéro tant qu'aucun élément n'est créé.
            </div>
          </div>
        )}

        {activity && (
          <>
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 8, marginBottom: 16 }}>
              {kpiCards.map(([label, kpi]) => (
                <div key={label} style={{
                  background: AC_SURF, borderRadius: 16, padding: '14px 12px', border: `1px solid ${AC_HAIR}`,
                }}>
                  <div style={{ fontSize: 22, fontWeight: 800, letterSpacing: '-0.5px' }}>{kpi.current}</div>
                  <div style={{ fontSize: 11, fontWeight: 500, color: AC_SUB, marginTop: 2, lineHeight: 1.25 }}>{label}</div>
                  <div className="mono" style={{
                    fontSize: 11, fontWeight: 600, marginTop: 4,
                    color: kpi.delta_percent === null ? AC_SUB : kpi.delta_percent >= 0 ? AC_OK : AC_DANGER,
                  }}>
                    {kpi.delta_percent === null ? '—' : `${kpi.delta_percent >= 0 ? '+' : ''}${kpi.delta_percent}%`}
                  </div>
                </div>
              ))}
            </div>

            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8, marginBottom: 16 }}>
              <div style={{ background: AC_SURF, borderRadius: 16, padding: '14px 12px', border: `1px solid ${AC_HAIR}` }}>
                <div style={{ fontSize: 13, fontWeight: 700, marginBottom: 8 }}>Patients vus</div>
                <AcLineChart
                  data={activity.series.patients_seen.map((point) => point.value)}
                  labels={acSeriesLabels(activity.series.patients_seen, activity.period, activity.time_zone)}
                  color="#3b82f6" height={80} />
              </div>
              <div style={{ background: AC_SURF, borderRadius: 16, padding: '14px 12px', border: `1px solid ${AC_HAIR}` }}>
                <div style={{ fontSize: 13, fontWeight: 700, marginBottom: 8 }}>Comptes rendus</div>
                <AcLineChart
                  data={activity.series.clinical_reports.map((point) => point.value)}
                  labels={acSeriesLabels(activity.series.clinical_reports, activity.period, activity.time_zone)}
                  color={AC_OK} height={80} />
              </div>
            </div>

            <div style={{
              background: AC_SURF, borderRadius: 16, padding: 16, border: `1px solid ${AC_HAIR}`, marginBottom: 16,
            }}>
              <div style={{ fontSize: 14, fontWeight: 700, marginBottom: 12 }}>Répartition</div>
              <AcDistribution
                title="Documents"
                entries={Object.keys(AC_DOC_LABELS).map((key) => [AC_DOC_LABELS[key], activity.distributions.documents[key]])}
                total={activity.kpis.documents.current} />
              <div style={{ height: 12 }} />
              <AcDistribution
                title="Rendez-vous"
                entries={Object.keys(AC_APPT_LABELS).map((key) => [AC_APPT_LABELS[key], activity.distributions.appointments[key]])}
                total={activity.kpis.appointments.current} />
            </div>
          </>
        )}

        {/* Ces deux blocs existaient en maquette avec des chiffres inventés.
            L'API ne les alimente pas : ils restent visibles mais explicitement
            indisponibles, avec la raison renvoyée par le serveur. */}
        <SettingGroup title="Non alimenté par le serveur">
          <SettingRow
            icon={<><rect x="1" y="4" width="22" height="16" rx="2"/><line x1="1" y1="10" x2="23" y2="10"/></>}
            label="Facturation"
            reason={acReason(capabilities?.billing)} />
          <SettingRow
            icon={<><path d="M3 3v18h18"/><path d="M7 14l4-4 4 4 5-6"/></>}
            label="Interventions par organe"
            reason={acReason(capabilities?.intervention_statistics)} />
        </SettingGroup>
      </div>
    </div>
  );
}

// ═══════════════════════════════════════════════════════════
// AccountScreen
// ═══════════════════════════════════════════════════════════
function AccountScreen({ accent, onTabChange, startOnStats, onStatsClosed, runtime = null, onLoggedOut }) {
  const [overview, setOverview] = React.useState(() => window.VASCO_RUNTIME?.account || null);
  const [error, setError] = React.useState(null);
  const [refreshing, setRefreshing] = React.useState(false);
  const [busy, setBusy] = React.useState(false);
  const [showStats, setShowStats] = React.useState(!!startOnStats);
  const [contentScreen, setContentScreen] = React.useState(null);
  const [profileOpen, setProfileOpen] = React.useState(false);
  const [logoutAllOpen, setLogoutAllOpen] = React.useState(false);
  const [clinic, setClinic] = React.useState(null);
  const [clinicOpening, setClinicOpening] = React.useState(false);
  const [clinicError, setClinicError] = React.useState(null);

  React.useEffect(() => { if (startOnStats) setShowStats(true); }, [startOnStats]);

  const load = React.useCallback(async (force) => {
    if (!runtime) return;
    if (force) setRefreshing(true);
    try {
      setOverview(force ? await runtime.reload() : await runtime.load());
      setError(null);
    } catch (requestError) {
      setError(requestError?.message || 'Compte indisponible');
    } finally {
      setRefreshing(false);
    }
  }, [runtime]);

  React.useEffect(() => { void load(false); }, [load]);

  const saveProfile = async (changes) => {
    setBusy(true);
    try {
      await runtime.updateProfile(overview.profile.version, changes);
      // La feuille se ferme sur un profil rechargé, qui affiche les nouvelles
      // valeurs.
      setProfileOpen(false);
      await load(true);
    } catch (requestError) {
      showToast(requestError?.message || 'Enregistrement impossible', 'error');
      // Le formulaire porte une version désormais périmée : le rouvrir sur des
      // données fraîches évite un second refus identique.
      if (requestError?.status === 409) { setProfileOpen(false); await load(true); }
    } finally {
      setBusy(false);
    }
  };

  // La feuille ne s'ouvre que sur une fiche réellement lue : l'ouvrir d'abord
  // et charger ensuite montrerait des champs vides qui ne disent rien de ce que
  // le serveur détient.
  const openClinic = async () => {
    if (!runtime || clinicOpening || busy) return;
    setClinicOpening(true);
    try {
      setClinic(await runtime.clinic.load());
      setClinicError(null);
    } catch (requestError) {
      showToast(requestError?.message || 'Fiche du cabinet indisponible', 'error');
    } finally {
      setClinicOpening(false);
    }
  };

  const saveClinic = async (fields) => {
    setBusy(true);
    try {
      await runtime.clinic.save(clinic.version, fields);
      // La feuille se referme sur une fiche enregistrée ; la rouvrir la relit.
      setClinic(null);
      setClinicError(null);
    } catch (requestError) {
      const message = requestError?.message || 'Enregistrement impossible';
      if (requestError?.status === 409) {
        // La fiche porte désormais une autre version : la recharger dans la
        // feuille évite un second refus identique, et montre ce qui a changé.
        try {
          setClinic(await runtime.clinic.load());
          setClinicError(`${message} La fiche affichée vient d’être rechargée depuis le serveur.`);
        } catch (reloadError) {
          setClinic(null);
          showToast(reloadError?.message || message, 'error');
        }
      } else {
        setClinicError(message);
      }
    } finally {
      setBusy(false);
    }
  };

  const toggleNotifications = async () => {
    if (busy) return;
    setBusy(true);
    const next = !overview.preferences.routine_in_app_notifications;
    try {
      await runtime.updatePreferences(overview.preferences.version, next);
      // L'interrupteur porte lui-même son état : le redire ailleurs n'ajoute rien.
      await load(true);
    } catch (requestError) {
      showToast(requestError?.message || 'Modification impossible', 'error');
      if (requestError?.status === 409) await load(true);
    } finally {
      setBusy(false);
    }
  };

  const confirmLogoutAll = async () => {
    setBusy(true);
    try {
      const result = await runtime.logoutAll();
      // La déconnexion qui suit est la preuve de la révocation : une
      // notification disparaîtrait avec l'écran qui la portait.
      setLogoutAllOpen(false);
      if (onLoggedOut) onLoggedOut();
    } catch (requestError) {
      showToast(requestError?.message || 'Révocation impossible', 'error');
    } finally {
      setBusy(false);
    }
  };

  const doLogout = async () => {
    if (busy) return;
    setBusy(true);
    try {
      await window.VascoApi.logout();
    } catch (requestError) {
      // Une déconnexion serveur qui échoue ne doit pas retenir l'utilisateur :
      // le bearer local est effacé dans tous les cas.
      window.VascoApi.clearLocalSession();
    } finally {
      setBusy(false);
      if (onLoggedOut) onLoggedOut();
    }
  };

  if (contentScreen) {
    return (
      <ContentScreen
        kind={contentScreen}
        accent={accent}
        runtime={runtime}
        onBack={() => setContentScreen(null)}
        onChanged={() => void load(true)} />
    );
  }

  if (showStats) {
    return (
      <StatsScreen
        accent={accent}
        runtime={runtime}
        capabilities={overview?.capabilities}
        onBack={() => {
          setShowStats(false);
          // Sans ça, le drapeau resterait armé et Compte rouvrirait sur les stats.
          if (onStatsClosed) onStatsClosed();
        }} />
    );
  }

  if (!overview) {
    return (
      <div style={{
        width: '100%', height: '100%', background: AC_BG, color: AC_INK,
        display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 14,
        fontFamily: "'Inter Tight', -apple-system, system-ui, sans-serif", padding: 24, textAlign: 'center',
      }}>
        {!error && <VascoSpinner size={24} color={accent} />}
        <div style={{ fontSize: 14, fontWeight: 650 }}>
          {error ? 'Compte indisponible' : 'Chargement de votre compte…'}
        </div>
        {error && <div style={{ fontSize: 12.5, fontWeight: 500, color: AC_SUB, lineHeight: 1.45 }}>{error}</div>}
        {error && (
          <button onClick={() => void load(true)} className="press" style={{
            height: 42, padding: '0 20px', borderRadius: 14, background: AC_INK, border: 'none',
            color: '#fff', fontFamily: 'inherit', fontSize: 13.5, fontWeight: 700, cursor: 'pointer',
          }}>Réessayer</button>
        )}
      </div>
    );
  }

  const profile = overview.profile;
  const capabilities = overview.capabilities;
  const counts = overview.counts;
  const initials = `${profile.given_name[0] || ''}${profile.family_name[0] || ''}`.toUpperCase();
  const authLabel = overview.authentication.auth_provider === 'demo' ? 'Données synthétiques' : 'Identité fédérée';

  return (
    <div style={{
      width: '100%', height: '100%', background: AC_BG, color: AC_INK,
      display: 'flex', flexDirection: 'column',
      fontFamily: "'Inter Tight', -apple-system, system-ui, sans-serif",
      position: 'relative',
    }}>
      <div className="vasco-scroll" style={{ flex: 1, overflowY: 'auto', paddingBottom: 20 }}>

        {/* En-tête de profil — valeurs réelles du compte */}
        <div style={{
          padding: '62px 28px 22px',
          display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 16,
        }}>
          <div style={{ flex: 1, minWidth: 0 }}>
            {AC_SALUTATIONS[profile.salutation] && (
              <div style={{ fontSize: 42, fontWeight: 900, letterSpacing: '-1.5px', lineHeight: 1.05 }}>
                {AC_SALUTATIONS[profile.salutation]}
              </div>
            )}
            <div style={{
              fontSize: 42, fontWeight: 900, letterSpacing: '-1.5px', lineHeight: 1.05,
              overflowWrap: 'anywhere',
            }}>{profile.family_name}</div>
            <div style={{
              display: 'inline-flex', alignItems: 'center', gap: 6,
              padding: '5px 12px', borderRadius: 999, marginTop: 12, background: AC_MED_TINT,
            }}>
              <svg width="13" height="13" viewBox="0 0 24 24" fill={AC_MED} stroke="none">
                <polygon points="12 2 15.1 8.3 22 9.2 17 14 18.2 21 12 17.8 5.8 21 7 14 2 9.2 8.9 8.3 12 2"/>
              </svg>
              <span style={{ fontSize: 12, fontWeight: 700, color: AC_MED, letterSpacing: '0.2px' }}>{authLabel}</span>
            </div>
            <div style={{ fontSize: 14, fontWeight: 500, color: AC_SUB, marginTop: 10 }}>
              {profile.specialty || 'Spécialité non renseignée'}
            </div>
          </div>
          <div style={{
            width: 88, height: 88, borderRadius: '50%', flexShrink: 0, background: 'rgba(10,10,10,0.06)',
            display: 'flex', alignItems: 'center', justifyContent: 'center', marginTop: 4,
          }}>
            <div style={{
              width: 80, height: 80, borderRadius: '50%', background: 'rgba(10,10,10,0.08)',
              display: 'flex', alignItems: 'center', justifyContent: 'center',
              fontSize: 28, fontWeight: 800, color: 'rgba(10,10,10,0.25)', letterSpacing: '-0.5px',
            }}>{initials}</div>
          </div>
        </div>

        <div style={{ padding: '0 16px 16px' }}>
          <button onClick={() => void load(true)} disabled={refreshing} className="press" style={{
            display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
            width: '100%', height: 40, borderRadius: 14, background: 'none',
            border: `1px solid ${AC_HAIR2}`, fontFamily: 'inherit',
            fontSize: 13, fontWeight: 650, color: AC_MED, cursor: refreshing ? 'default' : 'pointer',
          }}>
            {refreshing && <VascoSpinner size={13} color={AC_MED} thickness={2} />}
            {refreshing ? 'Actualisation…' : 'Actualiser'}
          </button>
        </div>

        {error && (
          <div role="alert" style={{
            margin: '0 16px 16px', background: 'rgba(185,28,28,0.06)', borderRadius: 14,
            padding: '12px 14px', fontSize: 12.5, fontWeight: 600, color: AC_DANGER,
          }}>{error}</div>
        )}

        <SettingGroup title="Informations du compte">
          <SettingRow
            icon={<><rect x="2" y="4" width="20" height="16" rx="2"/><path d="M22 7l-10 7L2 7"/></>}
            label={profile.email}
            sub={`Email professionnel · ${AC_VERIFICATION[profile.email_verification]}`} />
          <SettingRow
            icon={<><path d="M22 16.9v3a2 2 0 0 1-2.2 2 19.8 19.8 0 0 1-8.6-3.1 19.5 19.5 0 0 1-6-6A19.8 19.8 0 0 1 2.1 4.1 2 2 0 0 1 4.1 2h3"/></>}
            label={profile.phone || 'Téléphone non renseigné'}
            sub="Téléphone" />
          <SettingRow
            icon={<><path d="M4 7h16M4 12h16M4 17h10"/></>}
            label={profile.rpps ? `RPPS ${profile.rpps}` : 'RPPS non renseigné'}
            sub={`Numéro d'identification · ${AC_VERIFICATION[profile.rpps_verification]}`} />
        </SettingGroup>

        <SettingGroup title="Mon profil">
          <SettingRow
            icon={<><circle cx="12" cy="8" r="4"/><path d="M4 21v-1a8 8 0 0 1 16 0v1"/></>}
            label="Informations personnelles"
            sub="Civilité, nom, téléphone, spécialité et fuseau"
            reason={acReason(capabilities.edit_profile)}
            onClick={() => setProfileOpen(true)} />
          <SettingRow
            icon={<><rect x="3" y="5" width="18" height="16" rx="2"/><path d="M3 10h18"/><path d="M12 5v16"/></>}
            label="Ma clinique"
            sub="Adresse, logo, coordonnées"
            right={clinicOpening ? <VascoSpinner size={15} color={AC_SUB} thickness={2} /> : undefined}
            onClick={() => void openClinic()} />
          <SettingRow
            icon={<><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M12 8v8"/><path d="M8 12h8"/></>}
            label="Signature électronique"
            reason={acReason(capabilities.electronic_signature)} />
          <SettingRow
            icon={<><path d="M3 3v18h18"/><path d="M7 14l4-4 4 4 5-6"/></>}
            label="Mes statistiques"
            sub="Activité réellement calculée"
            reason={acReason(capabilities.activity_statistics)}
            onClick={() => setShowStats(true)} />
        </SettingGroup>

        <SettingGroup title="Connecteurs">
          {overview.connectors.map((connector) => (
            <SettingRow
              key={connector.id}
              icon={<><path d="M10 13a5 5 0 0 0 7.5.5l3-3a5 5 0 0 0-7-7l-1.7 1.7"/><path d="M14 11a5 5 0 0 0-7.5-.5l-3 3a5 5 0 0 0 7 7l1.7-1.7"/></>}
              label={connector.display_name}
              sub="Connecteur indisponible"
              reason={AC_REASONS[connector.reason_code] || 'Action indisponible.'} />
          ))}
        </SettingGroup>

        <SettingGroup title="Assistant IA">
          <SettingRow
            icon={<><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></>}
            label="Historique des conversations"
            sub={acPlural(counts.active_conversations, 'conversation active', 'conversations actives')}
            reason={acReason(capabilities.conversation_history)}
            onClick={() => onTabChange && onTabChange('IA')} />
          <SettingRow
            icon={<><path d="M14.5 4.5l5 5L9 20l-5 .5.5-5z"/></>}
            label="Prompts personnalisés"
            sub={acPlural(counts.active_prompts, 'prompt actif', 'prompts actifs')}
            reason={acReason(capabilities.manage_prompts)}
            onClick={() => setContentScreen('prompts')} />
          <SettingRow
            icon={<><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M9 9h6v6H9z"/></>}
            label="Modèles de documents"
            sub={acPlural(counts.active_templates, 'modèle actif', 'modèles actifs')}
            reason={acReason(capabilities.manage_templates)}
            onClick={() => setContentScreen('templates')} />
        </SettingGroup>

        <SettingGroup title="Préférences">
          <SettingRow
            icon={<><circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.2 4.2l1.4 1.4M18.4 18.4l1.4 1.4M1 12h2M21 12h2M4.2 19.8l1.4-1.4M18.4 5.6l1.4-1.4"/></>}
            label="Apparence"
            sub={overview.preferences.appearance === 'light' ? 'Clair' : overview.preferences.appearance}
            reason={acReason(capabilities.change_appearance)} />
          <SettingRow
            icon={<><path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z"/></>}
            label="Langue"
            sub={overview.preferences.language === 'fr-FR' ? 'Français' : overview.preferences.language}
            reason={acReason(capabilities.change_language)} />
          <SettingRow
            icon={<><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.7 21a2 2 0 0 1-3.4 0"/></>}
            label="Notifications internes de routine"
            sub={`${overview.preferences.routine_in_app_notifications ? 'Activées' : 'Désactivées'}. Les notifications cliniques et système restent visibles.`}
            reason={acReason(capabilities.routine_notifications)}
            role="switch"
            checked={overview.preferences.routine_in_app_notifications}
            onClick={toggleNotifications}
            right={<Toggle
              on={overview.preferences.routine_in_app_notifications}
              disabled={busy || !capabilities.routine_notifications.enabled} />} />
        </SettingGroup>

        <SettingGroup title="Abonnement">
          <SettingRow
            icon={<><polygon points="12 2 15.1 8.3 22 9.2 17 14 18.2 21 12 17.8 5.8 21 7 14 2 9.2 8.9 8.3 12 2"/></>}
            label="Abonnement"
            reason={acReason(capabilities.subscription)} />
          <SettingRow
            icon={<><rect x="1" y="4" width="22" height="16" rx="2"/><line x1="1" y1="10" x2="23" y2="10"/></>}
            label="Facturation"
            reason={acReason(capabilities.billing)} />
        </SettingGroup>

        <SettingGroup title="Sécurité & données">
          <SettingRow
            icon={<><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></>}
            label="Mot de passe"
            reason={acReason(capabilities.change_password)} />
          <SettingRow
            icon={<><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></>}
            label="Authentification 2FA"
            reason={acReason(capabilities.manage_mfa)} />
          <SettingRow
            icon={<><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></>}
            label="Exporter mes données"
            reason={acReason(capabilities.data_export)} />
          <SettingRow
            icon={<><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></>}
            label="Fermer mon compte"
            danger
            reason={acReason(capabilities.close_account)} />
          <SettingRow
            icon={<><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></>}
            label="Déconnecter tous les appareils"
            sub="Révoque toutes les sessions, y compris celle-ci"
            danger
            reason={acReason(capabilities.logout_all)}
            onClick={() => setLogoutAllOpen(true)} />
        </SettingGroup>

        <div style={{ padding: '0 16px 24px' }}>
          <button onClick={doLogout} disabled={busy} className="press" style={{
            width: '100%', height: AC_VT.hLg || 48, borderRadius: AC_VT.rLg || 16,
            background: 'none', border: `1px solid ${AC_HAIR2}`,
            fontFamily: 'inherit', fontSize: 14, fontWeight: 700, color: AC_DANGER,
            cursor: busy ? 'default' : 'pointer',
            display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
          }}>
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke={AC_DANGER}
              strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round">
              <path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/>
            </svg>
            Se déconnecter
          </button>
        </div>

        <div style={{ textAlign: 'center', padding: '0 0 16px' }}>
          <span className="mono" style={{ fontSize: 10, fontWeight: 500, color: AC_SUB, letterSpacing: '0.3px' }}>
            VASCO v{window.VASCO_VERSION || '0.0.0'} · Build {window.VASCO_BUILD || '—'}
          </span>
        </div>
      </div>

      {typeof BottomNav !== 'undefined' && (
        <BottomNav active="MOI" onChange={(id) => { if (onTabChange) onTabChange(id); }} accent={accent} />
      )}

      {profileOpen && (
        <ProfileEditorSheet
          profile={profile}
          busy={busy}
          onClose={() => setProfileOpen(false)}
          onSave={saveProfile} />
      )}

      {clinic && (
        <ClinicSheet
          key={clinic.version}
          clinic={clinic}
          organizationName={overview.organization.name}
          organizationReason={acReason(capabilities.edit_clinic)}
          busy={busy}
          error={clinicError}
          onClose={() => { setClinic(null); setClinicError(null); }}
          onSave={saveClinic} />
      )}

      {logoutAllOpen && (
        <LogoutAllSheet
          busy={busy}
          onCancel={() => setLogoutAllOpen(false)}
          onConfirm={confirmLogoutAll} />
      )}
    </div>
  );
}

window.AccountScreen = AccountScreen;
