/* global React */
// Shell — sidebar nav + topbar + global layout

// ── Session courante ─────────────────────────────────────────────────────────
// Sera remplacé par un appel /api/auth/me quand l'auth sera intégrée.
// Roles "direction" : ['pdg','direction'] → accès complet Propriétaire opp.
// Autres rôles : propriétaire limité à leur propre nom (lecture seule sur ce champ).
// Pour tester un autre rôle : window.ME.role = 'commercial' dans la console.
window.ME = window.ME || {
  name:      'Nils Bazin',
  firstName: 'Nils',
  lastName:  'Bazin',
  role:      'chef-de-projet', // 'pdg' | 'direction' | 'chef-de-projet' | 'commercial' | …
  initials:  'NB',
  title:     'Chef de projet',
};

const NAV = [
  { type: 'section', label: 'Pilotage' },
  { key: 'dashboard',  label: 'Cockpit',       icon: Icon.home,     badge: null },
  { key: 'crm',        label: 'CRM Clients',   icon: Icon.people,   badge: null },
  { key: 'dossiers',   label: 'Dossiers CEE',  icon: Icon.folder,   badge: 42 },

  { type: 'section', label: 'Terrain & Études' },
  { key: 'visites',    label: 'Visites IA',    icon: Icon.camera,   badge: 'LIVE' },
  { key: 'solar',      label: 'Studio solaire',icon: Icon.sun,      badge: 'NEW' },
  { key: 'etudes-reg', label: 'Études régl.',  icon: Icon.doc,      badge: 'NEW' },
  { key: 'audit',      label: 'Audit énergétique', icon: Icon.bolt, badge: null },
  { key: 'dim',        label: 'Dimensionnement', icon: Icon.layers, badge: null },
  { key: 'fost',       label: 'Fiches FOST',   icon: Icon.book,     badge: null },

  { type: 'section', label: 'CRM & Connexions' },
  { key: 'devis',      label: 'Devis',         icon: Icon.doc,      badge: null },
  { key: 'products',   label: 'Catalogue produits', icon: Icon.layers, badge: null },
  { key: 'odoo',       label: 'Hub Odoo',      icon: Icon.refresh,  badge: null },
  { key: 'comm',       label: 'Communication', icon: Icon.mic,      badge: null },
  { key: 'gov',        label: 'APIs Gouv.',    icon: Icon.shield,   badge: null },

  { type: 'section', label: 'Conformité' },
  { key: 'calendar',   label: 'Calendrier',    icon: Icon.calendar, badge: null },
  { key: 'signatures', label: 'Signatures',    icon: Icon.sign,     badge: 3 },
  { key: 'pncee',      label: 'Dépôts PNCEE',  icon: Icon.shield,   badge: null },
  { key: 'ledger',     label: 'Ledger cumac',  icon: Icon.lock,     badge: null },
  { key: 'cpe',        label: 'Énergie & CPE',   icon: Icon.bolt,     badge: 'NEW' },
  { key: 'mar',        label: 'MaPrimeRénov\'',  icon: Icon.home,     badge: 'NEW' },
  { key: 'carbone',    label: 'Bilan Carbone',   icon: Icon.flame,    badge: 'NEW' },

  { type: 'section', label: 'SaaS & Réseau' },
  { key: 'reseau',     label: 'Réseau RGE',    icon: Icon.people,   badge: null },
  { key: 'reports',    label: 'Reporting',     icon: Icon.chart,    badge: null },
  { key: 'org',        label: 'Mon organisation', icon: Icon.people, badge: 'NEW' },
  { key: 'equipe',     label: 'Équipe',        icon: Icon.people,   badge: null },
  { key: 'integ',      label: 'Intégrations',  icon: Icon.spark,    badge: null },
  { key: 'storage',    label: 'Documents',     icon: Icon.folder,   badge: null },
  { key: 'webhooks',   label: 'Webhooks',      icon: Icon.bolt,     badge: null },
  { key: 'events',     label: 'Événements',    icon: Icon.bolt,     badge: null },
  { key: 'settings',   label: 'Paramètres',    icon: Icon.spark,    badge: null },
];

// ── Hook SSE notifications + persistance unread ──────────────────────────────
// Mapping entity.kind → page de navigation (P1 — règle n°3 Interconnexion)
const NOTIF_ENTITY_NAV = {
  'dossier-cee':       'dossiers',
  'opportunity':       'crm',
  'lead':              'crm',
  'quote':             'crm',
  'contact':           'crm',
  'organization':      'crm',
  'appointment':       'calendar',
  'rdv':               'calendar',
  'signature':         'signatures',
  'tech-visit':        'visites',
  'visit':             'visites',
  'pre-visit':         'visites',
  'etude-reglementaire': 'etudes-reg',
  'etude':             'etudes-reg',
  'solar':             'solar',
  'solar-project':     'solar',
  'email':             'comm',
  'devis':             'devis',
  'product':           'products',
  'webhook':           'webhooks',
  'collaborator':      'equipe',
};

// Bruit système masqué par défaut (api.* du middleware)
const NOTIF_NOISY_PREFIXES = ['api.get.', 'api.post.', 'api.patch.', 'api.put.', 'api.delete.'];
const isNoisy = (evt) => NOTIF_NOISY_PREFIXES.some(p => (evt.type || '').startsWith(p));

function useNotifications() {
  const [notifs, setNotifs] = useState([]);
  const [toasts, setToasts] = useState([]);
  const [lastReadTs, setLastReadTs] = useState(() => {
    try { return localStorage.getItem('ae_notif_last_read') || '1970-01-01T00:00:00.000Z'; }
    catch { return '1970-01-01T00:00:00.000Z'; }
  });
  const BASE = (window.AE_API && window.AE_API.BASE) || '';

  useEffect(() => {
    const es = new EventSource(`${BASE}/api/events/stream`);
    es.onmessage = (e) => {
      try {
        const evt = JSON.parse(e.data);
        setNotifs(prev => [evt, ...prev].slice(0, 100));
        // Toast en bas à droite pour erreurs + warn appointment.reminder.hour_before
        const isUrgent = evt.level === 'error'
          || evt.type === 'appointment.reminder.hour_before';
        if (isUrgent && !isNoisy(evt)) {
          setToasts(prev => [...prev, evt].slice(-3)); // max 3 simultanés
          // Auto-dismiss après 8s (15s pour error)
          const ttl = evt.level === 'error' ? 15000 : 8000;
          setTimeout(() => {
            setToasts(prev => prev.filter(t => t.id !== evt.id));
          }, ttl);
        }
        // Évent global pour autres composants (badge sidebar calendar)
        try { window.dispatchEvent(new CustomEvent('ae:bus-event', { detail: evt })); } catch (_) {}
      } catch (_) {}
    };
    // Chargement initial depuis DB (50 derniers, on filtrera côté UI)
    fetch(`${BASE}/api/events?limit=50`)
      .then(r => r.json())
      .then(d => { if (d.ok && d.events) setNotifs(d.events.slice(0, 50)); })
      .catch(() => {});
    return () => { es.close(); };
  }, [BASE]);

  // Unread = events plus récents que lastReadTs (et pas du bruit système)
  const unread = notifs.filter(n => n.ts && n.ts > lastReadTs && !isNoisy(n)).length;

  const markRead = () => {
    const ts = new Date().toISOString();
    setLastReadTs(ts);
    try { localStorage.setItem('ae_notif_last_read', ts); } catch (_) {}
  };

  const dismissToast = (id) => setToasts(prev => prev.filter(t => t.id !== id));

  return { notifs, toasts, dismissToast, unread, lastReadTs, markRead };
}

// ── Toaster — pile de toasts en bas à droite ─────────────────────────────────
function Toaster({ toasts, onDismiss, onNav }) {
  if (!toasts || toasts.length === 0) return null;
  return (
    <div style={{
      position: 'fixed', bottom: 16, right: 16, zIndex: 5000,
      display: 'flex', flexDirection: 'column', gap: 8, maxWidth: 380,
    }}>
      {toasts.map(t => {
        const isErr = t.level === 'error';
        const navKind = t.entity?.kind;
        const target = navKind && NOTIF_ENTITY_NAV[navKind];
        return (
          <div key={t.id}
            style={{
              padding: '12px 14px', borderRadius: 8,
              background: isErr ? 'var(--paper)' : 'var(--paper)',
              border: '1px solid', borderColor: isErr ? 'rgba(239,68,68,.45)' : 'rgba(245,158,11,.45)',
              borderLeft: '4px solid', borderLeftColor: isErr ? 'var(--rouge)' : 'var(--amber)',
              boxShadow: '0 8px 24px rgba(0,0,0,.18)',
              display: 'grid', gridTemplateColumns: '1fr auto', gap: 8,
              animation: 'toast-slide-in 240ms ease-out',
            }}>
            <div>
              <div style={{ fontSize: 11, fontWeight: 700, color: isErr ? 'var(--rouge)' : 'var(--amber)', textTransform: 'uppercase', letterSpacing: 0.5, marginBottom: 3 }}>
                {isErr ? '⚠ Erreur' : '⏰ Rappel'}
              </div>
              <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--ink)', lineHeight: 1.3 }}>{t.title || t.type}</div>
              {t.message && <div style={{ fontSize: 11, color: 'var(--ink-3)', marginTop: 4, lineHeight: 1.4 }}>{t.message.slice(0, 120)}{t.message.length > 120 ? '…' : ''}</div>}
              {target && (
                <div style={{ marginTop: 8, display: 'flex', gap: 6, flexWrap: 'wrap', alignItems: 'center' }}>
                  <button onClick={() => {
                      try { window.AE_NOTIF_TARGET = { kind: navKind, id: t.entity?.id, ref: t.entity?.ref, type: t.type }; } catch (_) {}
                      onNav && onNav(target); onDismiss(t.id);
                    }}
                    style={{ fontSize: 11, padding: '4px 10px', border: '1px solid var(--line)', borderRadius: 5, background: 'var(--paper-2)', color: 'var(--ink-2)', fontWeight: 500 }}>
                    Voir →
                  </button>
                  {/* Snooze : uniquement pour rappels appointment H-1 */}
                  {(t.type === 'appointment.reminder.hour_before' || t.type === 'appointment.reminder.day_before') && t.entity?.id && (
                    <>
                      <span style={{ fontSize: 10, color: 'var(--ink-5)', marginLeft: 4 }}>Snooze :</span>
                      {[5, 15, 60].map(min => (
                        <button key={min} onClick={async () => {
                            try {
                              const BASE2 = (window.AE_API && window.AE_API.BASE) || '';
                              await fetch(`${BASE2}/api/calendar/appointments/${t.entity.id}/snooze`, {
                                method: 'POST', headers: { 'Content-Type': 'application/json' },
                                body: JSON.stringify({ minutes: min }),
                              });
                              onDismiss(t.id);
                            } catch (_) {}
                          }}
                          style={{ fontSize: 11, padding: '4px 8px', border: '1px solid var(--line)', borderRadius: 5, background: 'var(--paper-3)', color: 'var(--ink-3)', fontWeight: 500 }}>
                          +{min}min
                        </button>
                      ))}
                    </>
                  )}
                </div>
              )}
            </div>
            <button onClick={() => onDismiss(t.id)}
              style={{ alignSelf: 'flex-start', padding: 2, color: 'var(--ink-5)' }}><Icon.cross /></button>
          </div>
        );
      })}
    </div>
  );
}

const NOTIF_LEVEL_COLOR = {
  error: 'var(--rouge)',
  warn:  'var(--amber)',
  info:  'var(--signal)',
  debug: 'var(--ink-4)',
};

function NotifPanel({ notifs, onClose, onNav, onMarkAllRead, lastReadTs }) {
  const [filter, setFilter] = useState(() => {
    try { return localStorage.getItem('ae_notif_filter') || 'important'; }
    catch { return 'important'; }
  });
  const setF = (f) => { setFilter(f); try { localStorage.setItem('ae_notif_filter', f); } catch (_) {} };

  const fmtTime = (ts) => {
    if (!ts) return '';
    const d = new Date(ts);
    const now = new Date();
    const diff = Math.floor((now - d) / 1000);
    if (diff < 60) return 'à l\'instant';
    if (diff < 3600) return `il y a ${Math.floor(diff / 60)} min`;
    if (diff < 86400) return `il y a ${Math.floor(diff / 3600)} h`;
    return d.toLocaleDateString('fr-FR', { day: '2-digit', month: 'short' });
  };

  // Filtrage
  const filtered = notifs.filter(n => {
    if (filter === 'all')       return true;
    if (filter === 'errors')    return n.level === 'error';
    if (filter === 'important') return !isNoisy(n) && n.level !== 'debug';
    return true;
  });

  // Clic sur notif → nav vers entité (P1 règle n°3)
  const handleClick = (n) => {
    const kind = n.entity?.kind;
    const target = NOTIF_ENTITY_NAV[kind];
    if (target && onNav) {
      // Stocke l'id dans une variable globale pour que la page cible puisse pré-sélectionner
      try { window.AE_NOTIF_TARGET = { kind, id: n.entity?.id, ref: n.entity?.ref, type: n.type }; } catch (_) {}
      onNav(target);
      onClose();
    }
  };

  const FILTERS = [
    ['important', 'Important'],
    ['errors',    'Erreurs'],
    ['all',       'Tous'],
  ];

  return (
    <div style={{
      position: 'absolute', top: 48, right: 0, width: 380, zIndex: 1000,
      background: 'var(--paper)', border: '1px solid var(--line)',
      borderRadius: 10, boxShadow: '0 8px 32px rgba(0,0,0,.18)',
      overflow: 'hidden',
    }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '10px 14px', borderBottom: '1px solid var(--hairline)' }}>
        <Icon.bell style={{ color: 'var(--ink-3)' }} />
        <span style={{ fontWeight: 600, fontSize: 13 }}>Notifications</span>
        <span style={{ flex: 1 }} />
        <button onClick={onMarkAllRead} title="Tout marquer comme lu"
          style={{ fontSize: 10, color: 'var(--ink-4)', padding: '3px 8px', border: '1px solid var(--line)', borderRadius: 5, background: 'var(--paper-2)' }}>
          ✓ Tout lu
        </button>
        <button onClick={onClose} style={{ color: 'var(--ink-4)', padding: 2 }}><Icon.cross /></button>
      </div>

      {/* Filtres */}
      <div style={{ display: 'flex', gap: 4, padding: '8px 12px', borderBottom: '1px solid var(--hairline)', background: 'var(--paper-2)' }}>
        {FILTERS.map(([k, l]) => (
          <button key={k} onClick={() => setF(k)} style={{
            padding: '4px 10px', fontSize: 11, fontWeight: filter === k ? 600 : 500,
            color: filter === k ? 'var(--paper)' : 'var(--ink-3)',
            background: filter === k ? 'var(--ink)' : 'transparent',
            border: '1px solid', borderColor: filter === k ? 'var(--ink)' : 'var(--line)',
            borderRadius: 5, cursor: 'pointer',
          }}>{l}</button>
        ))}
      </div>

      {filtered.length === 0 ? (
        <div style={{ padding: '24px 16px', textAlign: 'center', color: 'var(--ink-4)', fontSize: 12 }}>Aucune notification</div>
      ) : (
        <div style={{ maxHeight: 420, overflowY: 'auto' }}>
          {filtered.map((n, i) => {
            const isUnread = n.ts && n.ts > lastReadTs && !isNoisy(n);
            const navKind = n.entity?.kind;
            const canNav = navKind && NOTIF_ENTITY_NAV[navKind];
            return (
              <div key={n.id || i}
                onClick={() => canNav && handleClick(n)}
                style={{
                  padding: '9px 14px', borderBottom: '1px solid var(--hairline)',
                  display: 'grid', gridTemplateColumns: '6px 1fr auto', gap: 10, alignItems: 'start',
                  background: isUnread ? 'rgba(34,197,94,.04)' : 'transparent',
                  cursor: canNav ? 'pointer' : 'default',
                  transition: 'background 120ms',
                }}
                onMouseEnter={e => { if (canNav) e.currentTarget.style.background = 'var(--paper-2)'; }}
                onMouseLeave={e => { e.currentTarget.style.background = isUnread ? 'rgba(34,197,94,.04)' : 'transparent'; }}>
                <div style={{ width: 6, height: 6, borderRadius: '50%', background: NOTIF_LEVEL_COLOR[n.level] || 'var(--ink-4)', marginTop: 5 }} />
                <div>
                  <div style={{ fontSize: 12, fontWeight: isUnread ? 600 : 500, color: 'var(--ink)', lineHeight: 1.3 }}>
                    {n.title || n.type}
                    {canNav && <span style={{ color: 'var(--ink-5)', marginLeft: 6, fontSize: 10 }}>→</span>}
                  </div>
                  {n.message && <div style={{ fontSize: 11, color: 'var(--ink-3)', marginTop: 2, lineHeight: 1.4 }}>{n.message.slice(0, 80)}{n.message.length > 80 ? '…' : ''}</div>}
                  <div className="mono" style={{ fontSize: 10, color: 'var(--ink-5)', marginTop: 3 }}>
                    {n.source} · {fmtTime(n.ts)}{navKind ? ` · ${navKind}` : ''}
                  </div>
                </div>
                <span className="mono" style={{
                  fontSize: 9, padding: '2px 5px', borderRadius: 4, fontWeight: 600,
                  background: n.level === 'error' ? 'rgba(239,68,68,.12)' : n.level === 'warn' ? 'rgba(245,158,11,.12)' : 'var(--paper-3)',
                  color: NOTIF_LEVEL_COLOR[n.level] || 'var(--ink-4)',
                }}>{n.level || 'info'}</span>
              </div>
            );
          })}
        </div>
      )}
      <div style={{ padding: '8px 14px', borderTop: '1px solid var(--hairline)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
        <span className="mono" style={{ fontSize: 10, color: 'var(--ink-5)' }}>{filtered.length} affichée{filtered.length > 1 ? 's' : ''} · {notifs.length} total</span>
        <button onClick={() => { onNav && onNav('events'); onClose(); }}
          style={{ fontSize: 11, color: 'var(--ink-4)' }}>Voir tous les événements →</button>
      </div>
    </div>
  );
}

function Shell({ current, onNav, children, onToggleTheme, theme, onOpenCopilot, copilotOpen, onOpenPalette }) {
  const [collapsed, setCollapsed] = useState(() => localStorage.getItem('dcee_sb_collapsed') === '1');
  useEffect(() => { localStorage.setItem('dcee_sb_collapsed', collapsed ? '1' : '0'); }, [collapsed]);
  const { notifs, toasts, dismissToast, unread, lastReadTs, markRead } = useNotifications();
  const [notifOpen, setNotifOpen] = useState(false);
  const handleBell = () => { setNotifOpen(v => !v); if (!notifOpen) markRead(); };

  // ── Mobile drawer state (responsive) ──────────────────────────────────
  const [isMobile, setIsMobile] = useState(() => typeof window !== 'undefined' && window.innerWidth <= 768);
  const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
  useEffect(() => {
    const onResize = () => setIsMobile(window.innerWidth <= 768);
    window.addEventListener('resize', onResize);
    return () => window.removeEventListener('resize', onResize);
  }, []);
  // Wrap onNav to auto-close drawer on mobile
  const handleNavMobile = (k) => { onNav(k); if (isMobile) setMobileDrawerOpen(false); };

  // Badge dynamique "Calendrier" — nombre RDV à venir aujourd'hui
  const [calTodayCount, setCalTodayCount] = useState(null);
  useEffect(() => {
    const BASE2 = (window.AE_API && window.AE_API.BASE) || '';
    const tick = async () => {
      try {
        const r = await fetch(`${BASE2}/api/calendar/appointments`).then(x => x.json());
        if (!r.ok) return;
        const now = new Date();
        const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
        const todayEnd = new Date(todayStart.getTime() + 86400_000);
        const count = (r.appointments || []).filter(a => {
          const t = new Date(a.scheduledAt).getTime();
          return a.status === 'scheduled' && t >= now.getTime() && t < todayEnd.getTime();
        }).length;
        setCalTodayCount(count);
        try { window.CAL_TODAY_COUNT = count; } catch (_) {}
      } catch (_) {}
    };
    tick();
    const id = setInterval(tick, 60_000); // refresh chaque minute
    // refresh aussi quand un event RDV passe dans les notifs
    const onEvt = (e) => {
      try {
        const evt = e.detail || JSON.parse(e.data || '{}');
        if (evt.source === 'calendar' || (evt.type || '').startsWith('appointment.')) tick();
      } catch (_) {}
    };
    window.addEventListener('ae:bus-event', onEvt);
    return () => { clearInterval(id); window.removeEventListener('ae:bus-event', onEvt); };
  }, []);
  const sbWidth = isMobile ? 0 : (collapsed ? 60 : 228);
  // En mobile : sidebar sort du flux (fixed drawer), main column = 100%
  return (
    <div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : `${sbWidth}px 1fr`, minHeight: '100vh', background: 'var(--paper)' }}>
      {/* Backdrop drawer mobile */}
      {isMobile && mobileDrawerOpen && (
        <div onClick={() => setMobileDrawerOpen(false)} style={{
          position: 'fixed', inset: 0, background: 'rgba(14,16,16,0.45)',
          zIndex: 99, animation: 'fadeIn 200ms ease',
        }} />
      )}
      {/* Sidebar */}
      <aside style={{
        borderRight: '1px solid var(--line)',
        background: 'var(--paper)',
        display: isMobile && !mobileDrawerOpen ? 'none' : 'flex',
        flexDirection: 'column',
        padding: collapsed && !isMobile ? '18px 8px 16px' : '18px 14px 16px',
        position: isMobile ? 'fixed' : 'sticky',
        top: 0, left: 0,
        height: '100vh',
        width: isMobile ? 280 : undefined,
        zIndex: isMobile ? 100 : 'auto',
        boxShadow: isMobile && mobileDrawerOpen ? '4px 0 24px rgba(14,16,16,0.18)' : 'none',
        overflow: 'hidden',
        animation: isMobile && mobileDrawerOpen ? 'slideInLeft 220ms cubic-bezier(.2,.8,.2,1) both' : 'none',
      }}>
        {/* Brand — logo only (logo PNG already contains the brand name) */}
        <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 6, padding: '4px 6px 18px', borderBottom: '1px solid var(--hairline)', marginBottom: 14 }}>
          <img
            src="../logo-audits-energies.png"
            alt="Audits Énergies"
            style={{ width: collapsed ? 36 : '100%', maxWidth: collapsed ? 36 : 180, height: 'auto', objectFit: 'contain', display: 'block' }}
          />
          {!collapsed && (
            <div className="mono" style={{ fontSize: 10, color: 'var(--ink-4)', letterSpacing: '0.02em' }}>v4.0 · cockpit CEE</div>
          )}
        </div>

        {/* Org switcher */}
        {!collapsed && (
        <button
          style={{
            display: 'flex', alignItems: 'center', gap: 10,
            padding: '8px 10px', marginBottom: 16,
            border: '1px solid var(--line)', borderRadius: 6,
            background: 'var(--paper-2)', width: '100%', textAlign: 'left',
          }}>
          <Sigil seed="audits-energies" size={22} />
          <div style={{ flex: 1, minWidth: 0, lineHeight: 1.2 }}>
            <div style={{ fontSize: 12, fontWeight: 600, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>Audits-Énergies SAS</div>
            <div className="mono" style={{ fontSize: 10, color: 'var(--ink-4)' }}>délégataire · FR-2026</div>
          </div>
          <svg width="10" height="10" viewBox="0 0 10 10" stroke="currentColor" strokeWidth="1.5" fill="none"><path d="M2 4l3-3 3 3M2 6l3 3 3-3"/></svg>
        </button>
        )}

        {/* Nav — sur mobile, masquer les modules admin/debug peu pertinents */}
        <nav style={{ display: 'flex', flexDirection: 'column', gap: 1, flex: 1, overflowY: 'auto', scrollbarWidth: 'thin', marginRight: -8, paddingRight: 8 }}>
          {NAV.filter(item => {
            if (!isMobile) return true;
            // Modules masqués sur mobile (toujours accessibles via URL directe / palette)
            const HIDDEN_ON_MOBILE = ['webhooks', 'integ', 'gov', 'odoo'];
            return !HIDDEN_ON_MOBILE.includes(item.key);
          }).map((rawItem, idx) => {
            if (rawItem.type === 'section') {
              if (collapsed) return <div key={`sec-${idx}`} style={{ height: 12 }} />;
              return (
                <div key={`sec-${idx}`} className="mono" style={{
                  fontSize: 9, color: 'var(--ink-5)', textTransform: 'uppercase',
                  letterSpacing: '0.1em', padding: '12px 8px 4px',
                  fontWeight: 600,
                }}>{rawItem.label}</div>
              );
            }
            let liveBadge = rawItem.badge;
            if (rawItem.key === 'dossiers' && window.DOSSIERS) liveBadge = window.DOSSIERS.length;
            if (rawItem.key === 'calendar' && calTodayCount && calTodayCount > 0) liveBadge = calTodayCount;
            const item = { ...rawItem, badge: liveBadge };
            const active = current === item.key;
            return (
              <button
                key={item.key}
                onClick={() => handleNavMobile(item.key)}
                data-onb={`nav-${item.key}`}
                title={collapsed ? item.label : undefined}
                style={{
                  display: 'flex', alignItems: 'center', gap: 10,
                  padding: collapsed ? '8px 0' : '7px 10px', borderRadius: 5,
                  fontSize: 13, fontWeight: active ? 600 : 500,
                  color: active ? 'var(--ink)' : 'var(--ink-3)',
                  background: active ? 'var(--paper-2)' : 'transparent',
                  borderLeft: active ? '2px solid var(--signal)' : '2px solid transparent',
                  paddingLeft: collapsed ? 0 : 8,
                  justifyContent: collapsed ? 'center' : 'flex-start',
                  textAlign: 'left', transition: 'all 120ms',
                }}
                onMouseEnter={e => !active && (e.currentTarget.style.background = 'var(--paper-2)')}
                onMouseLeave={e => !active && (e.currentTarget.style.background = 'transparent')}
              >
                <item.icon style={{ color: active ? 'var(--ink)' : 'var(--ink-4)' }} />
                {!collapsed && <span style={{ flex: 1 }}>{item.label}</span>}
                {!collapsed && item.badge !== null && (
                  item.badge === 'LIVE' ? (
                    <span className="mono" style={{
                      fontSize: 9, padding: '1px 5px 1px 4px',
                      background: 'transparent',
                      color: 'var(--rouge)',
                      border: '1px solid var(--rouge)',
                      borderRadius: 999, fontWeight: 700, letterSpacing: '0.08em',
                      display: 'inline-flex', alignItems: 'center', gap: 4,
                    }}>
                      <span style={{ width: 5, height: 5, borderRadius: '50%', background: 'var(--rouge)', animation: 'dot-pulse 1.4s infinite' }}/>
                      LIVE
                    </span>
                  ) : (
                    <span className="mono" style={{
                      fontSize: 9, padding: item.badge === 'NEW' ? '1px 5px' : '1px 6px',
                      background: item.badge === 'NEW' ? 'transparent' : (active ? 'var(--signal)' : 'var(--paper-3)'),
                      color: item.badge === 'NEW' ? 'var(--copper)' : (active ? '#0B1F12' : 'var(--ink-3)'),
                      border: item.badge === 'NEW' ? '1px solid var(--copper)' : 'none',
                      borderRadius: 999, fontWeight: 600, letterSpacing: item.badge === 'NEW' ? '0.05em' : 0,
                    }}>
                      {item.badge}
                    </span>
                  )
                )}
              </button>
            );
          })}
        </nav>

        {/* Bottom — user + theme */}
        <div style={{ borderTop: '1px solid var(--hairline)', paddingTop: 12, display: 'flex', flexDirection: 'column', gap: 2 }}>
          <button onClick={onOpenCopilot} title={collapsed ? 'Co-pilote CEE (⌘K)' : undefined} style={{
            display: 'flex', alignItems: 'center', gap: 10, padding: '7px 10px', borderRadius: 5,
            fontSize: 12, color: 'var(--ink-2)', background: copilotOpen ? 'var(--paper-2)' : 'transparent', textAlign: 'left',
            justifyContent: collapsed ? 'center' : 'flex-start',
          }}>
            <div style={{ position: 'relative', width: 8, height: 8 }}>
              <span style={{ position: 'absolute', inset: 0, borderRadius: '50%', background: 'var(--signal)', animation: 'breathe 2.4s ease-in-out infinite' }} />
            </div>
            {!collapsed && <span style={{ flex: 1 }}>Co-pilote CEE</span>}
            {!collapsed && <span className="mono" style={{ fontSize: 10, color: 'var(--ink-4)' }}>⌘K</span>}
          </button>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '8px 6px', justifyContent: collapsed ? 'center' : 'flex-start' }}>
            <div title={collapsed ? 'Nils Bazin · Chef de projet' : undefined} style={{
              width: 26, height: 26, borderRadius: '50%',
              background: 'var(--ink)', color: 'var(--paper)',
              display: 'grid', placeItems: 'center', fontSize: 11, fontWeight: 600,
              flexShrink: 0,
            }}>NB</div>
            {!collapsed && (
            <div style={{ lineHeight: 1.1, flex: 1, minWidth: 0 }}>
              <div style={{ fontSize: 12, fontWeight: 500 }}>Nils Bazin</div>
              <div style={{ fontSize: 10, color: 'var(--ink-4)' }}>Chef de projet</div>
            </div>
            )}
            {!collapsed && (
            <button onClick={onToggleTheme} title="Thème" style={{ padding: 4, color: 'var(--ink-4)' }}>
              {theme === 'dark' ? (
                <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5"><circle cx="8" cy="8" r="3"/><path d="M8 1v1m0 12v1M1 8h1m12 0h1M3 3l1 1m8 8 1 1M3 13l1-1m8-8 1-1"/></svg>
              ) : (
                <svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M6 1a7 7 0 1 0 9 9 6 6 0 0 1-9-9z"/></svg>
              )}
            </button>
            )}
          </div>
        </div>
      </aside>

      {/* Main column */}
      <div style={{ display: 'flex', flexDirection: 'column', minWidth: 0 }}>
        {/* Topbar */}
        <header style={{
          display: 'flex', alignItems: 'center', gap: 12,
          padding: '10px 24px', borderBottom: '1px solid var(--line)',
          position: 'sticky', top: 0, background: 'var(--paper)', zIndex: 20,
          height: 52, backdropFilter: 'blur(8px)',
        }}>
          {/* sidebar toggle (mobile : ouvre drawer ; desktop : collapse) */}
          <button
            onClick={() => isMobile ? setMobileDrawerOpen(v => !v) : setCollapsed(v => !v)}
            title={isMobile ? 'Menu' : (collapsed ? 'Déployer le menu' : 'Rétracter le menu')}
            aria-label={isMobile ? 'Menu' : (collapsed ? 'Déployer le menu' : 'Rétracter le menu')}
            style={{
              display: 'grid', placeItems: 'center',
              width: 28, height: 28, borderRadius: 5,
              border: '1px solid var(--line)', background: 'var(--paper-2)',
              color: 'var(--ink-3)', cursor: 'pointer', flexShrink: 0,
              transition: 'all 120ms',
            }}
            onMouseEnter={e => { e.currentTarget.style.background = 'var(--paper-3)'; e.currentTarget.style.color = 'var(--ink)'; }}
            onMouseLeave={e => { e.currentTarget.style.background = 'var(--paper-2)'; e.currentTarget.style.color = 'var(--ink-3)'; }}
          >
            <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5">
              {collapsed ? (
                <path d="M5 3l4 5-4 5M10 3l1 0M10 13l1 0" />
              ) : (
                <path d="M11 3L7 8l4 5M6 3l-1 0M6 13l-1 0" />
              )}
            </svg>
          </button>

          {/* breadcrumb + time — desktop seulement */}
          {!isMobile && (
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 12, color: 'var(--ink-3)' }}>
            <span className="mono" style={{ color: 'var(--ink-4)' }}>~/</span>
            <span style={{ textTransform: 'capitalize' }}>{NAV.find(n => n.type !== 'section' && n.key === current)?.label || ''}</span>
            <span style={{ color: 'var(--ink-5)' }}>·</span>
            <span className="mono" style={{ fontSize: 11, color: 'var(--ink-4)' }}>
              <Dot tone="signal" size={6} /> <LiveClock />
            </span>
          </div>
          )}

          <div style={{ flex: 1 }} />

          {/* Quick search / palette trigger — compact en mobile (icône only) */}
          <button onClick={onOpenPalette} style={{
            display: 'flex', alignItems: 'center', gap: 8,
            padding: isMobile ? '6px 8px' : '5px 10px 5px 8px',
            border: '1px solid var(--line-2)', borderRadius: 6,
            background: 'var(--paper-2)', color: 'var(--ink-4)',
            fontSize: 12, minWidth: isMobile ? 36 : 260,
          }}>
            <Icon.search style={{ color: 'var(--ink-4)' }} />
            {!isMobile && <span>Rechercher dossier, FOST, client…</span>}
            {!isMobile && <span style={{ flex: 1 }} />}
            {!isMobile && <span className="mono" style={{ fontSize: 10, padding: '1px 5px', border: '1px solid var(--line-2)', borderRadius: 3 }}>⌘K</span>}
          </button>

          {/* Market ticker — desktop seulement */}
          {!isMobile && (
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '5px 12px', background: 'var(--paper-2)', border: '1px solid var(--line)', borderRadius: 6 }}>
            <span className="mono" style={{ fontSize: 10, color: 'var(--ink-4)' }}>CEE SPOT</span>
            <span className="mono" style={{ fontSize: 12, fontWeight: 600 }}>7,82 €<span style={{ color: 'var(--ink-4)', fontWeight: 400 }}>/MWh</span></span>
            <span style={{ fontSize: 11, color: 'var(--signal-deep)', fontWeight: 600 }}>▲ 0,14</span>
            <Sparkline data={[7.6, 7.5, 7.6, 7.4, 7.5, 7.7, 7.68, 7.82]} width={40} height={14} stroke="var(--signal-deep)" strokeWidth={1.2} />
          </div>
          )}

          {/* Notification bell */}
          <div style={{ position: 'relative' }}>
            <button
              onClick={handleBell}
              title="Notifications"
              style={{
                position: 'relative', display: 'grid', placeItems: 'center',
                width: 34, height: 34, borderRadius: 7,
                border: '1px solid var(--line)', background: notifOpen ? 'var(--paper-3)' : 'var(--paper-2)',
                color: 'var(--ink-3)', cursor: 'pointer', flexShrink: 0,
              }}
            >
              <Icon.bell />
              {unread > 0 && (
                <span style={{
                  position: 'absolute', top: -6, right: -6,
                  background: 'var(--rouge)', color: '#fff',
                  fontSize: 9, fontWeight: 800, padding: '1px 5px',
                  borderRadius: 999, border: '2px solid var(--paper)',
                  minWidth: 16, textAlign: 'center', lineHeight: '14px',
                }}>{unread > 99 ? '99+' : unread}</span>
              )}
            </button>
            {notifOpen && <NotifPanel notifs={notifs} lastReadTs={lastReadTs}
              onClose={() => setNotifOpen(false)} onNav={onNav} onMarkAllRead={markRead} />}
          </div>

          {!isMobile && <Btn variant="outline" size="sm" icon={<Icon.plus />}>Nouveau dossier</Btn>}

          {/* A1 — Avatar utilisateur + menu déconnexion */}
          <UserMenu />
        </header>

        {/* Page content */}
        <main style={{ flex: 1, minWidth: 0 }}>
          {children}
        </main>
      </div>

      {/* Toaster global — erreurs + rappels H-1 */}
      <Toaster toasts={toasts} onDismiss={dismissToast} onNav={onNav} />

      {/* Animation slide-in */}
      <style>{`@keyframes toast-slide-in { from { transform: translateX(120%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }`}</style>
    </div>
  );
}

// A1 — UserMenu : avatar + déconnexion + lien vers Paramètres
function UserMenu() {
  const [open, setOpen] = useState(false);
  const [me, setMe] = useState(window.ME || {});
  const [installAvailable, setInstallAvailable] = useState(false);
  useEffect(() => {
    const onUser = (e) => setMe({ ...e.detail, name: e.detail.name, initials: e.detail.initials });
    window.addEventListener('ae:user-changed', onUser);
    // B4 — détection install A2HS
    const onInstall = () => setInstallAvailable(true);
    const onPrompted = () => setInstallAvailable(false);
    window.addEventListener('ae:install-available', onInstall);
    window.addEventListener('ae:install-prompted', onPrompted);
    return () => {
      window.removeEventListener('ae:user-changed', onUser);
      window.removeEventListener('ae:install-available', onInstall);
      window.removeEventListener('ae:install-prompted', onPrompted);
    };
  }, []);
  const initials = me.initials || ((me.firstName?.[0] || '') + (me.lastName?.[0] || '')).toUpperCase() || '?';
  return (
    <div style={{ position: 'relative' }}>
      <button onClick={() => setOpen(v => !v)}
        style={{
          width: 32, height: 32, borderRadius: '50%',
          background: 'var(--ink)', color: 'var(--paper)',
          fontSize: 12, fontWeight: 600, cursor: 'pointer',
          border: '1px solid var(--line-2)',
          display: 'grid', placeItems: 'center',
        }}
        title={me.name || 'Mon compte'}
      >
        {initials}
      </button>
      {open && (
        <>
          <div onClick={() => setOpen(false)} style={{ position: 'fixed', inset: 0, zIndex: 30 }} />
          <div style={{
            position: 'absolute', top: 38, right: 0, zIndex: 31,
            width: 240, background: 'var(--paper)',
            border: '1px solid var(--line-2)', borderRadius: 8,
            boxShadow: 'var(--shadow-3)', padding: 8,
          }}>
            <div style={{ padding: '10px 12px', borderBottom: '1px solid var(--hairline)', marginBottom: 6 }}>
              <div style={{ fontSize: 13, fontWeight: 600 }}>{me.name || me.email || '—'}</div>
              <div style={{ fontSize: 11, color: 'var(--ink-4)', marginTop: 2 }}>{me.role || '—'} · {me.email || ''}</div>
            </div>
            <button onClick={() => { setOpen(false); window.AE_NAV?.('settings'); }}
              style={{ display: 'flex', alignItems: 'center', gap: 8, width: '100%', textAlign: 'left', padding: '8px 12px', fontSize: 12, background: 'transparent', border: 'none', borderRadius: 4, cursor: 'pointer' }}
              onMouseEnter={e => e.currentTarget.style.background = 'var(--paper-2)'}
              onMouseLeave={e => e.currentTarget.style.background = 'transparent'}>
              ⚙️ Paramètres
            </button>
            <button onClick={() => { setOpen(false); window.AE_RESTART_ONBOARDING && window.AE_RESTART_ONBOARDING(); }}
              style={{ display: 'flex', alignItems: 'center', gap: 8, width: '100%', textAlign: 'left', padding: '8px 12px', fontSize: 12, background: 'transparent', border: 'none', borderRadius: 4, cursor: 'pointer' }}
              onMouseEnter={e => e.currentTarget.style.background = 'var(--paper-2)'}
              onMouseLeave={e => e.currentTarget.style.background = 'transparent'}>
              🎓 Refaire le tour
            </button>
            {installAvailable && (
              <button onClick={() => { setOpen(false); window.AE_INSTALL_PROMPT && window.AE_INSTALL_PROMPT(); }}
                style={{ display: 'flex', alignItems: 'center', gap: 8, width: '100%', textAlign: 'left', padding: '8px 12px', fontSize: 12, background: 'var(--signal-tint)', border: 'none', borderRadius: 4, cursor: 'pointer', color: 'var(--signal-deep)', fontWeight: 600 }}>
                📲 Installer l'app
              </button>
            )}
            <button onClick={() => { setOpen(false); window.AE_LOGOUT && window.AE_LOGOUT(); }}
              style={{ display: 'flex', alignItems: 'center', gap: 8, width: '100%', textAlign: 'left', padding: '8px 12px', fontSize: 12, background: 'transparent', border: 'none', borderRadius: 4, cursor: 'pointer', color: 'var(--rouge)' }}
              onMouseEnter={e => e.currentTarget.style.background = 'var(--rouge-tint)'}
              onMouseLeave={e => e.currentTarget.style.background = 'transparent'}>
              🚪 Déconnexion
            </button>
          </div>
        </>
      )}
    </div>
  );
}

function LiveClock() {
  const [t, setT] = useState(new Date());
  useEffect(() => { const id = setInterval(() => setT(new Date()), 1000); return () => clearInterval(id); }, []);
  const hh = String(t.getHours()).padStart(2, '0');
  const mm = String(t.getMinutes()).padStart(2, '0');
  const ss = String(t.getSeconds()).padStart(2, '0');
  return <>live · {hh}:{mm}:{ss} Paris</>;
}

Object.assign(window, { Shell });
