const { useState, useEffect, useRef, useCallback, useMemo, createContext, useContext, Fragment } = React;

const CONFIG = { API_URL: 'https://api.helvia.app' };

// ==================== ADMIN IMPERSONATION BOOTSTRAP ====================
// Admin can land here with ?impersonate_token=...&impersonate_user=BASE64.
// We persist them into localStorage and clean the URL before React boots.
(() => {
    try {
        const params = new URLSearchParams(window.location.search);
        const tok = params.get('impersonate_token');
        if (!tok) return;
        localStorage.setItem('vocal_my_token', tok);
        const userB64 = params.get('impersonate_user');
        if (userB64) {
            try {
                const u = JSON.parse(decodeURIComponent(escape(atob(userB64))));
                localStorage.setItem('vocal_my_user', JSON.stringify(u));
            } catch (e) {}
        }
        sessionStorage.setItem('vocal_my_impersonating', '1');
        const clean = window.location.pathname + window.location.hash;
        window.history.replaceState(null, '', clean);
    } catch (e) {}
})();

const LINE_TYPES = {
    forward: { label: 'Redirection', color: 'badge-info' },
    ivr: { label: 'Menu IVR', color: 'badge-warning' },
    voice_ai: { label: 'IA Vocale', color: 'badge-primary' },
    voicemail: { label: 'Messagerie', color: 'badge-gray' },
    browser: { label: 'Navigateur', color: 'badge-success' },
    link: { label: 'Envoi de lien', color: 'badge-purple' },
    webhook: { label: 'Webhook', color: 'badge-orange' },
    external_api: { label: 'Webhook', color: 'badge-orange' },
};

const CALL_STATUS = {
    completed: { label: 'Terminé', color: 'badge-success' },
    ringing: { label: 'Sonne', color: 'badge-info' },
    'in-progress': { label: 'En cours', color: 'badge-info' },
    busy: { label: 'Occupé', color: 'badge-warning' },
    'no-answer': { label: 'Sans réponse', color: 'badge-warning' },
    failed: { label: 'Échoué', color: 'badge-danger' },
    canceled: { label: 'Annulé', color: 'badge-gray' },
    missed: { label: 'Manqué', color: 'badge-danger' },
};

// ==================== FORMATTERS ====================
function formatDate(d) { if (!d) return '-'; return new Date(d).toLocaleDateString('fr-CH', { day: '2-digit', month: '2-digit', year: 'numeric' }); }
// Les timestamps SQLite (datetime('now')) sont en UTC mais sans suffixe Z.
// On force l'interprétation UTC pour que getHours() retourne l'heure locale du navigateur.
function parseUtcDate(d) {
    if (!d) return null;
    if (d instanceof Date) return d;
    if (typeof d === 'number') return new Date(d);
    const s = String(d).trim();
    // Déjà ISO avec Z ou offset (+02:00, -05:00, etc.) → laisser tel quel
    if (/Z$|[+-]\d{2}:?\d{2}$/.test(s)) return new Date(s);
    // Format SQLite "YYYY-MM-DD HH:MM:SS[.fff]" → ajouter T...Z
    if (/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}(:\d{2}(\.\d+)?)?$/.test(s)) {
        return new Date(s.replace(' ', 'T') + 'Z');
    }
    return new Date(s);
}
function formatDateTime(d) { const x = parseUtcDate(d); if (!x) return '-'; return `${String(x.getDate()).padStart(2,'0')}/${String(x.getMonth()+1).padStart(2,'0')}/${x.getFullYear()} ${String(x.getHours()).padStart(2,'0')}:${String(x.getMinutes()).padStart(2,'0')}`; }
function formatTimeOnly(d) { const x = parseUtcDate(d); if (!x) return ''; return x.toLocaleTimeString('fr-CH', { hour: '2-digit', minute: '2-digit' }); }
function formatDateShort(d) { const x = parseUtcDate(d); if (!x) return ''; return `${String(x.getDate()).padStart(2,'0')}/${String(x.getMonth()+1).padStart(2,'0')}/${String(x.getFullYear()).slice(-2)}`; }
function formatCurrency(amount, currency = 'CHF') { if (amount === null || amount === undefined) return '-'; return `${Number(amount).toFixed(2)} ${currency}`; }
function formatDuration(s) { if (!s) return '-'; const m = Math.floor(s/60); const sec = s%60; if (m === 0) return `${sec}s`; return `${m}m${sec>0?String(sec).padStart(2,'0')+'s':''}`; }
function formatRelative(d) {
    if (!d) return '-';
    const diff = (Date.now() - new Date(d).getTime()) / 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`;
    if (diff < 604800) return `il y a ${Math.floor(diff / 86400)} j`;
    return formatDate(d);
}
function formatAmount(n, currency = 'CHF', sign = false) {
    if (n === null || n === undefined) return '-';
    const v = Number(n);
    const prefix = sign && v > 0 ? '+' : '';
    return `${prefix}${v.toFixed(2)} ${currency}`;
}

// ==================== I18N ====================
const I18N = {
  fr: {
    'nav.dashboard': 'Tableau de bord',
    'nav.lines': 'Lignes',
    'nav.calls': 'Appels',
    'nav.sms': 'SMS',
    'nav.bots': 'Bots vocaux',
    'nav.contacts': 'Contacts',
    'nav.proxy': 'Numéros temporaires',
    'nav.spam': 'Spam & blacklist',
    'nav.porting': 'Portabilité',
    'nav.api': 'Clés API',
    'nav.billing': 'Facturation',
    'nav.profile': 'Profil',
    'nav.routing': 'Routing avancé',
    'nav.nps': 'Satisfaction (NPS)',
    'nav.conversations': 'Conversations',
    'nav.voice_clones': 'Voix custom',
    'nav.studio': 'Studio voix',
    'nav.modules': 'Modules',
    'nav.live': 'Live (center)',
    'common.save': 'Enregistrer',
    'common.cancel': 'Annuler',
    'common.delete': 'Supprimer',
    'common.edit': 'Modifier',
    'common.add': 'Ajouter',
    'common.search': 'Rechercher',
    'common.loading': 'Chargement…',
    'common.empty': 'Aucun élément',
    'common.back': 'Retour',
    'lang.label': 'Langue',
  },
  de: {
    'nav.dashboard': 'Übersicht', 'nav.lines': 'Leitungen', 'nav.calls': 'Anrufe', 'nav.sms': 'SMS',
    'nav.bots': 'Sprachbots', 'nav.contacts': 'Kontakte', 'nav.proxy': 'Temporäre Nummern',
    'nav.spam': 'Spam & Blacklist', 'nav.porting': 'Portierung', 'nav.api': 'API-Schlüssel',
    'nav.billing': 'Abrechnung', 'nav.profile': 'Profil', 'nav.routing': 'Erweitertes Routing',
    'nav.nps': 'Zufriedenheit (NPS)', 'nav.conversations': 'Konversationen', 'nav.voice_clones': 'Eigene Stimmen',
    'nav.studio': 'Sprachstudio',
    'nav.modules': 'Module',
    'nav.live': 'Live (Center)',
    'common.save': 'Speichern', 'common.cancel': 'Abbrechen', 'common.delete': 'Löschen',
    'common.edit': 'Bearbeiten', 'common.add': 'Hinzufügen', 'common.search': 'Suchen',
    'common.loading': 'Lädt…', 'common.empty': 'Keine Einträge', 'common.back': 'Zurück',
    'lang.label': 'Sprache',
  },
  en: {
    'nav.dashboard': 'Dashboard', 'nav.lines': 'Lines', 'nav.calls': 'Calls', 'nav.sms': 'SMS',
    'nav.bots': 'Voice bots', 'nav.contacts': 'Contacts', 'nav.proxy': 'Temporary numbers',
    'nav.spam': 'Spam & blacklist', 'nav.porting': 'Porting', 'nav.api': 'API keys',
    'nav.billing': 'Billing', 'nav.profile': 'Profile', 'nav.routing': 'Advanced routing',
    'nav.nps': 'Satisfaction (NPS)', 'nav.conversations': 'Conversations', 'nav.voice_clones': 'Custom voices',
    'nav.studio': 'Voice studio',
    'nav.modules': 'Modules',
    'nav.live': 'Live (center)',
    'common.save': 'Save', 'common.cancel': 'Cancel', 'common.delete': 'Delete',
    'common.edit': 'Edit', 'common.add': 'Add', 'common.search': 'Search',
    'common.loading': 'Loading…', 'common.empty': 'No items', 'common.back': 'Back',
    'lang.label': 'Language',
  },
  it: {
    'nav.dashboard': 'Cruscotto', 'nav.lines': 'Linee', 'nav.calls': 'Chiamate', 'nav.sms': 'SMS',
    'nav.bots': 'Bot vocali', 'nav.contacts': 'Contatti', 'nav.proxy': 'Numeri temporanei',
    'nav.spam': 'Spam & blacklist', 'nav.porting': 'Portabilità', 'nav.api': 'Chiavi API',
    'nav.billing': 'Fatturazione', 'nav.profile': 'Profilo', 'nav.routing': 'Routing avanzato',
    'nav.nps': 'Soddisfazione (NPS)', 'nav.conversations': 'Conversazioni', 'nav.voice_clones': 'Voci personalizzate',
    'nav.studio': 'Studio voce',
    'nav.modules': 'Moduli',
    'nav.live': 'Live (center)',
    'common.save': 'Salva', 'common.cancel': 'Annulla', 'common.delete': 'Elimina',
    'common.edit': 'Modifica', 'common.add': 'Aggiungi', 'common.search': 'Cerca',
    'common.loading': 'Caricamento…', 'common.empty': 'Nessun elemento', 'common.back': 'Indietro',
    'lang.label': 'Lingua',
  },
};

const LANG_KEY = 'vocal_lang';
function getLang() { return localStorage.getItem(LANG_KEY) || (navigator.language || 'fr').slice(0,2); }
function setLang(l) { localStorage.setItem(LANG_KEY, l); window.dispatchEvent(new Event('lang-change')); }
function useLang() {
  const [lang, setLangState] = React.useState(getLang());
  React.useEffect(() => {
    const h = () => setLangState(getLang());
    window.addEventListener('lang-change', h);
    return () => window.removeEventListener('lang-change', h);
  }, []);
  return lang;
}
function t(key, lang) {
  const L = lang || getLang();
  return (I18N[L] && I18N[L][key]) || I18N.fr[key] || key;
}

// ==================== ICONS ====================
const Icons = {
    Phone: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"/></svg>),
    PhoneIn: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M21 3l-6 6m0 0V4m0 5h5M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"/></svg>),
    Home: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/></svg>),
    Key: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"/></svg>),
    Send: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/></svg>),
    Chat: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/></svg>),
    Settings: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/><path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/></svg>),
    Search: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>),
    Plus: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M12 4v16m8-8H4"/></svg>),
    Trash: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>),
    Refresh: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>),
    Menu: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M4 6h16M4 12h16M4 18h16"/></svg>),
    LogOut: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/></svg>),
    Eye: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path strokeLinecap="round" strokeLinejoin="round" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>),
    EyeOff: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"/></svg>),
    Copy: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2" strokeLinecap="round" strokeLinejoin="round"/><path strokeLinecap="round" strokeLinejoin="round" d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>),
    Check: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7"/></svg>),
    ChevronLeft: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M15 19l-7-7 7-7"/></svg>),
    ChevronRight: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7"/></svg>),
    Clock: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>),
    Card: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z"/></svg>),
    Wallet: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M21 12V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2h14a2 2 0 002-2v-3m-4-1a2 2 0 11-4 0 2 2 0 014 0z"/></svg>),
    Edit: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg>),
    Star: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z"/></svg>),
    AlertTriangle: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/></svg>),
    ArrowLeft: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M10 19l-7-7m0 0l7-7m-7 7h18"/></svg>),
    Robot: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M12 2v3m-6 3h12a2 2 0 012 2v8a2 2 0 01-2 2H6a2 2 0 01-2-2v-8a2 2 0 012-2zm3 4h6m-6 4h.01m5.99 0H15M9 18h6"/></svg>),
    Calendar: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/></svg>),
    Sparkles: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z"/></svg>),
    Mic: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M19 11a7 7 0 01-14 0M12 18v3m-4 0h8M12 3a3 3 0 00-3 3v6a3 3 0 006 0V6a3 3 0 00-3-3z"/></svg>),
    Globe: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>),
    Document: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>),
    Play: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"/><path strokeLinecap="round" strokeLinejoin="round" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>),
    Users: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/></svg>),
    Shield: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"/></svg>),
    AlertCircle: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>),
    Moon: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"/></svg>),
    Briefcase: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M21 13.255A23.931 23.931 0 0112 15c-3.183 0-6.22-.62-9-1.745M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v2m4 6h.01M5 20h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/></svg>),
    MessageSquare: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/></svg>),
    X: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12"/></svg>),
    ExternalLink: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/></svg>),
    Upload: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"/></svg>),
    Download: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/></svg>),
    Stop: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><rect x="6" y="6" width="12" height="12" rx="2" strokeLinejoin="round"/></svg>),
    Volume: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M15.536 8.464a5 5 0 010 7.072M17.95 6.05a8 8 0 010 11.9M5 9v6a1 1 0 001 1h3l4 3V5L9 8H6a1 1 0 00-1 1z"/></svg>),
    FileAudio: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M9 19V6l11-2v12M9 19a3 3 0 11-6 0 3 3 0 016 0zm11-2a3 3 0 11-6 0 3 3 0 016 0z"/></svg>),
    CheckCircle: ({ className = "w-5 h-5", ...rest }) => (<svg xmlns="http://www.w3.org/2000/svg" className={className} {...rest} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"><path strokeLinecap="round" strokeLinejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>),
};

const CallDirectionIcon = ({ direction }) => direction === 'outbound'
    ? <svg xmlns="http://www.w3.org/2000/svg" className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2" style={{ color: '#2563eb' }}><path strokeLinecap="round" strokeLinejoin="round" d="M5 19l14-14m0 0h-10m10 0v10"/></svg>
    : <svg xmlns="http://www.w3.org/2000/svg" className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2" style={{ color: '#16a34a' }}><path strokeLinecap="round" strokeLinejoin="round" d="M19 5L5 19m0 0h10M5 19V9"/></svg>;

const CallStatusDot = ({ status }) => {
    const colors = { completed: '#22c55e', ringing: '#3b82f6', 'in-progress': '#3b82f6', busy: '#f59e0b', 'no-answer': '#f59e0b', failed: '#ef4444', canceled: '#9ca3af', missed: '#ef4444' };
    return <span style={{ width: 8, height: 8, borderRadius: '50%', background: colors[status] || '#9ca3af', display: 'inline-block', flexShrink: 0 }} />;
};

// ==================== SKELETON ====================
const Skeleton = ({ width, height, className = '', style = {}, circle = false }) => (
    <span className={`skeleton ${circle ? 'skeleton-circle' : ''} ${className}`}
        style={{ width: typeof width === 'number' ? `${width}px` : (width || '100%'), height: typeof height === 'number' ? `${height}px` : (height || '0.85rem'), ...style }}>
        &nbsp;
    </span>
);
const SkeletonStatsGrid = ({ count = 4 }) => (
    <div className="skeleton-stats-grid">{Array.from({ length: count }).map((_, i) => (
        <div key={i} className="skeleton-stat-card">
            <Skeleton width={48} height={48} circle />
            <div style={{ flex: 1 }}><Skeleton width="55%" height={10} style={{ marginBottom: '0.5rem' }} /><Skeleton width="40%" height={20} /></div>
        </div>
    ))}</div>
);
const SkeletonTable = ({ rows = 8, cols = 6, hasHeader = true }) => (
    <div className="table-container">
        <table className="skeleton-table">
            {hasHeader && (
                <thead><tr>{Array.from({ length: cols }).map((_, c) => (
                    <th key={c} style={{ padding: '0.75rem 1rem', background: '#f8fafc', borderBottom: '1px solid var(--border)' }}>
                        <Skeleton width={`${50 + (c*7)%30}%`} height={9} />
                    </th>
                ))}</tr></thead>
            )}
            <tbody>{Array.from({ length: rows }).map((_, r) => (
                <tr key={r}>{Array.from({ length: cols }).map((_, c) => (
                    <td key={c}><Skeleton width={`${40 + ((r+c*13)%50)}%`} height={11} /></td>
                ))}</tr>
            ))}</tbody>
        </table>
    </div>
);
const SkeletonCards = ({ count = 6, columns = 3 }) => (
    <div style={{ display: 'grid', gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`, gap: '1rem' }}>
        {Array.from({ length: count }).map((_, i) => (
            <div key={i} className="card" style={{ padding: '1.25rem' }}>
                <Skeleton width="60%" height={14} style={{ marginBottom: '0.75rem' }} />
                <Skeleton width="100%" height={10} style={{ marginBottom: '0.4rem' }} />
                <Skeleton width="80%" height={10} />
            </div>
        ))}
    </div>
);
const useDebouncedValue = (value, delay = 300) => {
    const [debounced, setDebounced] = useState(value);
    useEffect(() => { const t = setTimeout(() => setDebounced(value), delay); return () => clearTimeout(t); }, [value, delay]);
    return debounced;
};

// ==================== API ====================
// Wrapper fetch qui dispatch vocal:network-down / vocal:network-up.
// Une perte de réseau provoque un TypeError ("Failed to fetch") côté navigateur.
// On retente la requête en boucle pendant l'offline, et on émet network-up dès
// que ça repasse, pour que l'UI continue son flow sans recharger la page.
async function vocalFetch(input, init, opts = {}) {
    const maxWait = opts.maxWaitMs ?? 60_000; // attend jusqu'à 60s un retour réseau avant de rejeter
    const start = Date.now();
    let attempt = 0;
    while (true) {
        try {
            const resp = await fetch(input, init);
            // succès : si on était signalés offline, on dit que c'est ok
            if (!navigator.onLine || window.__vocalOffline) {
                window.dispatchEvent(new CustomEvent('vocal:network-up'));
            }
            return resp;
        } catch (e) {
            // TypeError = fetch a échoué (réseau coupé / DNS / CORS pre-net)
            const isNetErr = e instanceof TypeError;
            if (!isNetErr) throw e;
            window.dispatchEvent(new CustomEvent('vocal:network-down', { detail: { error: e.message } }));
            if (Date.now() - start >= maxWait) throw e;
            attempt += 1;
            const delay = Math.min(500 + attempt * 500, 3000);
            await new Promise(r => setTimeout(r, delay));
        }
    }
}

const api = {
    token: null,
    async req(path, options = {}) {
        const headers = { 'Content-Type': 'application/json', ...options.headers };
        if (this.token) headers['Authorization'] = `Bearer ${this.token}`;
        const resp = await vocalFetch(`${CONFIG.API_URL}${path}`, { ...options, headers });
        return resp.json();
    },
    get(path) { return this.req(path); },
    post(path, body) { return this.req(path, { method: 'POST', body: JSON.stringify(body) }); },
    put(path, body) { return this.req(path, { method: 'PUT', body: JSON.stringify(body) }); },
    patch(path, body) { return this.req(path, { method: 'PATCH', body: JSON.stringify(body) }); },
    del(path) { return this.req(path, { method: 'DELETE' }); },
    async upload(path, formData) {
        const headers = {};
        if (this.token) headers['Authorization'] = `Bearer ${this.token}`;
        const resp = await vocalFetch(`${CONFIG.API_URL}${path}`, { method: 'POST', headers, body: formData });
        return resp.json();
    },
};

// ==================== OFFLINE OVERLAY ====================
// Affiche un overlay bloquant non-fermable dès qu'on détecte une coupure réseau,
// disparaît automatiquement quand la connexion revient.
const OfflineOverlay = () => {
    const [offline, setOffline] = useState(false);
    const [since, setSince] = useState(null);
    const [pinging, setPinging] = useState(false);

    useEffect(() => {
        const goOffline = () => {
            window.__vocalOffline = true;
            setOffline(prev => { if (!prev) setSince(Date.now()); return true; });
        };
        const goOnline = () => {
            window.__vocalOffline = false;
            setOffline(false);
            setSince(null);
        };
        window.addEventListener('offline', goOffline);
        window.addEventListener('online', goOnline);
        window.addEventListener('vocal:network-down', goOffline);
        window.addEventListener('vocal:network-up', goOnline);
        if (!navigator.onLine) goOffline();
        return () => {
            window.removeEventListener('offline', goOffline);
            window.removeEventListener('online', goOnline);
            window.removeEventListener('vocal:network-down', goOffline);
            window.removeEventListener('vocal:network-up', goOnline);
        };
    }, []);

    // Ping périodique vers /health quand on est offline pour récupérer dès que possible
    useEffect(() => {
        if (!offline) return;
        let stopped = false;
        const tick = async () => {
            if (stopped) return;
            setPinging(true);
            try {
                const r = await fetch(`${CONFIG.API_URL}/health`, { cache: 'no-store' });
                if (r.ok && !stopped) {
                    window.__vocalOffline = false;
                    setOffline(false);
                    setSince(null);
                    window.dispatchEvent(new CustomEvent('vocal:network-up'));
                }
            } catch (_) { /* still offline */ }
            finally { setPinging(false); }
            if (!stopped) setTimeout(tick, 3000);
        };
        const t = setTimeout(tick, 2000);
        return () => { stopped = true; clearTimeout(t); };
    }, [offline]);

    const [elapsed, setElapsed] = useState(0);
    useEffect(() => {
        if (!offline || !since) return;
        const id = setInterval(() => setElapsed(Math.floor((Date.now() - since) / 1000)), 1000);
        return () => clearInterval(id);
    }, [offline, since]);

    if (!offline) return null;

    return (
        <div style={{
            position: 'fixed', inset: 0, zIndex: 100000,
            background: 'rgba(15,23,42,0.78)', backdropFilter: 'blur(6px)',
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            fontFamily: 'Inter, sans-serif', padding: '1.5rem',
        }}>
            <div style={{
                background: '#fff', borderRadius: 16, maxWidth: 420, width: '100%',
                padding: '2rem 1.75rem', textAlign: 'center',
                boxShadow: '0 25px 60px rgba(0,0,0,0.35)',
            }}>
                <div style={{
                    width: 64, height: 64, margin: '0 auto 1.25rem',
                    borderRadius: '50%', background: '#fef3c7',
                    display: 'flex', alignItems: 'center', justifyContent: 'center',
                    fontSize: '2rem',
                }}>📡</div>
                <h2 style={{ margin: '0 0 0.5rem', fontSize: '1.25rem', color: '#0f172a' }}>
                    Connexion perdue
                </h2>
                <p style={{ margin: '0 0 1.25rem', color: '#475569', fontSize: '0.9rem', lineHeight: 1.5 }}>
                    Attente de récupération de votre connexion Internet… L'application reprendra automatiquement dès que le réseau sera de retour.
                </p>
                <div style={{
                    display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '0.5rem',
                    fontSize: '0.8rem', color: '#64748b', marginBottom: '1rem',
                }}>
                    <div style={{
                        width: 8, height: 8, borderRadius: '50%',
                        background: pinging ? '#f59e0b' : '#94a3b8',
                        animation: 'vocal-pulse 1.4s ease-in-out infinite',
                    }} />
                    <span>{pinging ? 'Tentative de reconnexion…' : 'En attente du réseau…'}</span>
                </div>
                <div style={{ fontSize: '0.75rem', color: '#94a3b8' }}>
                    Hors-ligne depuis {elapsed >= 60 ? `${Math.floor(elapsed / 60)}m ${elapsed % 60}s` : `${elapsed}s`}
                </div>
                <style>{`
                    @keyframes vocal-pulse {
                        0%, 100% { transform: scale(1); opacity: 1; }
                        50% { transform: scale(1.6); opacity: 0.4; }
                    }
                `}</style>
            </div>
        </div>
    );
};

// ==================== NOTIFICATIONS ====================
const useNotifications = () => {
    const show = useCallback((message, type = 'info') => {
        const el = document.createElement('div');
        el.style.cssText = `position:fixed;bottom:1.5rem;right:1.5rem;z-index:9999;padding:0.875rem 1.25rem;border-radius:10px;font-size:0.875rem;font-weight:500;color:#fff;box-shadow:0 8px 24px rgba(0,0,0,0.18);transform:translateY(0);opacity:1;transition:all 0.3s ease;max-width:360px;font-family:Inter,sans-serif;`;
        const colors = { success: '#16a34a', error: '#dc2626', info: '#6366f1' };
        el.style.background = colors[type] || colors.info;
        el.textContent = message;
        document.body.appendChild(el);
        setTimeout(() => { el.style.opacity = '0'; el.style.transform = 'translateY(12px)'; setTimeout(() => el.remove(), 300); }, 4000);
    }, []);
    return useMemo(() => ({
        success: (m) => show(m, 'success'),
        error: (m) => show(m, 'error'),
        info: (m) => show(m, 'info'),
    }), [show]);
};

// ==================== CONTEXT ====================
const AppContext = createContext();
const useApp = () => useContext(AppContext);

const AppProvider = ({ children }) => {
    const [user, setUser] = useState(() => {
        const saved = localStorage.getItem('vocal_my_user');
        const token = localStorage.getItem('vocal_my_token');
        if (saved && token) { api.token = token; return { ...JSON.parse(saved), token }; }
        return null;
    });
    const [view, setView] = useState(() => {
        const h = (window.location.hash || '').replace('#', '');
        return h && ['dashboard','lines','calls','sms','whatsapp','bots','contacts','proxy','spam','billing','subscriptions','porting','api-keys','settings','conversations','nps','voice-clones','studio','modules','callback-requests','callback-requests-pending','callback-requests-done','callback-requests-cancelled','callback-requests-all','whatsapp-inbox','whatsapp-messages','whatsapp-senders','sip'].includes(h) ? h : 'dashboard';
    });
    const [sidebarOpen, setSidebarOpen] = useState(false);
    const [selectedLineId, setSelectedLineId] = useState(null);

    const notify = useNotifications();

    const login = useCallback((userData, token) => {
        api.token = token;
        setUser({ ...userData, token });
        localStorage.setItem('vocal_my_user', JSON.stringify(userData));
        localStorage.setItem('vocal_my_token', token);
        notify.success('Connexion réussie');
    }, [notify]);

    const logout = useCallback(() => {
        api.post('/auth/logout').catch(() => {});
        api.token = null;
        setUser(null);
        localStorage.removeItem('vocal_my_user');
        localStorage.removeItem('vocal_my_token');
        notify.info('Déconnexion réussie');
    }, [notify]);

    // Auto-login magic link : exécuté au boot quel que soit l'état de session.
    // Si on est déjà loggué avec le bon email -> on consomme le code en silence + clean URL.
    // Si on est loggué avec un autre email ou pas loggué -> on switch sur le compte du lien.
    useEffect(() => {
        const params = new URLSearchParams(window.location.search);
        const magicEmail = params.get('email');
        const magicCode = params.get('code');
        if (!magicEmail || !magicCode) return;

        const cleanUrl = () => {
            const sp = new URLSearchParams(window.location.search);
            sp.delete('email');
            sp.delete('code');
            const qs = sp.toString();
            const newUrl = window.location.pathname + (qs ? '?' + qs : '') + (window.location.hash || '');
            window.history.replaceState({}, '', newUrl);
        };

        // Si on est déjà loggué avec le même email, on consomme silencieusement le code
        // (best effort : il sera invalide après, c'est OK puisqu'on a déjà la session) et on nettoie.
        const currentEmail = (user && user.email) ? user.email.toLowerCase() : null;
        const targetEmail = magicEmail.toLowerCase();
        if (currentEmail === targetEmail) {
            cleanUrl();
            return;
        }

        // Sinon : on consomme le code, on login (en remplaçant la session existante si différente).
        fetch(`${CONFIG.API_URL}/auth/verify-code`, {
            method: 'POST', headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ email: targetEmail, code: magicCode }),
        })
        .then(r => r.json())
        .then(data => {
            if (data && data.success && data.token) {
                api.token = data.token;
                const u = data.user || { email: targetEmail };
                setUser({ ...u, token: data.token });
                localStorage.setItem('vocal_my_user', JSON.stringify(u));
                localStorage.setItem('vocal_my_token', data.token);
            }
        })
        .catch(() => {})
        .finally(() => { cleanUrl(); });
    }, []);

    const refreshProfile = useCallback(async () => {
        try {
            const data = await api.get('/auth/me');
            if (data?.success && data.user) {
                setUser(prev => ({ ...prev, ...data.user, token: prev?.token }));
                localStorage.setItem('vocal_my_user', JSON.stringify(data.user));
            }
        } catch (e) {}
    }, []);

    const [selectedBotId, setSelectedBotId] = useState(null);
    const navigate = useCallback((v, opts = {}) => {
        setView(v);
        setSidebarOpen(false);
        if (opts.lineId) setSelectedLineId(opts.lineId);
        if (opts.botId) setSelectedBotId(opts.botId);
        if (v !== 'line-detail') {
            try { window.history.replaceState(null, '', `#${v}`); } catch (e) {}
        }
    }, []);

    const value = useMemo(() => ({
        user, view, sidebarOpen, setSidebarOpen, selectedLineId, setSelectedLineId,
        selectedBotId, setSelectedBotId,
        login, logout, navigate, notify, refreshProfile, setUser,
    }), [user, view, sidebarOpen, selectedLineId, selectedBotId, login, logout, navigate, notify, refreshProfile]);
    return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
};

// ==================== LOGIN ====================
const LoginScreen = () => {
    const { login } = useApp();
    const [email, setEmail] = useState('');
    const [code, setCode] = useState('');
    const [step, setStep] = useState('email');
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState('');

    // Note : l'auto-login magic link est géré dans AppProvider au boot.
    // Si on arrive ici, c'est que le code a échoué (déjà consommé / expiré) :
    // on pré-remplit l'email pour faciliter la ressaisie d'un code.
    useEffect(() => {
        const params = new URLSearchParams(window.location.search);
        const magicEmail = params.get('email');
        if (magicEmail && !email) { setEmail(magicEmail); setStep('code'); }
    }, []);

    const handleSendCode = async (e) => {
        e.preventDefault(); if (!email) return;
        setLoading(true); setError('');
        try {
            const resp = await fetch(`${CONFIG.API_URL}/auth/request-code`, {
                method: 'POST', headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ email }),
            });
            const data = await resp.json();
            if (data.success) setStep('code');
            else setError(data.error || 'Erreur');
        } catch (e) { setError('Erreur réseau'); }
        finally { setLoading(false); }
    };

    const handleVerify = async (e) => {
        e.preventDefault(); if (!code) return;
        setLoading(true); setError('');
        try {
            const resp = await fetch(`${CONFIG.API_URL}/auth/verify-code`, {
                method: 'POST', headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ email, code }),
            });
            const data = await resp.json();
            if (data.success && data.token) login(data.user || { email }, data.token);
            else setError(data.error || 'Code invalide');
        } catch (e) { setError('Erreur réseau'); }
        finally { setLoading(false); }
    };

    const features = [
        { icon: '📊', title: 'Statistiques en temps réel', desc: 'Suivez vos appels, SMS et coûts' },
        { icon: '📞', title: 'Vos lignes', desc: 'Consultez la configuration de vos lignes' },
        { icon: '🔑', title: 'Clés API', desc: 'Gérez vos accès pour intégrations tierces' },
        { icon: '⚙️', title: 'Self-service', desc: 'Espace personnel sécurisé' },
    ];

    return (
        <div className="login-split">
            <div className="login-form-side">
                <div className="login-form-inner">
                    <div className="login-brand">
                        <img src="assets/img/vocal-logotype.svg" alt="Vocal" style={{ height: '36px' }} />
                    </div>

                    {step === 'email' ? (
                        <form onSubmit={handleSendCode} className="login-form">
                            <h1 className="login-title" style={{ textAlign: 'center', marginBottom: '0.5rem' }}>Mon Espace Vocal</h1>
                            <p style={{ textAlign: 'center', color: 'var(--text-muted)', fontSize: '0.875rem', marginBottom: '2rem' }}>
                                Recevez un lien magique de connexion par email
                            </p>
                            <div className="form-group">
                                <label className="form-label">Adresse email</label>
                                <input type="email" value={email} onChange={(e) => setEmail(e.target.value)}
                                    placeholder="vous@exemple.com" className="form-input" autoFocus />
                            </div>
                            {error && <p style={{ color: '#dc2626', fontSize: '0.875rem', margin: '0 0 1rem' }}>{error}</p>}
                            <button type="submit" disabled={loading || !email} className="btn btn-primary" style={{ width: '100%' }}>
                                {loading ? 'Envoi...' : 'Recevoir le lien'}
                            </button>
                            <p style={{ marginTop: '1.5rem', textAlign: 'center', fontSize: '0.75rem', color: 'var(--text-muted)' }}>
                                Pas de mot de passe : un email suffit. Votre compte est créé automatiquement.
                            </p>
                        </form>
                    ) : (
                        <form onSubmit={handleVerify} className="login-form">
                            <h1 className="login-title" style={{ textAlign: 'center', marginBottom: '0.5rem' }}>Vérification</h1>
                            <p style={{ textAlign: 'center', color: 'var(--text-muted)', fontSize: '0.875rem', marginBottom: '2rem' }}>
                                Code envoyé à <strong>{email}</strong>
                            </p>
                            <div className="form-group" style={{ textAlign: 'center' }}>
                                <input type="text" value={code} onChange={(e) => setCode(e.target.value)}
                                    placeholder="000000" className="form-code-input" maxLength="6" autoFocus />
                            </div>
                            {error && <p style={{ color: '#dc2626', fontSize: '0.875rem', margin: '0 0 1rem', textAlign: 'center' }}>{error}</p>}
                            <button type="submit" disabled={loading || code.length < 4} className="btn btn-primary" style={{ width: '100%', marginBottom: '0.75rem' }}>
                                {loading ? 'Vérification...' : 'Se connecter'}
                            </button>
                            <button type="button" onClick={() => { setStep('email'); setCode(''); setError(''); }} className="btn btn-secondary" style={{ width: '100%' }}>
                                Retour
                            </button>
                        </form>
                    )}
                </div>
            </div>

            <div className="login-visual-side">
                <div className="login-visual-content">
                    <h2>Mon Espace Vocal</h2>
                    <p style={{ marginBottom: '2.5rem' }}>Pilotez votre téléphonie cloud depuis un seul endroit</p>
                    <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem', textAlign: 'left' }}>
                        {features.map((f, i) => (
                            <div key={i} style={{ background: 'rgba(255,255,255,0.12)', borderRadius: '12px', padding: '1rem' }}>
                                <div style={{ fontSize: '1.5rem', marginBottom: '0.5rem' }}>{f.icon}</div>
                                <div style={{ fontWeight: 600, fontSize: '0.875rem', marginBottom: '0.25rem' }}>{f.title}</div>
                                <div style={{ fontSize: '0.75rem', opacity: 0.8 }}>{f.desc}</div>
                            </div>
                        ))}
                    </div>
                </div>
            </div>
        </div>
    );
};

// ==================== SIDEBAR ====================
// =================== APP SWITCHER ===================
// Permet de basculer entre les apps Vocal (Compte / Studio / Bots) en
// auto-loguant l'utilisateur sur l'app cible via /go/magic-link.
const VOCAL_APPS = [
    { id: 'my',     label: 'Compte', desc: 'Lignes, appels, facturation',     url: 'https://my.helvia.app',     pill: 'Compte', color: '#2563eb' },
    { id: 'studio', label: 'Studio', desc: 'Voix synthétiques (TTS)',         url: 'https://studio.helvia.app', pill: 'Voice',  color: '#7c3aed' },
    { id: 'bots',   label: 'Bots',   desc: 'Agents IA & conversations',       url: 'https://bots.helvia.app',   pill: 'Bots',   color: '#0891b2' },
];
const CURRENT_APP_ID = 'my';
const AppSwitcher = () => {
    const [open, setOpen] = useState(false);
    const [loadingId, setLoadingId] = useState(null);
    const [error, setError] = useState('');

    const goTo = async (app) => {
        if (app.id === CURRENT_APP_ID) { setOpen(false); return; }
        setLoadingId(app.id); setError('');
        try {
            const res = await api.post('/go/magic-link', { target: app.url });
            if (res && res.url) {
                window.location.href = res.url;
            } else {
                setError(res?.error || 'Impossible de générer le lien');
                setLoadingId(null);
            }
        } catch (e) {
            setError('Erreur réseau');
            setLoadingId(null);
        }
    };

    return (
        <>
            <button
                type="button"
                onClick={() => setOpen(true)}
                title="Changer d'application"
                style={{
                    background: 'transparent', border: 'none', cursor: 'pointer',
                    padding: '0.15rem 0.3rem', marginLeft: '0.2rem',
                    color: 'var(--text-muted, #6b7280)', fontSize: '0.7rem',
                    display: 'inline-flex', alignItems: 'center',
                }}
            >▾</button>
            {open && (
                <div
                    onClick={(e) => e.target === e.currentTarget && setOpen(false)}
                    style={{ position: 'fixed', inset: 0, background: 'rgba(15,23,42,0.55)', zIndex: 9999, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '1rem' }}
                >
                    <div style={{ background: 'white', borderRadius: '1rem', width: '100%', maxWidth: '420px', padding: '1.5rem', boxShadow: '0 20px 60px rgba(0,0,0,0.3)' }}>
                        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '0.25rem' }}>
                            <h3 style={{ margin: 0, fontSize: '1.05rem', fontWeight: 700 }}>Applications Vocal</h3>
                            <button onClick={() => setOpen(false)} style={{ background: 'transparent', border: 'none', fontSize: '1.4rem', cursor: 'pointer', color: '#9ca3af', lineHeight: 1 }}>×</button>
                        </div>
                        <p style={{ margin: '0 0 1rem', fontSize: '0.8rem', color: '#6b7280' }}>
                            Vous êtes connecté une seule fois. Cliquez pour basculer sans ressaisir votre code.
                        </p>
                        <div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
                            {VOCAL_APPS.map(app => {
                                const isCurrent = app.id === CURRENT_APP_ID;
                                const isLoading = loadingId === app.id;
                                return (
                                    <button
                                        key={app.id}
                                        onClick={() => goTo(app)}
                                        disabled={isCurrent || !!loadingId}
                                        style={{
                                            display: 'flex', alignItems: 'center', gap: '0.75rem',
                                            textAlign: 'left', padding: '0.85rem 1rem',
                                            background: isCurrent ? '#f3f4f6' : 'white',
                                            border: '1px solid ' + (isCurrent ? '#e5e7eb' : '#e5e7eb'),
                                            borderRadius: '0.75rem',
                                            cursor: isCurrent ? 'default' : 'pointer',
                                            opacity: loadingId && !isLoading ? 0.5 : 1,
                                            transition: 'all 0.15s',
                                        }}
                                        onMouseEnter={(e) => { if (!isCurrent && !loadingId) { e.currentTarget.style.borderColor = app.color; e.currentTarget.style.background = '#fafafa'; } }}
                                        onMouseLeave={(e) => { if (!isCurrent) { e.currentTarget.style.borderColor = '#e5e7eb'; e.currentTarget.style.background = 'white'; } }}
                                    >
                                        <div style={{
                                            width: 36, height: 36, borderRadius: 8, flexShrink: 0,
                                            background: app.color + '15', color: app.color,
                                            display: 'flex', alignItems: 'center', justifyContent: 'center',
                                            fontWeight: 800, fontSize: '0.7rem', letterSpacing: '0.04em',
                                        }}>{app.pill.slice(0, 3).toUpperCase()}</div>
                                        <div style={{ flex: 1, minWidth: 0 }}>
                                            <div style={{ fontWeight: 600, fontSize: '0.9rem', color: '#111827' }}>
                                                {app.label}
                                                {isCurrent && <span style={{ marginLeft: 8, fontSize: '0.7rem', color: '#6b7280', fontWeight: 500 }}>(actuel)</span>}
                                            </div>
                                            <div style={{ fontSize: '0.75rem', color: '#6b7280', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{app.desc}</div>
                                        </div>
                                        <div style={{ fontSize: '0.75rem', color: '#9ca3af' }}>
                                            {isLoading ? '...' : (isCurrent ? '' : '→')}
                                        </div>
                                    </button>
                                );
                            })}
                        </div>
                        {error && <div style={{ marginTop: '0.75rem', padding: '0.5rem 0.75rem', background: '#fef2f2', color: '#991b1b', borderRadius: '0.5rem', fontSize: '0.8rem' }}>{error}</div>}
                    </div>
                </div>
            )}
        </>
    );
};

const Sidebar = () => {
    const { user, view, sidebarOpen, setSidebarOpen, navigate, logout } = useApp();
    const lang = useLang();
    // Niveau client : 1 (minimal) → 5 (tout). minLevel par item.
    // Niveau 1 voit : dashboard, lines, calls, sms, demandes, contacts, spam, billing, subscriptions, settings.
    const clientLevel = user?.client?.level ?? 1;
    const allItems = [
        { id: 'dashboard',         label: t('nav.dashboard', lang),     icon: Icons.Home,           section: 'main',    minLevel: 1 },
        { id: 'lines',             label: t('nav.lines', lang),         icon: Icons.Phone,          section: 'main',    minLevel: 1, keepOwnView: true },
        { id: 'calls',             label: t('nav.calls', lang),         icon: Icons.PhoneIn,        section: 'main',    minLevel: 1, parent: 'lines' },
        { id: 'sms',               label: t('nav.sms', lang),           icon: Icons.Send,           section: 'main',    minLevel: 1 },
        { id: 'callback-requests',           label: 'Demandes',     icon: Icons.Refresh,       section: 'main', minLevel: 1 },
        { id: 'callback-requests-pending',   label: 'À traiter',    icon: Icons.AlertCircle,   section: 'main', minLevel: 1, parent: 'callback-requests' },
        { id: 'callback-requests-done',      label: 'Traités',      icon: Icons.Phone,         section: 'main', minLevel: 1, parent: 'callback-requests' },
        { id: 'callback-requests-cancelled', label: 'Annulés',      icon: Icons.AlertCircle,   section: 'main', minLevel: 1, parent: 'callback-requests' },
        { id: 'callback-requests-all',       label: 'Tous',         icon: Icons.MessageSquare, section: 'main', minLevel: 1, parent: 'callback-requests' },
        { id: 'whatsapp',          label: 'WhatsApp',                   icon: Icons.Chat,           section: 'main',    minLevel: 1 },
        { id: 'whatsapp-inbox',    label: 'Conversations',              icon: Icons.MessageSquare,  section: 'main',    minLevel: 1, parent: 'whatsapp' },
        { id: 'whatsapp-messages', label: 'Historique brut',            icon: Icons.Send,           section: 'main',    minLevel: 1, parent: 'whatsapp' },
        { id: 'whatsapp-senders',  label: 'Comptes Business',           icon: Icons.Shield,         section: 'main',    minLevel: 1, parent: 'whatsapp' },
        { id: 'ai-agents',         label: 'Agents IA',                  icon: Icons.Sparkles,       section: 'main',    minLevel: 1 },
        { id: 'widgets',           label: 'Widgets',                    icon: Icons.Globe,          section: 'main',    minLevel: 1 },
        { id: 'bots',              label: t('nav.bots', lang),          icon: Icons.Robot,          section: 'main',    minLevel: 2 },
        { id: 'voice-clones',      label: t('nav.voice_clones', lang),  icon: Icons.Sparkles,       section: 'main',    minLevel: 3 },
        { id: 'studio',            label: t('nav.studio', lang),        icon: Icons.Sparkles,       section: 'main',    minLevel: 3 },
        { id: 'contacts',          label: t('nav.contacts', lang),      icon: Icons.Users,          section: 'crm',     minLevel: 1 },
        { id: 'conversations',     label: t('nav.conversations', lang), icon: Icons.MessageSquare,  section: 'crm',     minLevel: 2 },
        { id: 'nps',               label: t('nav.nps', lang),           icon: Icons.AlertCircle,    section: 'crm',     minLevel: 3 },
        { id: 'proxy',             label: t('nav.proxy', lang),         icon: Icons.Shield,         section: 'crm',     minLevel: 2 },
        { id: 'spam',              label: t('nav.spam', lang),          icon: Icons.AlertCircle,    section: 'crm',     minLevel: 1 },
        { id: 'billing',           label: t('nav.billing', lang),       icon: Icons.Wallet,         section: 'billing', minLevel: 1 },
        { id: 'subscriptions',     label: 'Abonnements',                icon: Icons.Card,           section: 'billing', minLevel: 1 },
        { id: 'modules',           label: t('nav.modules', lang),       icon: Icons.Sparkles,       section: 'billing', minLevel: 2 },
        { id: 'porting',           label: t('nav.porting', lang),       icon: Icons.Refresh,        section: 'billing', minLevel: 2 },
        { id: 'api-keys',          label: t('nav.api', lang),           icon: Icons.Key,            section: 'tools',   minLevel: 1 },
        { id: 'sip',               label: 'SIP / Wildix',               icon: Icons.Phone,          section: 'tools',   minLevel: 1 },
        { id: 'settings',          label: 'Paramètres',                 icon: Icons.Settings,       section: 'tools',   minLevel: 1 },
    ];
    const items = allItems.filter(i => clientLevel >= (i.minLevel || 1));
    const sections = {
        main: 'ESPACE PERSONNEL',
        crm: 'RELATIONS',
        billing: 'FACTURATION',
        tools: 'OUTILS',
    };
    const grouped = Object.entries(sections).map(([key, label]) => ({
        label, items: items.filter(i => i.section === key),
    }));
    return (
        <>
            {sidebarOpen && (<div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.3)', zIndex: 99 }} onClick={() => setSidebarOpen(false)} />)}
            <aside className={`sidebar ${sidebarOpen ? 'open' : ''}`}>
                <div className="sidebar-header">
                    <img src="assets/img/vocal-logotype.svg" alt="Vocal" className="logo" />
                    <span className="studio-pill">Compte</span>
                    <AppSwitcher />
                </div>
                <nav className="sidebar-nav">
                    {grouped.map(group => (
                        <div className="nav-section" key={group.label}>
                            <div className="nav-section-title">{group.label}</div>
                            {group.items.map(item => {
                                // Les sous-items (avec parent) ne s'affichent que si le parent
                                // OU l'un de ses sous-items est actif (groupe déplié).
                                if (item.parent) {
                                    const parentActive = view === item.parent
                                        || group.items.some(s => s.parent === item.parent && view === s.id)
                                        || (item.parent === 'lines' && view === 'line-detail');
                                    if (!parentActive) return null;
                                }
                                // Le parent ne se surligne PAS quand un sous-item est actif
                                // (sinon double highlight). On highlight seulement le sous-item.
                                const hasActiveChild = !item.parent && group.items.some(s => s.parent === item.id && view === s.id);
                                const isActive = !hasActiveChild && (
                                    view === item.id
                                    || (item.id === 'lines' && view === 'line-detail')
                                );
                                // Un parent qui a des enfants navigue vers son premier enfant
                                // (auto-sélection du sous-menu par défaut), sauf si keepOwnView=true
                                // (le parent reste cliquable vers sa propre vue, ex: Lignes -> Appels).
                                const firstChild = group.items.find(s => s.parent === item.id);
                                const target = (firstChild && !item.keepOwnView) ? firstChild.id : item.id;
                                return (
                                    <button
                                        key={item.id}
                                        className={`nav-item ${isActive ? 'active' : ''}`}
                                        onClick={() => navigate(target)}
                                        style={item.parent ? { paddingLeft: '2.5rem', fontSize: '0.85rem' } : undefined}
                                    >
                                        <item.icon className="nav-icon" />
                                        {item.label}
                                    </button>
                                );
                            })}
                        </div>
                    ))}
                </nav>
                <div className="sidebar-footer">
                    <TopbarControls />
                    <div className="user-info">
                        <div className="user-avatar">{(user?.name || user?.email || 'U')[0].toUpperCase()}</div>
                        <div className="user-details">
                            <div className="user-name">{user?.name || user?.client?.name || 'Mon compte'}</div>
                            <div className="user-role">{user?.email}</div>
                        </div>
                        <button onClick={logout} title="Déconnexion" style={{ background: 'none', border: 'none', cursor: 'pointer', padding: '0.5rem', color: 'var(--text-muted)' }}>
                            <Icons.LogOut className="w-5 h-5" />
                        </button>
                    </div>
                </div>
            </aside>
        </>
    );
};

// ==================== TOPBAR CONTROLS (lang) ====================
const TopbarControls = () => {
    const lang = useLang();
    return (
        <div style={{ display: 'flex', gap: '0.4rem', alignItems: 'center', marginBottom: '0.6rem', flexWrap: 'wrap' }}>
            <select
                aria-label={t('lang.label', lang)}
                title={t('lang.label', lang)}
                value={lang}
                onChange={(e) => setLang(e.target.value)}
                className="form-input"
                style={{ flex: '1 1 auto', minWidth: 80, padding: '0.4rem 0.5rem', fontSize: '0.8rem', marginBottom: 0 }}
            >
                <option value="fr">FR</option>
                <option value="de">DE</option>
                <option value="en">EN</option>
                <option value="it">IT</option>
            </select>
        </div>
    );
};

const MobileHeader = () => {
    const { setSidebarOpen } = useApp();
    return (
        <div className="mobile-header" style={{ display: 'none', position: 'sticky', top: 0, zIndex: 50, height: 'var(--header-height)', padding: '0 1rem', background: 'var(--bg-white)', borderBottom: '1px solid var(--border)', alignItems: 'center', gap: '0.75rem' }}>
            <button onClick={() => setSidebarOpen(true)} style={{ background: 'none', border: 'none', cursor: 'pointer', padding: '0.5rem' }}>
                <Icons.Menu className="w-6 h-6" />
            </button>
            <img src="assets/img/vocal-logotype.svg" alt="Vocal" style={{ height: '28px' }} />
            <span style={{ fontWeight: 700, fontSize: '1rem' }}>Mon Espace</span>
        </div>
    );
};

// ==================== DASHBOARD ====================
// ==================== RECOMMENDATIONS CARD (personnalisées) ====================
// Affiche jusqu'à 3 suggestions calculées côté serveur (modules à activer, packs adaptés, etc).
// Le client peut masquer une reco (persistée localStorage) ou tout déplier pour voir le détail + CTA.
const RECO_TYPE_STYLE = {
    module:          { color: '#a855f7', label: 'Module suggéré' },
    bot:             { color: '#6366f1', label: 'Bot vocal' },
    pack:            { color: '#0891b2', label: 'Pack recommandé' },
    recharge_urgent: { color: '#dc2626', label: 'Wallet bas' },
    reallocation:    { color: '#0d9488', label: 'Crédit non utilisé' },
    upgrade_annual:  { color: '#059669', label: 'Économisez en annuel' },
};
const PersonalRecommendationsCard = ({ navigate, notify }) => {
    const [recos, setRecos] = useState(null);
    const [loading, setLoading] = useState(true);
    const [expanded, setExpanded] = useState(false);
    const [hidden, setHidden] = useState(() => {
        try { return new Set(JSON.parse(localStorage.getItem('vocal_my_reco_hidden') || '[]')); }
        catch (e) { return new Set(); }
    });
    const persistHidden = (s) => {
        setHidden(new Set(s));
        try { localStorage.setItem('vocal_my_reco_hidden', JSON.stringify([...s])); } catch (e) {}
    };

    useEffect(() => {
        let cancel = false;
        (async () => {
            try {
                const r = await api.get('/my/recommendations');
                if (!cancel && !r?.error) setRecos(r);
            } catch (e) { /* silencieux */ }
            finally { if (!cancel) setLoading(false); }
        })();
        return () => { cancel = true; };
    }, []);

    if (loading || !recos || !recos.offers || recos.offers.length === 0) return null;
    const visible = recos.offers.filter(o => !hidden.has(o.code));
    if (visible.length === 0) return null;

    const top = expanded ? visible : visible.slice(0, 3);
    const ctaTarget = (offer) => {
        if (offer.type === 'pack' || offer.type === 'recharge_urgent') return 'billing';
        if (offer.type === 'module' || offer.type === 'reallocation') return 'modules';
        if (offer.type === 'bot') return null;
        if (offer.type === 'upgrade_annual') return 'lines';
        return null;
    };
    const externalLink = (offer) => {
        if (offer.type === 'bot') return `https://bots.helvia.app?token=${encodeURIComponent(localStorage.getItem('vocal_my_token') || '')}`;
        return null;
    };

    return (
        <div className="card" style={{
            marginBottom: '1.5rem', padding: '1.25rem 1.5rem',
            background: 'linear-gradient(135deg, #f5f3ff 0%, #ecfeff 100%)',
            border: '1px solid #c7d2fe',
        }}>
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '1rem', gap: '1rem', flexWrap: 'wrap' }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
                    <div style={{
                        width: 42, height: 42, borderRadius: 12,
                        background: 'linear-gradient(135deg, #6366f1, #a855f7)',
                        color: '#fff', display: 'flex', alignItems: 'center', justifyContent: 'center',
                    }}>
                        <Icons.Sparkles className="w-5 h-5" />
                    </div>
                    <div>
                        <div style={{ fontSize: '1rem', fontWeight: 800, color: 'var(--text-main)' }}>
                            Suggestions personnalisées pour vous
                        </div>
                        <div style={{ fontSize: '0.78rem', color: 'var(--text-muted)' }}>
                            Calculées d'après votre usage des 30 derniers jours.
                        </div>
                    </div>
                </div>
                {visible.length > 3 && (
                    <button className="btn btn-secondary btn-sm" onClick={() => setExpanded(e => !e)}>
                        {expanded ? 'Réduire' : `Voir les ${visible.length} suggestions`}
                    </button>
                )}
            </div>
            <div style={{ display: 'grid', gap: '0.6rem' }}>
                {top.map((o, i) => {
                    const meta = RECO_TYPE_STYLE[o.type] || { color: '#6366f1', label: 'Suggestion' };
                    const internal = ctaTarget(o);
                    const external = externalLink(o);
                    return (
                        <div key={i} style={{
                            background: '#fff', border: '1px solid var(--border)',
                            borderLeft: `4px solid ${meta.color}`,
                            borderRadius: 10, padding: '0.85rem 1rem',
                        }}>
                            <div style={{ display: 'flex', gap: '0.45rem', alignItems: 'center', marginBottom: '0.35rem' }}>
                                <span style={{
                                    background: `${meta.color}1a`, color: meta.color,
                                    padding: '0.18rem 0.55rem', borderRadius: 6,
                                    fontSize: '0.62rem', fontWeight: 800, letterSpacing: '0.04em',
                                }}>{meta.label.toUpperCase()}</span>
                            </div>
                            <div style={{ fontSize: '0.95rem', fontWeight: 700, color: 'var(--text-main)', marginBottom: '0.25rem' }}>{o.title}</div>
                            <div style={{ fontSize: '0.83rem', color: 'var(--text-muted)', lineHeight: 1.55, marginBottom: '0.6rem' }}>{o.desc}</div>
                            <div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.4rem', alignItems: 'center' }}>
                                {internal && (
                                    <button className="btn btn-primary btn-sm" onClick={() => navigate(internal)}>
                                        {o.cta.length > 60 ? 'Découvrir' : o.cta}
                                    </button>
                                )}
                                {external && (
                                    <a className="btn btn-primary btn-sm" href={external} target="_blank" rel="noopener">
                                        Ouvrir bots.helvia.app
                                    </a>
                                )}
                                {!internal && !external && (
                                    <button className="btn btn-secondary btn-sm" onClick={() => notify.info(o.cta)}>
                                        Détails
                                    </button>
                                )}
                                <button className="btn btn-secondary btn-sm" onClick={() => {
                                    const next = new Set(hidden); next.add(o.code); persistHidden(next);
                                }} title="Masquer cette suggestion">
                                    Plus tard
                                </button>
                            </div>
                        </div>
                    );
                })}
            </div>
            {hidden.size > 0 && (
                <div style={{ marginTop: '0.6rem', textAlign: 'right' }}>
                    <button className="btn btn-secondary btn-sm" onClick={() => persistHidden(new Set())}>
                        Réafficher les {hidden.size} suggestion(s) masquée(s)
                    </button>
                </div>
            )}
        </div>
    );
};

const DashboardView = () => {
    const { user, navigate, notify } = useApp();
    const [stats, setStats] = useState(null);
    const [balance, setBalance] = useState(null);
    const [recent, setRecent] = useState([]);
    const [loading, setLoading] = useState(true);
    const [refreshing, setRefreshing] = useState(false);

    const load = useCallback(async (background = false) => {
        if (background) setRefreshing(true); else setLoading(true);
        try {
            const [s, c, b] = await Promise.all([
                api.get('/my/stats'),
                api.get('/my/calls?page=1&limit=5'),
                api.get('/my/billing/balance'),
            ]);
            if (s?.error) {
                notify.error(s.error);
                setStats({ lines: { total: 0, active: 0 }, calls: { total: 0, today: 0, month: 0, total_duration: 0, total_cost: 0 }, api_keys: { total: 0 } });
            } else {
                setStats(s);
            }
            setBalance(b);
            setRecent(c?.calls || []);
        } catch (e) { notify.error('Erreur chargement dashboard'); }
        finally { setLoading(false); setRefreshing(false); }
    }, [notify]);

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

    const cards = stats ? [
        { label: 'Mes lignes actives', value: `${stats.lines?.active || 0} / ${stats.lines?.total || 0}`, icon: Icons.Phone, color: 'green', view: 'lines' },
        { label: 'Appels ce mois', value: stats.calls?.month || 0, icon: Icons.PhoneIn, color: 'blue', view: 'calls' },
        { label: 'Coût total', value: formatCurrency(stats.calls?.total_cost || 0), icon: Icons.Card, color: 'purple', view: 'calls' },
        { label: 'Clés API', value: stats.api_keys?.total || 0, icon: Icons.Key, color: 'orange', view: 'api-keys' },
    ] : [];

    return (
        <>
            <div className="page-header">
                <div>
                    <h1 className="page-title">Bonjour, {user?.name || user?.client?.name || user?.email?.split('@')[0]}</h1>
                    <p className="page-subtitle">Vue d'ensemble de votre activité Vocal</p>
                </div>
                <div className="page-actions">
                    <button className="btn btn-secondary btn-sm" onClick={() => load(true)} disabled={refreshing}>
                        <Icons.Refresh className="w-4 h-4" /> {refreshing ? 'Mise à jour...' : 'Actualiser'}
                    </button>
                </div>
            </div>
            <div className="page-content">
                {/* Mini-widget solde */}
                {!loading && balance && (
                    <button onClick={() => navigate('billing')} className="card"
                        style={{ width: '100%', textAlign: 'left', cursor: 'pointer', padding: '1.25rem 1.5rem', marginBottom: '1.5rem', background: balance.balance < (balance.critical_threshold || 1) ? 'linear-gradient(135deg, #fef2f2, #fef3c7)' : balance.balance < (balance.low_threshold || 5) ? 'linear-gradient(135deg, #fef3c7, #fffbeb)' : 'linear-gradient(135deg, #eef2ff, #f0f9ff)', border: '1px solid #c7d2fe', display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: '1rem' }}>
                        <div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
                            <div style={{ width: 48, height: 48, borderRadius: 12, background: '#fff', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--primary)' }}>
                                <Icons.Wallet className="w-6 h-6" />
                            </div>
                            <div>
                                <div style={{ fontSize: '0.75rem', color: 'var(--text-muted)', textTransform: 'uppercase', fontWeight: 600, letterSpacing: '0.05em' }}>Solde wallet</div>
                                <div style={{ fontSize: '1.5rem', fontWeight: 800, color: balance.balance < (balance.critical_threshold || 1) ? '#dc2626' : balance.balance < (balance.low_threshold || 5) ? '#d97706' : 'var(--text-main)' }}>
                                    {(balance.balance || 0).toFixed(2)} {balance.currency || 'CHF'}
                                </div>
                            </div>
                        </div>
                        <div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', color: 'var(--primary)', fontWeight: 600, fontSize: '0.875rem' }}>
                            Recharger
                            <span>→</span>
                        </div>
                    </button>
                )}

                {/* Suggestions personnalisées (masquées au niveau 1 pour ne pas brouiller l'expérience) */}
                {(user?.client?.level ?? 1) >= 2 && (
                    <PersonalRecommendationsCard navigate={navigate} notify={notify} />
                )}

                {loading ? (
                    <SkeletonStatsGrid count={4} />
                ) : (
                    <div className="stats-grid">
                        {cards.map((c, i) => (
                            <button key={i} className="stat-card" onClick={() => navigate(c.view)}
                                style={{ cursor: 'pointer', textAlign: 'left', border: '1px solid var(--border)', background: 'var(--bg-white)' }}>
                                <div className={`stat-icon ${c.color}`}><c.icon className="w-6 h-6" /></div>
                                <div>
                                    <div className="stat-label">{c.label}</div>
                                    <div className="stat-value">{c.value}</div>
                                </div>
                            </button>
                        ))}
                    </div>
                )}

                <div className="card" style={{ marginBottom: '1.5rem' }}>
                    <div style={{ padding: '1rem 1.25rem', borderBottom: '1px solid var(--border)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
                        <h3 style={{ fontSize: '0.95rem', fontWeight: 700, margin: 0 }}>Derniers appels</h3>
                        <button className="btn btn-secondary btn-sm" onClick={() => navigate('calls')}>Voir tout</button>
                    </div>
                    {loading ? (
                        <SkeletonTable rows={5} cols={5} hasHeader={false} />
                    ) : recent.length === 0 ? (
                        <div style={{ padding: '3rem 2rem', textAlign: 'center', color: 'var(--text-muted)' }}>
                            <Icons.PhoneIn className="w-10 h-10" style={{ margin: '0 auto 0.75rem', opacity: 0.4 }} />
                            <p style={{ margin: 0, fontSize: '0.9rem' }}>Aucun appel récent</p>
                        </div>
                    ) : (
                        <div className="table-container">
                            <table className="data-table">
                                <thead><tr><th></th><th>Date</th><th>Appelant</th><th>Ligne</th><th>Statut</th><th style={{ textAlign: 'right' }}>Durée</th></tr></thead>
                                <tbody>
                                    {recent.map(call => {
                                        const st = CALL_STATUS[call.status] || { label: call.status, color: 'badge-gray' };
                                        return (
                                            <tr key={call.id}>
                                                <td style={{ width: 30 }}><CallDirectionIcon direction={call.direction} /></td>
                                                <td style={{ fontSize: '0.8rem' }}>
                                                    <div style={{ fontWeight: 500 }}>{formatDateShort(call.created_at)}</div>
                                                    <div style={{ fontSize: '0.7rem', color: 'var(--text-muted)' }}>{formatTimeOnly(call.created_at)}</div>
                                                </td>
                                                <td style={{ fontFamily: 'ui-monospace, monospace', fontSize: '0.8rem' }}>{call.from_number || '-'}</td>
                                                <td style={{ fontSize: '0.8rem' }}>{call.line_label || call.line_phone || '-'}</td>
                                                <td><div style={{ display: 'flex', alignItems: 'center', gap: '0.4rem' }}><CallStatusDot status={call.status} /><span style={{ fontSize: '0.8rem' }}>{st.label}</span></div></td>
                                                <td style={{ textAlign: 'right', fontSize: '0.8rem', fontFeatureSettings: '"tnum"' }}>{formatDuration(call.duration)}</td>
                                            </tr>
                                        );
                                    })}
                                </tbody>
                            </table>
                        </div>
                    )}
                </div>

            </div>
        </>
    );
};

// ==================== LINES ====================
// Bouton "Activer WhatsApp" pour une ligne — détecte le sender Twilio, l'importe
// en DB, synchronise les webhooks et active le bot. Idempotent : recliquer
// resynchronise simplement les webhooks.
const WhatsappActivateButton = ({ line }) => {
    const { notify } = useApp();
    const [busy, setBusy] = useState(false);
    const [state, setState] = useState(line.wa_bot_enabled ? 'enabled' : 'unknown');

    const run = async () => {
        if (busy) return;
        setBusy(true);
        try {
            const r = await api.post(`/my/lines/${line.id}/whatsapp-activate`, {});
            if (r?.error) { notify.error(r.error); setState('error'); return; }
            if (r.ready === false) {
                const help = r.reason === 'no_sender_on_twilio'
                    ? 'Aucun sender WhatsApp. Créez-le via "Activer un numéro".'
                    : r.message || 'Pas prêt';
                notify.warning ? notify.warning(help) : notify.error(help);
                setState('not_ready');
                return;
            }
            if (r.webhook_sync && r.webhook_sync.ok === false) {
                notify.error('Sender importé mais erreur sync webhooks : ' + (r.webhook_sync.twilio_error || r.webhook_sync.error || ''));
                setState('partial');
                return;
            }
            notify.success(r.message || 'WhatsApp activé');
            setState('enabled');
        } catch (e) {
            notify.error('Erreur activation WhatsApp');
            setState('error');
        } finally { setBusy(false); }
    };

    if (state === 'enabled') {
        return (
            <button className="btn btn-success btn-sm" onClick={run} disabled={busy}
                title="WhatsApp activé. Cliquer pour resynchroniser les webhooks.">
                {busy ? '…' : '✅ Activé'}
            </button>
        );
    }
    return (
        <button className="btn btn-primary btn-sm" onClick={run} disabled={busy}
            title="Détecter le sender WhatsApp et synchroniser les webhooks.">
            {busy ? '…' : '💬 Activer WhatsApp'}
        </button>
    );
};

const LinesView = () => {
    const { notify, navigate } = useApp();
    const [lines, setLines] = useState([]);
    const [subs, setSubs] = useState([]);
    const [loading, setLoading] = useState(true);
    const [search, setSearch] = useState('');

    const load = useCallback(async () => {
        setLoading(true);
        try {
            const [l, s] = await Promise.all([api.get('/my/lines'), api.get('/my/subscriptions')]);
            if (l?.error) notify.error(l.error);
            setLines(l?.lines || []);
            setSubs(s?.subscriptions || []);
        } catch (e) { notify.error('Erreur chargement lignes'); }
        finally { setLoading(false); }
    }, [notify]);

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

    const subByLine = useMemo(() => {
        const m = {};
        for (const s of subs) {
            if (!m[s.line_id] || parseUtcDate(s.created_at) > parseUtcDate(m[s.line_id].created_at)) m[s.line_id] = s;
        }
        return m;
    }, [subs]);

    const filtered = useMemo(() => {
        if (!search) return lines;
        const q = search.toLowerCase();
        return lines.filter(l => (l.phone_number || '').toLowerCase().includes(q) || (l.label || '').toLowerCase().includes(q) || (l.description || '').toLowerCase().includes(q));
    }, [lines, search]);

    return (
        <>
            <div className="page-header">
                <div>
                    <h1 className="page-title">Mes lignes</h1>
                    <p className="page-subtitle">{lines.length} ligne(s) configurée(s)</p>
                </div>
                <div className="page-actions">
                    <button className="btn btn-secondary btn-sm" onClick={load}><Icons.Refresh className="w-4 h-4" /> Actualiser</button>
                </div>
            </div>
            <div className="page-content">
                <div style={{ marginBottom: '1rem', position: 'relative', maxWidth: '320px' }}>
                    <Icons.Search className="w-4 h-4" style={{ position: 'absolute', left: '0.75rem', top: '50%', transform: 'translateY(-50%)', color: 'var(--text-muted)' }} />
                    <input type="text" value={search} onChange={(e) => setSearch(e.target.value)}
                        placeholder="Rechercher une ligne..." className="form-input" style={{ paddingLeft: '2.25rem' }} />
                </div>

                {loading ? (
                    <SkeletonTable rows={5} cols={6} />
                ) : filtered.length === 0 ? (
                    <div className="card" style={{ padding: '4rem 2rem', textAlign: 'center' }}>
                        <Icons.Phone className="w-12 h-12" style={{ margin: '0 auto 1rem', opacity: 0.4, color: 'var(--text-muted)' }} />
                        <h3 style={{ margin: '0 0 0.5rem', fontWeight: 600 }}>Aucune ligne</h3>
                        <p style={{ margin: 0, color: 'var(--text-muted)', fontSize: '0.875rem' }}>
                            Vous n'avez pas encore de ligne assignée. Contactez l'équipe Vocal pour configurer votre numéro.
                        </p>
                        <a href="mailto:contact@helvia.app" className="btn btn-primary btn-sm" style={{ marginTop: '1.25rem', display: 'inline-flex' }}>
                            Contacter Vocal
                        </a>
                    </div>
                ) : (
                    <div className="card" style={{ overflow: 'hidden' }}>
                        <div className="table-container">
                            <table className="data-table">
                                <thead>
                                    <tr>
                                        <th>Numéro</th>
                                        <th>Libellé</th>
                                        <th>Type</th>
                                        <th>Abonnement</th>
                                        <th style={{ textAlign: 'right' }}>Appels (mois)</th>
                                        <th style={{ textAlign: 'right' }}>Total</th>
                                        <th style={{ textAlign: 'center' }}>Statut</th>
                                        <th style={{ textAlign: 'center' }}>WhatsApp</th>
                                    </tr>
                                </thead>
                                <tbody>
                                    {filtered.map(l => {
                                        const t = LINE_TYPES[l.type] || { label: l.type, color: 'badge-gray' };
                                        const sub = subByLine[l.id];
                                        const isSubActive = sub && (sub.status === 'active' || sub.status === 'trialing');
                                        return (
                                            <tr key={l.id} onClick={() => navigate('line-detail', { lineId: l.id })} style={{ cursor: 'pointer' }}>
                                                <td style={{ fontFamily: 'ui-monospace, monospace', fontWeight: 700, fontSize: '0.85rem' }}>{l.phone_number}</td>
                                                <td>
                                                    <div style={{ fontWeight: 500, fontSize: '0.85rem' }}>{l.label || '-'}</div>
                                                    {l.description && <div style={{ fontSize: '0.7rem', color: 'var(--text-muted)', marginTop: '0.15rem' }}>{l.description.length > 60 ? l.description.slice(0, 60) + '…' : l.description}</div>}
                                                </td>
                                                <td><span className={`badge ${t.color}`}>{t.label}</span></td>
                                                <td>
                                                    {sub ? (
                                                        <span className={`badge ${isSubActive ? 'badge-success' : (sub.status === 'past_due' ? 'badge-warning' : 'badge-gray')}`}>
                                                            {sub.billing_period === 'yearly' ? 'Annuel' : 'Mensuel'} • {isSubActive ? 'actif' : sub.status}
                                                        </span>
                                                    ) : <span className="badge badge-gray">Aucun</span>}
                                                </td>
                                                <td style={{ textAlign: 'right', fontWeight: 600, fontSize: '0.85rem', fontFeatureSettings: '"tnum"' }}>{l.month_calls || 0}</td>
                                                <td style={{ textAlign: 'right', fontSize: '0.85rem', color: 'var(--text-muted)', fontFeatureSettings: '"tnum"' }}>{l.total_calls || 0}</td>
                                                <td style={{ textAlign: 'center' }}>
                                                    <span className={`badge ${l.is_active ? 'badge-success' : 'badge-gray'}`}>{l.is_active ? 'Active' : 'Inactive'}</span>
                                                </td>
                                                <td style={{ textAlign: 'center' }}>
                                                    <span className={`badge ${l.wa_bot_enabled ? 'badge-success' : 'badge-gray'}`}
                                                        title={l.wa_bot_enabled ? 'WhatsApp activé sur cette ligne' : 'WhatsApp non activé (l\'administrateur peut l\'activer depuis adm.helvia.app)'}>
                                                        {l.wa_bot_enabled ? '💬 Actif' : '○ Inactif'}
                                                    </span>
                                                </td>
                                            </tr>
                                        );
                                    })}
                                </tbody>
                            </table>
                        </div>
                    </div>
                )}
            </div>
        </>
    );
};

// ==================== LINE DETAIL ====================
const DAY_LABELS = [
    { id: 'monday',    label: 'Lundi' },
    { id: 'tuesday',   label: 'Mardi' },
    { id: 'wednesday', label: 'Mercredi' },
    { id: 'thursday',  label: 'Jeudi' },
    { id: 'friday',    label: 'Vendredi' },
    { id: 'saturday',  label: 'Samedi' },
    { id: 'sunday',    label: 'Dimanche' },
];
function defaultBusinessHours() {
    const out = { holidays: [] };
    DAY_LABELS.forEach(d => {
        const isWeekend = d.id === 'saturday' || d.id === 'sunday';
        out[d.id] = { open: !isWeekend, from: '09:00', to: '18:00' };
    });
    return out;
}

// === Bloc "Message d'accueil" pour le mode Redirection ===
// Permet d'activer un greeting joué AVANT le transfert : soit un texte (voix Twilio),
// soit un MP3/WAV uploadé (priorité à l'audio si les deux sont remplis).
// Zone de dépôt audio réutilisable : drag & drop + sélecteur de fichier classique
// + lecteur intégré quand un audio est défini. Accepte MP3, WAV, OGG, M4A.
const AudioDropzone = ({ value, onChange, disabled, notify, hint, maxSizeMB = 10 }) => {
    const [uploading, setUploading] = useState(false);
    const [dragOver, setDragOver] = useState(false);
    const inputRef = useRef(null);

    const ACCEPT = 'audio/mpeg,audio/mp3,audio/wav,audio/ogg,audio/m4a,audio/x-m4a,audio/webm';
    const ACCEPT_EXT = /\.(mp3|wav|ogg|m4a|webm)$/i;

    const upload = async (file) => {
        if (!file) return;
        const isAudio = (file.type && file.type.startsWith('audio/')) || ACCEPT_EXT.test(file.name);
        if (!isAudio) { notify.error('Format audio non supporté'); return; }
        if (file.size > maxSizeMB * 1024 * 1024) { notify.error(`Fichier trop volumineux (max ${maxSizeMB} Mo)`); return; }
        setUploading(true);
        try {
            const fd = new FormData();
            fd.append('file', file);
            const res = await api.upload('/api/audio/upload', fd);
            if (res?.url) {
                onChange(res.url);
                notify.success('Audio uploadé');
            } else {
                notify.error(res?.error || 'Erreur upload');
            }
        } catch (err) {
            notify.error('Erreur upload');
        } finally {
            setUploading(false);
        }
    };

    const handleFileInput = async (e) => {
        const file = e.target.files && e.target.files[0];
        await upload(file);
        try { e.target.value = ''; } catch (_) {}
    };

    const handleDrop = async (e) => {
        e.preventDefault(); e.stopPropagation();
        setDragOver(false);
        if (disabled || uploading) return;
        const file = e.dataTransfer.files && e.dataTransfer.files[0];
        await upload(file);
    };

    const onDragEnter = (e) => { e.preventDefault(); e.stopPropagation(); if (!disabled && !uploading) setDragOver(true); };
    const onDragOver  = (e) => { e.preventDefault(); e.stopPropagation(); if (!disabled && !uploading) setDragOver(true); };
    const onDragLeave = (e) => { e.preventDefault(); e.stopPropagation(); setDragOver(false); };

    if (value) {
        return (
            <div style={{ display: 'flex', alignItems: 'center', gap: '0.6rem', flexWrap: 'wrap', background: '#fff', padding: '0.55rem 0.7rem', border: '1px solid var(--border)', borderRadius: '0.5rem' }}>
                <audio src={value} controls preload="metadata" style={{ height: '36px', maxWidth: '100%', flex: '1 1 220px' }} />
                {!disabled && (
                    <button type="button" className="btn btn-secondary btn-sm" onClick={() => onChange('')} title="Supprimer l'audio">
                        Supprimer
                    </button>
                )}
            </div>
        );
    }

    return (
        <div>
            <div
                onClick={() => !disabled && !uploading && inputRef.current?.click()}
                onDragEnter={onDragEnter}
                onDragOver={onDragOver}
                onDragLeave={onDragLeave}
                onDrop={handleDrop}
                style={{
                    border: `2px dashed ${dragOver ? 'var(--primary)' : 'var(--border)'}`,
                    background: dragOver ? 'rgba(124,58,237,0.06)' : '#fff',
                    borderRadius: '0.6rem',
                    padding: '0.9rem 1rem',
                    textAlign: 'center',
                    cursor: disabled || uploading ? 'not-allowed' : 'pointer',
                    opacity: disabled ? 0.55 : 1,
                    transition: 'border-color 120ms ease, background 120ms ease',
                    display: 'flex',
                    alignItems: 'center',
                    justifyContent: 'center',
                    gap: '0.6rem',
                    minHeight: 64,
                }}
            >
                <span style={{ fontSize: '1.1rem', lineHeight: 1 }}>{uploading ? '⏳' : '🎵'}</span>
                <div style={{ textAlign: 'left' }}>
                    <div style={{ fontSize: '0.85rem', fontWeight: 600, color: 'var(--text)' }}>
                        {uploading ? 'Upload en cours…' : (dragOver ? 'Relâcher pour uploader' : 'Glisser un fichier ici ou cliquer pour parcourir')}
                    </div>
                    <div style={{ fontSize: '0.72rem', color: 'var(--text-muted)', marginTop: 2 }}>
                        MP3, WAV, OGG, M4A — max {maxSizeMB} Mo
                    </div>
                </div>
            </div>
            <input
                ref={inputRef}
                type="file"
                accept={ACCEPT}
                onChange={handleFileInput}
                disabled={disabled || uploading}
                style={{ display: 'none' }}
            />
            {hint && <small style={{ display: 'block', marginTop: '0.4rem', fontSize: '0.72rem', color: 'var(--text-muted)' }}>{hint}</small>}
        </div>
    );
};

const ForwardWelcomePanel = ({ form, setForm, editMode, notify }) => (
    <div style={{ marginTop: '0.5rem', padding: '1rem 1.25rem', background: '#f8fafc', borderRadius: '0.75rem', border: '1px solid var(--border)' }}>
        <h4 style={{ margin: '0 0 0.5rem', fontSize: '0.9rem', fontWeight: 700, color: 'var(--text)' }}>Message d'accueil avant la redirection</h4>
        <p style={{ margin: '0 0 0.85rem', fontSize: '0.8rem', color: 'var(--text-muted)' }}>
            Joue un court message à l'appelant avant que ça sonne sur votre numéro (ex : "Vous êtes bien chez X, je vous mets en relation").
            Vous pouvez choisir un texte (voix synthétique) ou uploader un MP3 personnalisé. Si les deux sont définis, l'audio est prioritaire.
        </p>

        <label style={{ display: 'flex', alignItems: 'center', gap: '0.6rem', padding: '0.4rem 0', cursor: editMode ? 'pointer' : 'default' }}>
            <input
                type="checkbox"
                checked={!!form.welcome_enabled}
                onChange={(e) => setForm(f => ({ ...f, welcome_enabled: e.target.checked }))}
                disabled={!editMode}
            />
            <span style={{ fontSize: '0.875rem' }}>Activer le message d'accueil</span>
        </label>

        {form.welcome_enabled && (
            <div style={{ marginTop: '0.75rem', display: 'flex', flexDirection: 'column', gap: '0.85rem', paddingLeft: '1.25rem', borderLeft: '2px solid var(--border)' }}>
                <div className="form-group" style={{ margin: 0 }}>
                    <label className="form-label" style={{ fontSize: '0.78rem', fontWeight: 600, marginBottom: '0.35rem' }}>
                        Texte d'accueil (lu par voix synthétique fr-FR)
                    </label>
                    <textarea
                        className="form-input"
                        rows="2"
                        value={form.voice_welcome_message || ''}
                        placeholder="Bonjour, vous êtes bien chez X. Je vous mets en relation, merci de patienter."
                        onChange={(e) => setForm(f => ({ ...f, voice_welcome_message: e.target.value }))}
                        disabled={!editMode}
                        style={{ resize: 'vertical' }}
                    />
                </div>

                <div className="form-group" style={{ margin: 0 }}>
                    <label className="form-label" style={{ fontSize: '0.78rem', fontWeight: 600, marginBottom: '0.35rem' }}>
                        Ou un fichier audio (prioritaire sur le texte)
                    </label>
                    <AudioDropzone
                        value={form.welcome_audio}
                        onChange={(url) => setForm(f => ({ ...f, welcome_audio: url, welcome_enabled: url ? true : f.welcome_enabled }))}
                        disabled={!editMode}
                        notify={notify}
                        hint="Astuce : pour générer un audio professionnel, utilisez le Studio voix (studio.helvia.app) puis uploadez le MP3 ici."
                    />
                </div>
            </div>
        )}
    </div>
);

// Annonce de messagerie : texte synthétique OU MP3 uploadé, joué avant le bip d'enregistrement.
const VoicemailGreetingPanel = ({ form, setForm, editMode, notify }) => (
    <div style={{ marginTop: '0.5rem', padding: '1rem 1.25rem', background: '#f8fafc', borderRadius: '0.75rem', border: '1px solid var(--border)' }}>
        <h4 style={{ margin: '0 0 0.5rem', fontSize: '0.9rem', fontWeight: 700, color: 'var(--text)' }}>Annonce de la messagerie</h4>
        <p style={{ margin: '0 0 0.85rem', fontSize: '0.8rem', color: 'var(--text-muted)' }}>
            Joué à l'appelant avant le bip d'enregistrement. Si l'audio est uploadé, il est prioritaire sur le texte.
        </p>
        <div style={{ display: 'flex', flexDirection: 'column', gap: '0.85rem' }}>
            <div className="form-group" style={{ margin: 0 }}>
                <label className="form-label" style={{ fontSize: '0.78rem', fontWeight: 600, marginBottom: '0.35rem' }}>
                    Texte d'annonce (voix synthétique fr-FR)
                </label>
                <textarea
                    className="form-input"
                    rows="2"
                    value={form.voicemail_greeting || ''}
                    placeholder="Bonjour, vous êtes bien chez X. Laissez-nous un message après le bip, nous vous rappellerons."
                    onChange={(e) => setForm(f => ({ ...f, voicemail_greeting: e.target.value }))}
                    disabled={!editMode}
                    style={{ resize: 'vertical' }}
                />
            </div>
            <div className="form-group" style={{ margin: 0 }}>
                <label className="form-label" style={{ fontSize: '0.78rem', fontWeight: 600, marginBottom: '0.35rem' }}>
                    Ou un fichier audio (prioritaire sur le texte)
                </label>
                <AudioDropzone
                    value={form.voicemail_greeting_audio}
                    onChange={(url) => setForm(f => ({ ...f, voicemail_greeting_audio: url }))}
                    disabled={!editMode}
                    notify={notify}
                    hint="Astuce : pour générer un audio professionnel, utilisez le Studio voix (studio.helvia.app) puis uploadez le MP3 ici."
                />
            </div>
        </div>
    </div>
);

// ==================== EASY WIZARD =====================================
// Vue simplifiée de configuration d'une ligne en 4 étapes :
//   1) Type de ligne (Transfert / Messagerie / Agent IA)
//   2) Numéro de renvoi (si transfert) ou voix IA (si agent IA)
//   3) Message d'accueil
//   4) Email de notification
// La vue Easy n'écrit qu'un sous-ensemble de champs ; tout le reste est
// préservé tel quel côté API.
// ----------------------------------------------------------------------
const EASY_TYPES = [
    {
        id: 'forward',
        label: 'Transférer les appels',
        desc: 'Le numéro sonne directement chez vous.',
        icon: 'forward',
        color: '#0ea5e9',
    },
    {
        id: 'forward_voicemail',
        label: 'Transfert + messagerie',
        desc: 'Sonne chez vous, bascule en messagerie si vous ne répondez pas.',
        icon: 'forward',
        color: '#10b981',
    },
    {
        id: 'simulring',
        label: 'Sonnerie simultanée',
        desc: 'Plusieurs numéros sonnent en même temps, le premier qui répond prend l\'appel.',
        icon: 'forward',
        color: '#8b5cf6',
    },
    {
        id: 'voicemail',
        label: 'Messagerie vocale',
        desc: 'Vos appelants laissent un message, vous le recevez par email.',
        icon: 'voicemail',
        color: '#6366f1',
    },
    {
        id: 'ivr',
        label: 'Menu à touches (IVR)',
        desc: 'Proposez un choix : "Tapez 1 pour…, tapez 2 pour…".',
        icon: 'forward',
        color: '#f59e0b',
    },
    {
        id: 'voice_ai',
        label: 'Agent IA vocal',
        desc: 'Une IA répond à vos appels 24h/24, dans la langue de l\'appelant.',
        icon: 'sparkles',
        color: '#ec4899',
    },
];

const LineEasyWizard = ({ line, onSaved, onCancel }) => {
    const { notify } = useApp();
    const [step, setStep] = useState(1);
    const [saving, setSaving] = useState(false);
    // Détection du pseudo-type Easy à partir des champs DB (forward + flags).
    const initialEasyType = (() => {
        if (line.type !== 'forward') return line.type || 'forward';
        if (line.forward_strategy === 'simulring') return 'simulring';
        if (line.forward_fallback_to_voicemail) return 'forward_voicemail';
        return 'forward';
    })();
    // Détermine la famille d'accueil pré-sélectionnée à partir du type
    const initialFamily = (() => {
        if (initialEasyType === 'voicemail') return 'voicemail';
        if (initialEasyType === 'voice_ai') return 'ai';
        if (['forward', 'forward_voicemail', 'simulring', 'ivr'].includes(initialEasyType)) {
            return line.welcome_enabled ? 'transfer_welcome' : 'transfer';
        }
        return '';
    })();
    const [form, setForm] = useState({
        family:               initialFamily,
        type:                 initialEasyType,
        forward_number:       line.forward_number || '',
        fallback_number_1:    line.fallback_number_1 || '',
        fallback_number_2:    line.fallback_number_2 || '',
        fallback_number_3:    line.fallback_number_3 || '',
        voice_welcome_message: line.voice_welcome_message || 'Bonjour, merci de votre appel.',
        welcome_enabled:      line.welcome_enabled !== 0,
        voicemail_email:      line.voicemail_email || '',
        voicemail_greeting:   line.voicemail_greeting || 'Bonjour, vous êtes bien chez nous. Laissez-nous un message après le bip et nous vous rappellerons.',
        voicemail_voice:      line.voicemail_voice || 'aura-2-agathe-fr',
        ai_greeting:          line.ai_greeting || 'Bonjour, vous êtes bien chez nous, comment puis-je vous aider ?',
        ai_name:              line.ai_name || '',
        voice_ai_prompt:      line.voice_ai_prompt || '',
        ai_knowledge:         line.ai_knowledge || '',
        voice_ai_engine:      line.voice_ai_engine || 'vDeepgram',
        voice_ai_elevenlabs_voice_id: line.voice_ai_elevenlabs_voice_id || '',
        voice_ai_languages: (() => {
            const v = line.voice_ai_languages;
            if (Array.isArray(v)) return v;
            if (typeof v === 'string' && v.trim()) { try { return JSON.parse(v) || []; } catch { return []; } }
            return [];
        })(),
        voice_ai_eleven_voices: (() => {
            const v = line.voice_ai_eleven_voices;
            if (v && typeof v === 'object' && !Array.isArray(v)) return v;
            if (typeof v === 'string' && v.trim()) { try { return JSON.parse(v) || {}; } catch { return {}; } }
            return {};
        })(),
        notification_email:   line.transcription_email || line.voicemail_email || '',
        // Capture structurée (RDV / réservation) — null = simple agent conversationnel
        ai_capture_mode:      line.ai_capture_mode || '',
        ai_capture_email:     line.ai_capture_email || '',
        ai_capture_extra_name: (() => {
            try { return JSON.parse(line.ai_capture_extra || '{}').establishment_name || ''; }
            catch { return ''; }
        })(),
        // IVR : options à touches. Chargé depuis ivr_config si existant.
        ivr_options: (() => {
            try {
                const cfg = typeof line.ivr_config === 'string' ? JSON.parse(line.ivr_config) : (line.ivr_config || {});
                if (Array.isArray(cfg.options) && cfg.options.length) {
                    return cfg.options.map(o => ({
                        digit: String(o.digit || ''),
                        label: o.label || '',
                        action: o.action || 'forward',
                        number: o.number || '',
                    }));
                }
            } catch (_) {}
            return [
                { digit: '1', label: '', action: 'forward', number: '' },
                { digit: '2', label: '', action: 'forward', number: '' },
            ];
        })(),
        // Étape 2 voicemail / accueil : source de l'audio ('tts' = généré, 'upload' = MP3 client)
        voicemail_audio_source: line.voicemail_greeting_audio && !line.voicemail_greeting ? 'upload' : 'tts',
        voicemail_uploaded_url:  (line.voicemail_greeting_audio && !line.voicemail_greeting) ? line.voicemail_greeting_audio : '',
        welcome_audio_source:    (line.welcome_audio && !line.voice_welcome_message) ? 'upload' : 'tts',
        welcome_uploaded_url:    line.welcome_audio || '',
        welcome_voice:           line.welcome_voice || line.voicemail_voice || 'aura-2-agathe-fr',
        // SIP / Wildix
        transfer_mode:    'phone',
        sip_pbx_url:      '',
        sip_extension:    '',
        sip_transport:    'tls',
        sip_username:     '',
        sip_password:     '',
    });

    const update = (k, v) => setForm(f => ({ ...f, [k]: v }));

    // Charge la config SIP (Wildix) si présente et pré-remplit le wizard en mode 'sip'.
    useEffect(() => {
        if (!line?.id) return;
        (async () => {
            try {
                const r = await api.get(`/my/lines/${line.id}/sip`);
                if (r?.sip?.pbx_url && r.sip?.extension) {
                    setForm(f => ({
                        ...f,
                        transfer_mode: 'sip',
                        sip_pbx_url:   r.sip.pbx_url,
                        sip_extension: r.sip.extension,
                        sip_transport: r.sip.transport || 'tls',
                        sip_username:  r.sip.username || '',
                        sip_password:  r.sip.password || '',
                    }));
                }
            } catch {}
        })();
    }, [line?.id]);

    // Étape 1 : famille (transfer / voicemail / ai). Étape 2 : sous-type (uniquement pour 'transfer').
    // Puis étapes spécifiques selon le type final retenu.
    // - transfer  → 5 étapes (famille, sous-type, détails, accueil, notif)
    // - voicemail → 3 étapes (famille, détails, notif)
    // - ai        → 3 étapes (famille, détails, notif)
    const isForwardLike = form.type === 'forward' || form.type === 'forward_voicemail' || form.type === 'simulring';
    const isTransferFamily = form.family === 'transfer' || form.family === 'transfer_welcome';

    // Construit dynamiquement la liste des étapes en fonction de la famille et du sous-type.
    // Chaque étape a un id qui pilote le rendu et la validation.
    const stepsPipeline = (() => {
        const list = ['family'];
        if (isTransferFamily) {
            list.push('subtype');
            list.push('details'); // numéros (forward/simulring) ou options IVR
            if (form.type === 'forward_voicemail') list.push('voicemail'); // annonce de la messagerie
            if (form.family === 'transfer_welcome') list.push('welcome'); // phrase d'accueil
            list.push('notif');
        } else if (form.family === 'voicemail') {
            list.push('voicemail');
            list.push('notif');
        } else if (form.family === 'ai') {
            list.push('ai');
            list.push('ai_knowledge'); // savoir métier de l'IA
            list.push('notif');
        }
        return list;
    })();
    const currentStepId = stepsPipeline[step - 1] || 'family';
    const totalSteps = stepsPipeline.length;

    const canNext = () => {
        switch (currentStepId) {
            case 'family':  return !!form.family;
            case 'subtype': return !!form.type;
            case 'details':
                if (form.type === 'simulring') {
                    const all = [form.forward_number, form.fallback_number_1, form.fallback_number_2, form.fallback_number_3]
                        .filter(n => n && n.trim());
                    return all.length >= 2 && all.every(n => /^\+?[0-9 ]{6,}$/.test(n));
                }
                if (isForwardLike) return /^\+?[0-9 ]{6,}$/.test(form.forward_number || '');
                if (form.type === 'ivr') {
                    const opts = (form.ivr_options || []).filter(o => o.digit && (o.number || o.action === 'voicemail'));
                    return opts.length >= 1;
                }
                return true;
            case 'voicemail':
                if (form.voicemail_audio_source === 'upload') return !!form.voicemail_uploaded_url;
                return !!(form.voicemail_greeting && form.voicemail_greeting.trim());
            case 'welcome':
                // Accueil optionnel : si activé en mode upload, exige un fichier ;
                // sinon (TTS ou désactivé), on accepte.
                if (!form.welcome_enabled) return true;
                if (form.welcome_audio_source === 'upload') return !!form.welcome_uploaded_url;
                return !!(form.voice_welcome_message && form.voice_welcome_message.trim());
            case 'ai':
                return !!(form.ai_greeting && form.ai_greeting.trim());
            case 'ai_knowledge':
                return true;
            case 'notif':
                return /^\S+@\S+\.\S+$/.test(form.notification_email || '');
            default: return true;
        }
    };

    const save = async () => {
        setSaving(true);
        try {
            // Mapping pseudo-types Easy → type DB réel
            const dbType = (form.type === 'forward_voicemail' || form.type === 'simulring') ? 'forward' : form.type;

            const payload = {
                type: dbType,
                welcome_enabled: form.welcome_enabled ? 1 : 0,
            };

            // Audio d'accueil : si l'utilisateur a uploadé un MP3, on le branche en welcome_audio,
            // sinon on garde voice_welcome_message (TTS au moment de l'appel).
            if (form.welcome_enabled) {
                if (form.welcome_audio_source === 'upload' && form.welcome_uploaded_url) {
                    payload.welcome_audio = form.welcome_uploaded_url;
                    payload.voice_welcome_message = '';
                } else {
                    payload.welcome_audio = '';
                    payload.voice_welcome_message = form.voice_welcome_message || '';
                }
            } else {
                payload.welcome_audio = '';
                payload.voice_welcome_message = '';
            }

            if (form.type === 'forward' || form.type === 'forward_voicemail' || form.type === 'simulring') {
                payload.forward_number = (form.forward_number || '').replace(/\s+/g, '');
                payload.voicemail_email = form.notification_email;
                payload.transcription_email = form.notification_email;
                // IMPORTANT : reset explicite si l'utilisateur n'est plus en mode "forward_voicemail"
                payload.forward_fallback_to_voicemail = (form.type === 'forward_voicemail') ? 1 : 0;
                // Nettoie voicemail_greeting si on n'est pas en mode messagerie après transfert.
                if (form.type !== 'forward_voicemail') {
                    payload.voicemail_greeting = '';
                    payload.voicemail_greeting_audio = '';
                } else {
                    // Mode "Transfert + messagerie" : prend l'annonce saisie par l'utilisateur
                    // (TTS ou upload MP3) — comme la famille "Messagerie vocale simple".
                    if (form.voicemail_audio_source === 'upload' && form.voicemail_uploaded_url) {
                        payload.voicemail_greeting_audio = form.voicemail_uploaded_url;
                        payload.voicemail_greeting = '';
                    } else {
                        payload.voicemail_greeting_audio = '';
                        payload.voicemail_greeting = form.voicemail_greeting || '';
                    }
                }
                payload.forward_strategy = form.type === 'simulring' ? 'simulring'
                    : (form.type === 'forward' && (form.fallback_number_1 || form.fallback_number_2 || form.fallback_number_3)) ? 'fallback'
                    : 'single';
                // Persiste les numéros fallback / simulring
                payload.fallback_number_1 = (form.fallback_number_1 || '').replace(/\s+/g, '') || null;
                payload.fallback_number_2 = (form.fallback_number_2 || '').replace(/\s+/g, '') || null;
                payload.fallback_number_3 = (form.fallback_number_3 || '').replace(/\s+/g, '') || null;
                if (form.type === 'forward_voicemail') {
                    payload.voicemail_voice = form.voicemail_voice || 'aura-2-agathe-fr';
                }
            } else if (form.type === 'ivr') {
                const options = (form.ivr_options || []).filter(o => o.digit && (o.number || o.action === 'voicemail'));
                payload.ivr_config = {
                    welcome: form.voice_welcome_message || '',
                    welcome_audio: (form.welcome_audio_source === 'upload' ? form.welcome_uploaded_url : '') || '',
                    options: options.map(o => ({
                        digit: o.digit,
                        label: o.label || `Touche ${o.digit}`,
                        action: o.action || 'forward',
                        number: (o.number || '').replace(/\s+/g, ''),
                        fallback_number: '',
                        audio_url: '',
                        message: '',
                    })),
                };
                payload.voicemail_email = form.notification_email;
                payload.transcription_email = form.notification_email;
            } else if (form.type === 'voicemail') {
                payload.voicemail_greeting = form.voicemail_greeting;
                payload.voicemail_voice = form.voicemail_voice || 'aura-2-agathe-fr';
                payload.voicemail_email = form.notification_email;
                payload.transcription_enabled = 1;
                payload.transcription_email = form.notification_email;
                if (form.voicemail_audio_source === 'upload' && form.voicemail_uploaded_url) {
                    payload.voicemail_greeting_audio = form.voicemail_uploaded_url;
                }
            } else if (form.type === 'voice_ai') {
                payload.ai_mode = 'deepgram';
                payload.ai_voice = 'aura-2-agathe-fr';
                payload.ai_greeting = form.ai_greeting;
                payload.ai_name = (form.ai_name || '').trim() || null;
                payload.voice_ai_prompt = form.voice_ai_prompt || `Tu es l'assistant vocal de ${line.label || 'notre entreprise'}. Réponds toujours en français par défaut, brièvement (1 à 2 phrases), de manière naturelle, chaleureuse et professionnelle. Si l'appelant te demande explicitement de parler une autre langue, fais-le. Si on te demande à être rappelé, propose de noter le nom et le numéro.`;
                payload.voice_ai_language = 'multi';
                payload.ai_knowledge = (form.ai_knowledge || '').trim() || null;
                payload.voice_ai_engine = (form.voice_ai_engine === 'vEleven') ? 'vEleven' : 'vDeepgram';
                payload.voice_ai_elevenlabs_voice_id = (form.voice_ai_elevenlabs_voice_id || '').trim() || null;
                // Langues actives + map { lang_code: voice_id } pour vEleven.
                {
                    const langs = Array.isArray(form.voice_ai_languages)
                        ? form.voice_ai_languages
                        : (typeof form.voice_ai_languages === 'string' && form.voice_ai_languages.trim()
                            ? (() => { try { return JSON.parse(form.voice_ai_languages); } catch { return []; } })()
                            : []);
                    payload.voice_ai_languages = langs.length ? langs : null;
                    const vm = (form.voice_ai_eleven_voices && typeof form.voice_ai_eleven_voices === 'object' && !Array.isArray(form.voice_ai_eleven_voices))
                        ? form.voice_ai_eleven_voices
                        : (typeof form.voice_ai_eleven_voices === 'string' && form.voice_ai_eleven_voices.trim()
                            ? (() => { try { return JSON.parse(form.voice_ai_eleven_voices); } catch { return {}; } })()
                            : {});
                    // On ne garde que les langues actives dans la map.
                    const filteredMap = {};
                    for (const code of (langs || [])) {
                        if (vm[code]) filteredMap[code] = vm[code];
                    }
                    payload.voice_ai_eleven_voices = Object.keys(filteredMap).length ? filteredMap : null;
                }
                payload.transcription_enabled = 1;
                payload.transcription_email = form.notification_email;
                payload.voicemail_email = form.notification_email;
                // Mode capture (RDV / réservation taxi / réservation restaurant)
                payload.ai_capture_mode = form.ai_capture_mode || null;
                if (form.ai_capture_mode) {
                    payload.ai_capture_email = (form.ai_capture_email && form.ai_capture_email.trim())
                        || form.notification_email
                        || null;
                    payload.ai_capture_extra = form.ai_capture_extra_name
                        ? { establishment_name: form.ai_capture_extra_name.trim() }
                        : null;
                } else {
                    payload.ai_capture_email = null;
                    payload.ai_capture_extra = null;
                }
            }
            const r = await api.put(`/my/lines/${line.id}`, payload);
            if (r?.error) { notify.error(r.error); return; }

            // Persiste la config SIP (Wildix) : create/update si mode sip + champs présents, sinon supprime.
            const sipMode = form.transfer_mode || (form.sip_pbx_url || form.sip_extension ? 'sip' : 'phone');
            const wantsSip = sipMode === 'sip' && (form.type === 'forward' || form.type === 'forward_voicemail' || form.type === 'simulring' || form.type === 'voice_ai');
            try {
                if (wantsSip && form.sip_pbx_url && form.sip_extension) {
                    await api.put(`/my/lines/${line.id}/sip`, {
                        pbx_url: String(form.sip_pbx_url).trim(),
                        extension: String(form.sip_extension).trim(),
                        transport: form.sip_transport || 'tls',
                        username: form.sip_username || null,
                        password: form.sip_password || null,
                    });
                } else if (!wantsSip) {
                    // Supprime toute config SIP existante si l'utilisateur revient en mode téléphone.
                    await api.put(`/my/lines/${line.id}/sip`, {});
                }
            } catch (e) { notify.error('SIP : ' + (e?.message || 'erreur enregistrement')); }

            // Phrase d'accueil TTS : génère le MP3 Aura-2 et l'attache en welcome_audio.
            // (Skippé si l'utilisateur a uploadé son propre MP3 ou désactivé l'accueil.)
            const shouldGenerateWelcome = form.welcome_enabled
                && form.welcome_audio_source !== 'upload'
                && form.voice_welcome_message && form.voice_welcome_message.trim()
                && (form.type === 'forward' || form.type === 'forward_voicemail' || form.type === 'simulring' || form.type === 'ivr');
            if (shouldGenerateWelcome) {
                try {
                    const g = await api.post(`/api/lines/${line.id}/welcome-generate`, {
                        text: form.voice_welcome_message.trim(),
                        voice: form.welcome_voice || 'aura-2-agathe-fr',
                    });
                    if (g?.error) notify.error('Accueil non généré : ' + g.error);
                    else notify.success('Phrase d\'accueil générée');
                } catch (e) { notify.error('Erreur génération accueil'); }
            }
            // Mode messagerie : génère l'audio Aura-2 pour <Play> et l'attache à la ligne.
            // Skippé si l'utilisateur a uploadé son propre MP3.
            const shouldGenerateVoicemail = (form.type === 'voicemail' || form.type === 'forward_voicemail')
                && form.voicemail_audio_source !== 'upload'
                && form.voicemail_greeting && form.voicemail_greeting.trim();
            if (shouldGenerateVoicemail) {
                try {
                    const g = await api.post(`/api/lines/${line.id}/voicemail-generate`, {
                        text: form.voicemail_greeting.trim(),
                        voice: form.voicemail_voice || 'aura-2-agathe-fr',
                    });
                    if (g?.error) notify.error('Audio non généré : ' + g.error);
                    else notify.success('Annonce vocale générée');
                } catch (e) { notify.error('Erreur génération audio'); }
            }
            notify.success('Configuration enregistrée');
            if (onSaved) onSaved();
        } catch (e) {
            notify.error('Erreur lors de la sauvegarde');
        } finally {
            setSaving(false);
        }
    };

    // ------- Preview live -------
    const Preview = () => {
        const t = EASY_TYPES.find(x => x.id === form.type) || EASY_TYPES[0];
        return (
            <div style={{ position: 'sticky', top: '1rem' }}>
                <div className="card" style={{ padding: '1.25rem', background: 'linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%)' }}>
                    <div style={{ fontSize: '0.7rem', textTransform: 'uppercase', color: 'var(--text-muted)', fontWeight: 700, letterSpacing: '0.05em', marginBottom: '0.5rem' }}>Aperçu de votre ligne</div>
                    <div style={{ fontFamily: 'ui-monospace, monospace', fontWeight: 800, fontSize: '1.15rem', marginBottom: '0.25rem' }}>{line.phone_number}</div>
                    <div style={{ fontSize: '0.85rem', color: 'var(--text-muted)', marginBottom: '1rem' }}>{line.label || 'Sans libellé'}</div>

                    <div style={{ borderTop: '1px solid var(--border)', paddingTop: '1rem', display: 'flex', flexDirection: 'column', gap: '0.65rem' }}>
                        <div style={{ display: 'flex', gap: '0.65rem', alignItems: 'flex-start' }}>
                            <div style={{ width: 28, height: 28, borderRadius: 8, background: t.color + '22', color: t.color, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
                                <Icons.Phone className="w-4 h-4" />
                            </div>
                            <div>
                                <div style={{ fontWeight: 600, fontSize: '0.85rem' }}>Appel entrant</div>
                                <div style={{ fontSize: '0.75rem', color: 'var(--text-muted)' }}>Quelqu'un appelle votre numéro</div>
                            </div>
                        </div>
                        {/* Phrase d'accueil (commune à forward / forward_voicemail / simulring / ivr) */}
                        {form.welcome_enabled && (form.type === 'forward' || form.type === 'forward_voicemail' || form.type === 'simulring' || form.type === 'ivr') && (
                            form.welcome_audio_source === 'upload' && form.welcome_uploaded_url
                                ? <Bubble icon="speaker" color="#64748b" title="Message d'accueil (MP3)" text="Fichier audio importé" />
                                : (form.voice_welcome_message
                                    ? <Bubble icon="speaker" color="#64748b" title={`Message d'accueil · ${voiceShortLabel(form.welcome_voice)}`} text={form.voice_welcome_message} />
                                    : null)
                        )}

                        {/* Transfert simple */}
                        {form.type === 'forward' && (
                            <>
                                <Bubble icon="forward" color={t.color} title="Transfert" text={form.forward_number || '+41 ...'} />
                                {(form.fallback_number_1 || form.fallback_number_2 || form.fallback_number_3) && (
                                    <Bubble icon="forward" color="#f59e0b" title="Si pas de réponse"
                                        text={[form.fallback_number_1, form.fallback_number_2, form.fallback_number_3].filter(Boolean).join(' → ')} />
                                )}
                            </>
                        )}

                        {/* Transfert + messagerie */}
                        {form.type === 'forward_voicemail' && (
                            <>
                                <Bubble icon="forward" color={t.color} title="Transfert" text={form.forward_number || '+41 ...'} />
                                <Bubble icon="voicemail" color="#6366f1" title="Si pas de réponse → messagerie"
                                    text={form.voicemail_greeting || 'Annonce par défaut'} />
                                <Bubble icon="mail" color="#0ea5e9" title="Notification email" text={form.notification_email || '...'} />
                            </>
                        )}

                        {/* Sonnerie simultanée */}
                        {form.type === 'simulring' && (
                            <>
                                <Bubble icon="forward" color={t.color} title="Sonnerie simultanée"
                                    text={[form.forward_number, form.fallback_number_1, form.fallback_number_2, form.fallback_number_3]
                                        .filter(n => n && n.trim()).join(' · ') || '+41 ...'} />
                                <Bubble icon="sparkles" color="#8b5cf6" title="Premier qui répond" text="Les autres téléphones arrêtent de sonner." />
                            </>
                        )}

                        {/* Menu IVR */}
                        {form.type === 'ivr' && (
                            <>
                                <Bubble icon="forward" color={t.color} title="Menu à touches"
                                    text={(form.ivr_options || []).filter(o => o.digit).map(o =>
                                        `${o.digit} → ${o.label || (o.action === 'voicemail' ? 'Messagerie' : (o.number || '—'))}`
                                    ).join(' · ') || 'Aucune touche configurée'} />
                            </>
                        )}

                        {/* Messagerie seule */}
                        {form.type === 'voicemail' && (
                            <>
                                {form.voicemail_audio_source === 'upload' && form.voicemail_uploaded_url
                                    ? <Bubble icon="voicemail" color={t.color} title="Annonce (MP3)" text="Fichier audio importé" />
                                    : <Bubble icon="voicemail" color={t.color} title={`Annonce · ${voiceShortLabel(form.voicemail_voice)}`} text={form.voicemail_greeting} />}
                                <Bubble icon="mail" color="#0ea5e9" title="Notification email" text={form.notification_email || '...'} />
                            </>
                        )}

                        {/* Agent IA */}
                        {form.type === 'voice_ai' && (
                            <>
                                <Bubble icon="sparkles" color={t.color}
                                    title={form.ai_name ? `L'agent IA — ${form.ai_name}` : "L'agent IA"}
                                    text={form.ai_greeting} />
                                <Bubble icon="globe" color="#22c55e" title="Multilingue" text="L'agent parle automatiquement la langue de l'appelant (FR / EN / DE / ES / IT...)" />
                                {form.ai_capture_mode && (
                                    <Bubble icon="sparkles" color="#f59e0b"
                                        title={
                                            form.ai_capture_mode === 'appointment' ? 'Prise de RDV' :
                                            form.ai_capture_mode === 'reservation_taxi' ? 'Réservation taxi' :
                                            'Réservation restaurant'
                                        }
                                        text={`Récap envoyé à ${form.ai_capture_email || form.notification_email || '...'}${form.ai_capture_extra_name ? ` · ${form.ai_capture_extra_name}` : ''}`} />
                                )}
                                <Bubble icon="mail" color="#0ea5e9" title="Transcription par email" text={form.notification_email || '...'} />
                            </>
                        )}
                    </div>
                </div>
            </div>
        );
    };

    return (
        <div className="card" style={{ padding: 0, overflow: 'hidden' }}>
            <div style={{ padding: '1rem 1.5rem', borderBottom: '1px solid var(--border)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
                <div style={{ fontWeight: 700, fontSize: '0.95rem' }}>Configuration rapide</div>
                <div style={{ display: 'flex', gap: '0.35rem' }}>
                    {Array.from({ length: totalSteps }).map((_, i) => (
                        <div key={i} style={{
                            width: 26, height: 4, borderRadius: 2,
                            background: i < step ? 'var(--vocal-primary, #6366f1)' : '#e2e8f0',
                            transition: 'background 200ms',
                        }} />
                    ))}
                </div>
            </div>
            <div style={{ display: 'grid', gridTemplateColumns: 'minmax(0,1fr) 320px', gap: '1.5rem', padding: '1.5rem' }}>
                <div>
                    {currentStepId === 'family'    && <Step0Family    form={form} update={update} />}
                    {currentStepId === 'subtype'   && <Step1Type      form={form} update={update} />}
                    {currentStepId === 'details'   && (form.type === 'ivr'
                        ? <Step2Ivr     form={form} update={update} />
                        : <Step2Forward form={form} update={update} />)}
                    {currentStepId === 'voicemail' && <Step2Voicemail form={form} update={update} />}
                    {currentStepId === 'welcome'   && <Step3Welcome   form={form} update={update} line={line} />}
                    {currentStepId === 'ai'           && <Step2AI           form={form} update={update} />}
                    {currentStepId === 'ai_knowledge' && <Step3AiKnowledge form={form} update={update} line={line} />}
                    {currentStepId === 'notif'     && <Step4Notif     form={form} update={update} />}

                    <div style={{ marginTop: '2rem', display: 'flex', justifyContent: 'space-between', gap: '0.75rem' }}>
                        <button className="btn btn-secondary btn-sm" onClick={() => step === 1 ? onCancel?.() : setStep(s => s - 1)} disabled={saving}>
                            <Icons.ArrowLeft className="w-4 h-4" /> {step === 1 ? 'Annuler' : 'Précédent'}
                        </button>
                        {step < totalSteps ? (
                            <button className="btn btn-primary btn-sm" disabled={!canNext()} onClick={() => setStep(s => s + 1)}>
                                Continuer <Icons.ChevronRight className="w-4 h-4" />
                            </button>
                        ) : (
                            <button className="btn btn-primary btn-sm" disabled={!canNext() || saving} onClick={save}>
                                {saving ? 'Enregistrement…' : <>Enregistrer <Icons.Check className="w-4 h-4" /></>}
                            </button>
                        )}
                    </div>
                </div>
                <Preview />
            </div>
        </div>
    );
};

// Trouve le libellé court d'une voix Aura-2 (ex. "aura-2-agathe-fr" → "Agathe")
const voiceShortLabel = (id) => {
    if (!id) return 'voix par défaut';
    try {
        for (const grp of TTS_VOICES_ALL) {
            const v = (grp.voices || []).find(x => x.id === id);
            if (v) return (v.label || '').replace(/\s*\(.+\)\s*$/, '').trim() || id;
        }
    } catch (_) {}
    return id;
};

const Bubble = ({ icon, color, title, text }) => {
    const I = (Icons[{
        speaker: 'Mic', forward: 'Phone', voicemail: 'Chat',
        mail: 'Send', sparkles: 'Sparkles', globe: 'Globe',
    }[icon] || 'Phone']) || Icons.Phone;
    return (
        <div style={{ display: 'flex', gap: '0.65rem', alignItems: 'flex-start' }}>
            <div style={{ width: 28, height: 28, borderRadius: 8, background: color + '22', color, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
                <I className="w-4 h-4" />
            </div>
            <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontWeight: 600, fontSize: '0.8rem' }}>{title}</div>
                <div style={{ fontSize: '0.75rem', color: 'var(--text-muted)', wordBreak: 'break-word' }}>
                    {text && text.length > 120 ? text.slice(0, 120) + '…' : (text || '—')}
                </div>
            </div>
        </div>
    );
};

const FAMILY_OPTIONS = [
    {
        id: 'transfer',
        label: 'Transférer l\'appel sans message d\'accueil',
        desc: 'L\'appel sonne directement sur un ou plusieurs téléphones, sans annonce préalable.',
        color: '#0ea5e9',
        icon: 'Phone',
    },
    {
        id: 'transfer_welcome',
        label: 'Transférer l\'appel après message d\'accueil',
        desc: 'Une phrase d\'accueil est diffusée, puis l\'appel est transféré.',
        color: '#10b981',
        icon: 'Mic',
    },
    {
        id: 'voicemail',
        label: 'Messagerie vocale simple',
        desc: 'Une annonce diffusée à vos appelants, puis vous recevez le message par email.',
        color: '#6366f1',
        icon: 'MessageSquare',
    },
    {
        id: 'ai',
        label: 'L\'IA intelligente répond à votre place',
        desc: 'Un agent vocal converse, prend les messages, RDV ou réservations 24h/24.',
        color: '#ec4899',
        icon: 'Sparkles',
    },
];

const Step0Family = ({ form, update }) => {
    const choose = (f) => {
        update('family', f);
        // Pré-positionne le type final + active/désactive l'accueil selon la famille.
        if (f === 'voicemail') update('type', 'voicemail');
        else if (f === 'ai')   update('type', 'voice_ai');
        else if (f === 'transfer' || f === 'transfer_welcome') {
            if (!['forward', 'forward_voicemail', 'simulring', 'ivr'].includes(form.type)) {
                update('type', 'forward');
            }
            update('welcome_enabled', f === 'transfer_welcome');
        }
    };
    return (
        <div>
            <h2 style={{ margin: '0 0 0.35rem', fontSize: '1.15rem' }}>Comment accueillir vos clients ?</h2>
            <p style={{ margin: '0 0 1.25rem', color: 'var(--text-muted)', fontSize: '0.85rem' }}>
                Choisissez la grande famille — vous affinerez ensuite si nécessaire.
            </p>
            <div style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
                {FAMILY_OPTIONS.map(f => {
                    const active = form.family === f.id;
                    const I = Icons[f.icon] || Icons.Phone;
                    return (
                        <button key={f.id} type="button" onClick={() => choose(f.id)} style={{
                            textAlign: 'left', padding: '1.1rem 1.15rem',
                            border: `2px solid ${active ? f.color : 'var(--border)'}`,
                            background: active ? f.color + '0d' : '#fff',
                            borderRadius: 14, cursor: 'pointer',
                            display: 'flex', gap: '1rem', alignItems: 'center', transition: 'all 150ms',
                        }}>
                            <div style={{
                                width: 48, height: 48, borderRadius: 12,
                                background: f.color + '22', color: f.color,
                                display: 'flex', alignItems: 'center', justifyContent: 'center',
                                flexShrink: 0,
                            }}><I className="w-6 h-6" /></div>
                            <div style={{ flex: 1 }}>
                                <div style={{ fontWeight: 700, fontSize: '1rem' }}>{f.label}</div>
                                <div style={{ fontSize: '0.8rem', color: 'var(--text-muted)', marginTop: 2 }}>{f.desc}</div>
                            </div>
                            {active && <Icons.Check className="w-5 h-5" style={{ color: f.color }} />}
                        </button>
                    );
                })}
            </div>
        </div>
    );
};

const Step1Type = ({ form, update }) => (
    <div>
        <h2 style={{ margin: '0 0 0.35rem', fontSize: '1.05rem' }}>Comment doit se passer le transfert ?</h2>
        <p style={{ margin: '0 0 1.25rem', color: 'var(--text-muted)', fontSize: '0.85rem' }}>4 façons de gérer le transfert vers vos téléphones.</p>
        <div style={{ display: 'flex', flexDirection: 'column', gap: '0.65rem' }}>
            {EASY_TYPES.filter(t => ['forward', 'forward_voicemail', 'simulring', 'ivr'].includes(t.id)).map(t => {
                const active = form.type === t.id;
                return (
                    <button key={t.id} type="button" onClick={() => update('type', t.id)} style={{
                        textAlign: 'left', padding: '1rem', border: `2px solid ${active ? t.color : 'var(--border)'}`,
                        background: active ? t.color + '0d' : '#fff', borderRadius: 12, cursor: 'pointer',
                        display: 'flex', gap: '0.85rem', alignItems: 'center', transition: 'all 150ms',
                    }}>
                        <div style={{ width: 40, height: 40, borderRadius: 10, background: t.color + '22', color: t.color, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                            <Icons.Phone className="w-5 h-5" />
                        </div>
                        <div style={{ flex: 1 }}>
                            <div style={{ fontWeight: 700, fontSize: '0.95rem' }}>{t.label}</div>
                            <div style={{ fontSize: '0.78rem', color: 'var(--text-muted)' }}>{t.desc}</div>
                        </div>
                        {active && <Icons.Check className="w-5 h-5" style={{ color: t.color }} />}
                    </button>
                );
            })}
        </div>
    </div>
);

const Step2Forward = ({ form, update }) => {
    const phoneInputStyle = { fontFamily: 'ui-monospace, monospace', fontSize: '1.05rem', fontWeight: 600 };

    if (form.type === 'simulring') {
        // Liste de numéros sonnant en parallèle (forward_number + fallback_1/2/3)
        const slots = [
            { key: 'forward_number',    label: 'Numéro principal' },
            { key: 'fallback_number_1', label: '2ème numéro' },
            { key: 'fallback_number_2', label: '3ème numéro (optionnel)' },
            { key: 'fallback_number_3', label: '4ème numéro (optionnel)' },
        ];
        return (
            <div>
                <h2 style={{ margin: '0 0 0.35rem', fontSize: '1.05rem' }}>Quels numéros doivent sonner en même temps ?</h2>
                <p style={{ margin: '0 0 1.25rem', color: 'var(--text-muted)', fontSize: '0.85rem' }}>
                    Tous ces numéros sonneront simultanément. Le premier à décrocher prendra l'appel, les autres s'arrêteront.
                </p>
                <div style={{ display: 'flex', flexDirection: 'column', gap: '0.65rem' }}>
                    {slots.map(s => (
                        <div key={s.key}>
                            <label className="form-label" style={{ fontSize: '0.72rem', fontWeight: 600, marginBottom: '0.3rem', display: 'block', color: 'var(--text-muted)' }}>
                                {s.label}
                            </label>
                            <input type="tel" className="form-input" placeholder="+41 79 123 45 67"
                                value={form[s.key] || ''} onChange={e => update(s.key, e.target.value)}
                                style={phoneInputStyle} />
                        </div>
                    ))}
                </div>
            </div>
        );
    }

    // Mode 'forward' ou 'forward_voicemail' : numéro principal + option de fallback (sauf si déjà voicemail)
    const isVm = form.type === 'forward_voicemail';
    const mode = form.transfer_mode || (form.sip_pbx_url || form.sip_extension ? 'sip' : 'phone');
    return (
        <div>
            <h2 style={{ margin: '0 0 0.35rem', fontSize: '1.05rem' }}>Sur quel numéro transférer les appels ?</h2>

            {/* Sélecteur mode : Téléphone classique vs Standard Wildix (SIP) */}
            <div style={{ display: 'flex', gap: '0.5rem', marginBottom: '1rem' }}>
                {[
                    { id: 'phone', label: 'Téléphone',         desc: 'Mobile ou fixe (E.164)' },
                    { id: 'sip',   label: 'Standard Wildix',   desc: 'Transfert SIP vers PBX' },
                ].map(opt => {
                    const active = mode === opt.id;
                    return (
                        <button key={opt.id} type="button" onClick={() => update('transfer_mode', opt.id)} style={{
                            flex: 1, padding: '0.75rem 0.9rem', textAlign: 'left',
                            border: `2px solid ${active ? 'var(--primary)' : 'var(--border)'}`,
                            background: active ? 'rgba(99,102,241,0.06)' : '#fff',
                            borderRadius: 10, cursor: 'pointer',
                        }}>
                            <div style={{ fontWeight: 700, fontSize: '0.88rem' }}>{opt.label}</div>
                            <div style={{ fontSize: '0.74rem', color: 'var(--text-muted)' }}>{opt.desc}</div>
                        </button>
                    );
                })}
            </div>

            {mode === 'sip' ? (
                <div style={{ display: 'flex', flexDirection: 'column', gap: '0.65rem' }}>
                    <div>
                        <label className="form-label" style={{ fontSize: '0.72rem', fontWeight: 600, marginBottom: '0.3rem', display: 'block', color: 'var(--text-muted)' }}>
                            URL / domaine du PBX Wildix
                        </label>
                        <input type="text" className="form-input" placeholder="pbx.client.ch"
                            value={form.sip_pbx_url || ''} onChange={e => update('sip_pbx_url', e.target.value)}
                            style={{ fontFamily: 'ui-monospace, monospace', fontSize: '0.95rem' }} />
                    </div>
                    <div>
                        <label className="form-label" style={{ fontSize: '0.72rem', fontWeight: 600, marginBottom: '0.3rem', display: 'block', color: 'var(--text-muted)' }}>
                            Numéro interne / extension
                        </label>
                        <input type="text" className="form-input" placeholder="101"
                            value={form.sip_extension || ''} onChange={e => update('sip_extension', e.target.value)}
                            style={{ fontFamily: 'ui-monospace, monospace', fontSize: '1rem', fontWeight: 600 }} />
                        <div style={{ fontSize: '0.72rem', color: 'var(--text-muted)', marginTop: '0.3rem' }}>
                            URI SIP générée : <code>sip:{form.sip_extension || '101'}@{form.sip_pbx_url || 'pbx.client.ch'};transport=tls</code>
                        </div>
                    </div>
                    <details style={{ marginTop: '0.25rem' }}>
                        <summary style={{ cursor: 'pointer', fontSize: '0.8rem', color: 'var(--text-muted)' }}>Options avancées (auth + transport)</summary>
                        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '0.5rem', marginTop: '0.5rem' }}>
                            <div>
                                <label className="form-label" style={{ fontSize: '0.7rem', fontWeight: 600 }}>Transport</label>
                                <select className="form-input" value={form.sip_transport || 'tls'}
                                    onChange={e => update('sip_transport', e.target.value)}>
                                    <option value="tls">TLS (sécurisé)</option>
                                    <option value="tcp">TCP</option>
                                    <option value="udp">UDP</option>
                                </select>
                            </div>
                            <div>
                                <label className="form-label" style={{ fontSize: '0.7rem', fontWeight: 600 }}>Login SIP (optionnel)</label>
                                <input type="text" className="form-input" placeholder="vide si IP whitelist"
                                    value={form.sip_username || ''} onChange={e => update('sip_username', e.target.value)} />
                            </div>
                            <div>
                                <label className="form-label" style={{ fontSize: '0.7rem', fontWeight: 600 }}>Mot de passe</label>
                                <input type="password" className="form-input" placeholder="••••••••"
                                    value={form.sip_password || ''} onChange={e => update('sip_password', e.target.value)} />
                            </div>
                        </div>
                    </details>
                </div>
            ) : (
                <>
                    <p style={{ margin: '0 0 0.75rem', color: 'var(--text-muted)', fontSize: '0.85rem' }}>Format international avec indicatif (ex. +41 79 123 45 67).</p>
                    <input type="tel" className="form-input" placeholder="+41 79 123 45 67"
                        value={form.forward_number} onChange={e => update('forward_number', e.target.value)}
                        style={{ ...phoneInputStyle, fontSize: '1.1rem' }} />
                </>
            )}

            {!isVm && mode === 'phone' && (
                <div style={{
                    marginTop: '1.25rem', padding: '1rem 1.15rem', borderRadius: 12,
                    border: `1px solid ${form.fallback_number_1 ? 'var(--vocal-primary, #6366f1)' : 'var(--border)'}`,
                    background: form.fallback_number_1 ? 'rgba(99,102,241,0.04)' : '#fafbfc',
                }}>
                    <div style={{ fontWeight: 600, fontSize: '0.9rem', marginBottom: '0.25rem' }}>
                        Si vous ne répondez pas, transférer l'appel sur
                        <span style={{ display: 'block', fontSize: '0.72rem', color: 'var(--text-muted)', fontWeight: 400, marginTop: 2 }}>
                            Optionnel — un second numéro est essayé si le premier ne décroche pas.
                        </span>
                    </div>
                    <input type="tel" className="form-input" placeholder="+41 ... (laisser vide pour désactiver)"
                        value={form.fallback_number_1 || ''} onChange={e => update('fallback_number_1', e.target.value)}
                        style={{ ...phoneInputStyle, marginTop: '0.5rem' }} />
                    {form.fallback_number_1 && (
                        <>
                            <input type="tel" className="form-input" placeholder="Puis (optionnel)"
                                value={form.fallback_number_2 || ''} onChange={e => update('fallback_number_2', e.target.value)}
                                style={{ ...phoneInputStyle, marginTop: '0.45rem', fontSize: '0.9rem' }} />
                            {form.fallback_number_2 && (
                                <input type="tel" className="form-input" placeholder="Puis (optionnel)"
                                    value={form.fallback_number_3 || ''} onChange={e => update('fallback_number_3', e.target.value)}
                                    style={{ ...phoneInputStyle, marginTop: '0.45rem', fontSize: '0.9rem' }} />
                            )}
                        </>
                    )}
                </div>
            )}
        </div>
    );
};

// Voix Aura-2 vérifiées (liste officielle Deepgram 05/2026).
const TTS_VOICES_ALL = [
    { group: '🇫🇷 Français', voices: [
        { id: 'aura-2-agathe-fr', label: 'Agathe (F)' },
        { id: 'aura-2-hector-fr', label: 'Hector (M)' },
    ]},
    { group: '🇬🇧 English', voices: [
        { id: 'aura-2-thalia-en',    label: 'Thalia (F, US)' },
        { id: 'aura-2-asteria-en',   label: 'Asteria (F, US)' },
        { id: 'aura-2-luna-en',      label: 'Luna (F, US)' },
        { id: 'aura-2-andromeda-en', label: 'Andromeda (F, US)' },
        { id: 'aura-2-orion-en',     label: 'Orion (M, US)' },
        { id: 'aura-2-arcas-en',     label: 'Arcas (M, US)' },
        { id: 'aura-2-zeus-en',      label: 'Zeus (M, US)' },
        { id: 'aura-2-pandora-en',   label: 'Pandora (F, UK)' },
        { id: 'aura-2-draco-en',     label: 'Draco (M, UK)' },
    ]},
    { group: '🇪🇸 Español', voices: [
        { id: 'aura-2-celeste-es',  label: 'Celeste (F, CO)' },
        { id: 'aura-2-estrella-es', label: 'Estrella (F, MX)' },
        { id: 'aura-2-silvia-es',   label: 'Silvia (F, ES)' },
        { id: 'aura-2-nestor-es',   label: 'Nestor (M, ES)' },
        { id: 'aura-2-alvaro-es',   label: 'Alvaro (M, ES)' },
        { id: 'aura-2-javier-es',   label: 'Javier (M, MX)' },
    ]},
    { group: '🇩🇪 Deutsch', voices: [
        { id: 'aura-2-aurelia-de',  label: 'Aurelia (F)' },
        { id: 'aura-2-elara-de',    label: 'Elara (F)' },
        { id: 'aura-2-lara-de',     label: 'Lara (F)' },
        { id: 'aura-2-viktoria-de', label: 'Viktoria (F)' },
        { id: 'aura-2-julius-de',   label: 'Julius (M)' },
        { id: 'aura-2-fabian-de',   label: 'Fabian (M)' },
    ]},
    { group: '🇮🇹 Italiano', voices: [
        { id: 'aura-2-cinzia-it',   label: 'Cinzia (F)' },
        { id: 'aura-2-livia-it',    label: 'Livia (F)' },
        { id: 'aura-2-maia-it',     label: 'Maia (F)' },
        { id: 'aura-2-cesare-it',   label: 'Cesare (M)' },
        { id: 'aura-2-flavio-it',   label: 'Flavio (M)' },
        { id: 'aura-2-dionisio-it', label: 'Dionisio (M)' },
    ]},
    { group: '🇳🇱 Nederlands', voices: [
        { id: 'aura-2-beatrix-nl',  label: 'Beatrix (F)' },
        { id: 'aura-2-daphne-nl',   label: 'Daphne (F)' },
        { id: 'aura-2-sander-nl',   label: 'Sander (M)' },
    ]},
    { group: '🇯🇵 日本語', voices: [
        { id: 'aura-2-izanami-ja',  label: 'Izanami (F)' },
        { id: 'aura-2-uzume-ja',    label: 'Uzume (F)' },
        { id: 'aura-2-ebisu-ja',    label: 'Ebisu (M)' },
        { id: 'aura-2-fujin-ja',    label: 'Fujin (M)' },
    ]},
];

const TtsVoicePicker = ({ value, onChange, text, disabled = false }) => {
    const { notify } = useApp();
    const [loading, setLoading] = useState(false);
    const [playing, setPlaying] = useState(false);
    const audioRef = React.useRef(null);
    const objectUrlRef = React.useRef(null);

    const unmountingRef = React.useRef(false);

    React.useEffect(() => () => {
        unmountingRef.current = true;
        if (audioRef.current) {
            audioRef.current.onerror = null;
            audioRef.current.onended = null;
            audioRef.current.pause();
            audioRef.current.src = '';
        }
        if (objectUrlRef.current) URL.revokeObjectURL(objectUrlRef.current);
    }, []);

    const stop = () => {
        if (audioRef.current) {
            audioRef.current.onerror = null;
            audioRef.current.pause();
            audioRef.current.currentTime = 0;
        }
        setPlaying(false);
    };

    const preview = async () => {
        const t = String(text || '').trim();
        if (!t) return;
        if (playing) { stop(); return; }
        setLoading(true);
        try {
            const r = await fetch(`${CONFIG.API_URL}/api/tts/preview`, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${api.token}` },
                body: JSON.stringify({ text: t, voice: value }),
            });
            if (!r.ok) {
                let msg = `HTTP ${r.status}`;
                try { const j = await r.json(); msg = j.error || j.detail || msg; } catch (_) {}
                notify.error('Génération audio échouée : ' + msg);
                setLoading(false);
                return;
            }
            const blob = await r.blob();
            if (objectUrlRef.current) URL.revokeObjectURL(objectUrlRef.current);
            const url = URL.createObjectURL(blob);
            objectUrlRef.current = url;
            const a = new Audio(url);
            audioRef.current = a;
            a.onended = () => setPlaying(false);
            a.onerror = () => {
                setPlaying(false);
                // Ignore les erreurs causées par le démontage du composant (changement d'étape, etc.)
                if (unmountingRef.current) return;
                notify.error('Lecture audio impossible');
            };
            setLoading(false);
            setPlaying(true);
            await a.play();
        } catch (e) {
            setLoading(false);
            setPlaying(false);
            notify.error('Erreur : ' + (e.message || 'inconnue'));
        }
    };

    return (
        <>
            <div style={{ display: 'flex', gap: '0.5rem', alignItems: 'stretch' }}>
                <select className="form-input" value={value} onChange={e => onChange(e.target.value)} disabled={disabled || loading} style={{ flex: 1 }}>
                    {TTS_VOICES_ALL.map(g => (
                        <optgroup key={g.group} label={g.group}>
                            {g.voices.map(v => <option key={v.id} value={v.id}>{v.label}</option>)}
                        </optgroup>
                    ))}
                </select>
                <button type="button" className="btn btn-secondary" onClick={preview}
                    disabled={disabled || loading || !String(text || '').trim()}
                    title={!String(text || '').trim() ? 'Saisissez un texte pour écouter' : (playing ? 'Stopper' : 'Écouter cette voix')}
                    style={{ whiteSpace: 'nowrap', minWidth: 120, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: '0.4rem' }}>
                    {loading ? 'Génération…' : (playing ? <><Icons.Stop className="w-4 h-4" /> Stop</> : <><Icons.Volume className="w-4 h-4" /> Écouter</>)}
                </button>
            </div>
            {loading && (
                <div style={{
                    position: 'fixed', inset: 0, background: 'rgba(15, 23, 42, 0.55)', backdropFilter: 'blur(4px)',
                    display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 9999,
                }}>
                    <div style={{
                        background: '#fff', padding: '2rem 2.25rem', borderRadius: 14, boxShadow: '0 24px 64px rgba(0,0,0,0.25)',
                        textAlign: 'center', maxWidth: 360,
                    }}>
                        <div style={{
                            width: 48, height: 48, border: '4px solid #e2e8f0', borderTopColor: 'var(--primary)',
                            borderRadius: '50%', animation: 'spin 0.9s linear infinite', margin: '0 auto 1rem',
                        }} />
                        <div style={{ fontWeight: 700, fontSize: '1rem', marginBottom: '0.35rem' }}>Génération de la voix…</div>
                        <div style={{ fontSize: '0.85rem', color: 'var(--text-muted)' }}>Synthèse vocale en cours, quelques secondes.</div>
                    </div>
                    <style>{`@keyframes spin{to{transform:rotate(360deg)}}`}</style>
                </div>
            )}
        </>
    );
};

const Step2Voicemail = ({ form, update }) => (
    <div>
        <h2 style={{ margin: '0 0 0.35rem', fontSize: '1.05rem' }}>Que dira votre messagerie ?</h2>
        <p style={{ margin: '0 0 0.5rem', color: 'var(--text-muted)', fontSize: '0.85rem' }}>
            Court et clair, c'est mieux. Mentionnez de laisser le nom et le sujet du message.
        </p>
        <AudioSourcePicker
            sourceKey="voicemail_audio_source"
            urlKey="voicemail_uploaded_url"
            textKey="voicemail_greeting"
            voiceKey="voicemail_voice"
            defaultVoice="aura-2-agathe-fr"
            textRows={4}
            textPlaceholder="Bonjour, vous êtes bien sur la messagerie de…"
            helperText="Cliquez sur Écouter pour entendre l'annonce. Le fichier audio sera généré à la validation."
            form={form} update={update}
        />
    </div>
);

const CAPTURE_MODES = [
    { id: '',                          label: 'Conversation libre',         hint: 'Pas de collecte structurée.' },
    { id: 'appointment',               label: 'Prise de rendez-vous',       hint: 'Nom, motif, date, heure, contact.' },
    { id: 'reservation_taxi',          label: 'Réservation taxi / transfert', hint: 'Adresses (avec CP), date, véhicule, options.' },
    { id: 'reservation_restaurant',    label: 'Réservation restaurant',     hint: 'Nombre, date, heure, contact.' },
];

const Step2AI = ({ form, update }) => {
    const showEstabName = form.ai_capture_mode === 'reservation_restaurant' || form.ai_capture_mode === 'reservation_taxi';
    return (
    <div>
        <h2 style={{ margin: '0 0 0.35rem', fontSize: '1.05rem' }}>Comment l'agent IA doit-il accueillir vos appels ?</h2>
        <p style={{ margin: '0 0 1.25rem', color: 'var(--text-muted)', fontSize: '0.85rem' }}>
            La voix utilisée est <strong>Agathe (français)</strong>. L'agent comprendra et basculera automatiquement vers l'anglais, l'allemand, l'espagnol, etc. si l'appelant le demande.
        </p>

        <label className="form-label" style={{ fontSize: '0.75rem' }}>
            Prénom de l'agent <span style={{ color: 'var(--text-muted)', fontWeight: 400 }}>(optionnel)</span>
        </label>
        <input className="form-input" type="text" maxLength={40}
            placeholder="Ex. Sabrina"
            value={form.ai_name}
            onChange={e => update('ai_name', e.target.value)}
            style={{ marginBottom: '0.75rem' }} />
        <p style={{ margin: '-0.4rem 0 1rem', color: 'var(--text-muted)', fontSize: '0.72rem' }}>
            Si renseigné, l'agent répondra qu'il s'appelle ainsi (ex. « Bonjour, je suis Sabrina »). Pensez à l'inclure aussi dans votre phrase d'accueil.
        </p>

        <label className="form-label" style={{ fontSize: '0.75rem' }}>Phrase d'accueil</label>
        <textarea className="form-input" rows={3}
            value={form.ai_greeting} onChange={e => update('ai_greeting', e.target.value)} />

        <label className="form-label" style={{ fontSize: '0.75rem', marginTop: '1rem' }}>
            Que doit faire l'agent ?
        </label>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(180px, 1fr))', gap: '0.5rem', marginTop: '0.35rem' }}>
            {CAPTURE_MODES.map(m => {
                const active = (form.ai_capture_mode || '') === m.id;
                return (
                    <button key={m.id || 'none'} type="button"
                        onClick={() => update('ai_capture_mode', m.id)}
                        style={{
                            textAlign: 'left', padding: '0.65rem 0.75rem',
                            border: `1px solid ${active ? 'var(--vocal-primary, #6366f1)' : 'var(--border)'}`,
                            background: active ? 'rgba(99,102,241,0.08)' : 'transparent',
                            borderRadius: 10, cursor: 'pointer',
                        }}>
                        <div style={{ fontWeight: 600, fontSize: '0.85rem' }}>{m.label}</div>
                        <div style={{ fontSize: '0.72rem', color: 'var(--text-muted)', marginTop: 2 }}>{m.hint}</div>
                    </button>
                );
            })}
        </div>

        {showEstabName && (
            <div style={{ marginTop: '0.85rem' }}>
                <label className="form-label" style={{ fontSize: '0.75rem' }}>
                    Nom de l'établissement <span style={{ color: 'var(--text-muted)', fontWeight: 400 }}>(utilisé par l'agent)</span>
                </label>
                <input className="form-input" type="text"
                    placeholder={form.ai_capture_mode === 'reservation_restaurant' ? 'Ex. Mr. Pickwick' : 'Ex. Genève Taxi 27'}
                    value={form.ai_capture_extra_name}
                    onChange={e => update('ai_capture_extra_name', e.target.value)} />
            </div>
        )}

        {form.ai_capture_mode && (
            <div style={{ marginTop: '0.85rem' }}>
                <label className="form-label" style={{ fontSize: '0.75rem' }}>
                    Email destinataire des récapitulatifs <span style={{ color: 'var(--text-muted)', fontWeight: 400 }}>(optionnel — défaut : email de notification)</span>
                </label>
                <input className="form-input" type="email"
                    placeholder="reservations@mon-restaurant.ch"
                    value={form.ai_capture_email}
                    onChange={e => update('ai_capture_email', e.target.value)} />
            </div>
        )}

        <label className="form-label" style={{ fontSize: '0.75rem', marginTop: '1rem' }}>
            Consignes pour l'agent <span style={{ color: 'var(--text-muted)', fontWeight: 400 }}>(optionnel)</span>
        </label>
        <textarea className="form-input" rows={4}
            placeholder="Ex. : Tu es l'assistant de notre cabinet d'avocats. Réponds brièvement, propose un RDV si pertinent."
            value={form.voice_ai_prompt} onChange={e => update('voice_ai_prompt', e.target.value)} />

        {/* Choix du moteur vocal (vDeepgram | vEleven) */}
        <label className="form-label" style={{ fontSize: '0.75rem', marginTop: '1.25rem' }}>Moteur vocal</label>
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '0.5rem', marginTop: '0.35rem' }}>
            {[
                { id: 'vDeepgram', label: 'vDeepgram', sub: 'Rapide · EN, FR, ES, DE, IT, NL, JA', badge: 'Recommandé' },
                { id: 'vEleven',   label: 'vEleven',   sub: 'Multilingue 30+ langues (PT, RU, AR…)', badge: 'Beta' },
            ].map(opt => {
                const engine = form.voice_ai_engine || 'vDeepgram';
                const active = engine === opt.id;
                return (
                    <button key={opt.id} type="button" onClick={() => update('voice_ai_engine', opt.id)}
                        style={{
                            textAlign: 'left', padding: '0.7rem 0.8rem',
                            border: `1px solid ${active ? 'var(--vocal-primary, #6366f1)' : 'var(--border)'}`,
                            background: active ? 'rgba(99,102,241,0.08)' : 'transparent',
                            borderRadius: 10, cursor: 'pointer', position: 'relative',
                        }}>
                        <div style={{ fontWeight: 700, fontSize: '0.88rem', display: 'flex', alignItems: 'center', gap: '0.4rem' }}>
                            {opt.label}
                            <span style={{
                                fontSize: '0.65rem', fontWeight: 700, padding: '0.1rem 0.4rem', borderRadius: 4,
                                background: opt.id === 'vDeepgram' ? '#dcfce7' : '#fef3c7',
                                color: opt.id === 'vDeepgram' ? '#166534' : '#92400e',
                            }}>{opt.badge}</span>
                        </div>
                        <div style={{ fontSize: '0.72rem', color: 'var(--text-muted)', marginTop: 2 }}>{opt.sub}</div>
                    </button>
                );
            })}
        </div>

        {form.voice_ai_engine === 'vEleven' && (
            <ElevenLanguageVoices form={form} update={update} />
        )}
    </div>
    );
};

// =====================================================================
// Sélecteur multi-langues + voice_id par langue pour vEleven.
// L'utilisateur coche les langues qu'il veut activer pour cette ligne ;
// pour chaque langue active il peut choisir une voix ElevenLabs parmi
// les presets recommandés, ou coller un voice_id custom.
// Le bot vocal choisira automatiquement la voix selon la langue
// détectée par le STT Deepgram à chaque tour de parole.
// =====================================================================
const ELEVEN_VOICE_PRESETS = {
    fr: [
        { id: 'WeAAwKYcS06VmXw086yZ', label: 'Recommandée FR — féminine douce' },
    ],
    en: [
        { id: 'yj30vwTGJxSHezdAGsv9', label: 'Recommandée EN' },
        { id: 'pNInz6obpgDQGcFmaJgB', label: 'Adam — masculine multilingue' },
    ],
    es: [
        { id: 'br0MPoLVxuslVxf61qHn', label: 'Recommandée ES' },
    ],
    pt: [
        { id: 'cyD08lEy76q03ER1jZ7y', label: 'Recommandée PT' },
    ],
    de: [
        { id: 'KbSC2XTZL12xT3fm2fcD', label: 'Recommandée DE' },
    ],
    it: [],
    nl: [],
    ar: [],
    ru: [],
    ja: [],
    zh: [],
};
const ALL_LANGS = [
    { code: 'fr', label: 'Français', flag: '🇫🇷' },
    { code: 'en', label: 'English',  flag: '🇬🇧' },
    { code: 'es', label: 'Español',  flag: '🇪🇸' },
    { code: 'pt', label: 'Português', flag: '🇵🇹' },
    { code: 'de', label: 'Deutsch',  flag: '🇩🇪' },
    { code: 'it', label: 'Italiano', flag: '🇮🇹' },
    { code: 'nl', label: 'Nederlands', flag: '🇳🇱' },
    { code: 'ar', label: 'العربية',  flag: '🇦🇪' },
    { code: 'ru', label: 'Русский',  flag: '🇷🇺' },
    { code: 'ja', label: '日本語',    flag: '🇯🇵' },
    { code: 'zh', label: '中文',      flag: '🇨🇳' },
];

const ElevenLanguageVoices = ({ form, update }) => {
    // form.voice_ai_languages : array ou JSON string. form.voice_ai_eleven_voices : objet ou JSON string.
    const parseList = (v) => {
        if (Array.isArray(v)) return v;
        if (typeof v === 'string' && v.trim()) { try { return JSON.parse(v) || []; } catch { return []; } }
        return [];
    };
    const parseMap = (v) => {
        if (v && typeof v === 'object' && !Array.isArray(v)) return v;
        if (typeof v === 'string' && v.trim()) { try { return JSON.parse(v) || {}; } catch { return {}; } }
        return {};
    };
    const activeLangs = parseList(form.voice_ai_languages);
    const voicesMap   = parseMap(form.voice_ai_eleven_voices);

    const toggleLang = (code) => {
        const set = new Set(activeLangs);
        if (set.has(code)) set.delete(code);
        else set.add(code);
        const next = ALL_LANGS.filter(l => set.has(l.code)).map(l => l.code);
        update('voice_ai_languages', next);
        // Si on coche une langue et qu'elle a un preset, on pré-remplit la voix.
        if (!voicesMap[code] && ELEVEN_VOICE_PRESETS[code]?.[0]) {
            const nextMap = { ...voicesMap, [code]: ELEVEN_VOICE_PRESETS[code][0].id };
            update('voice_ai_eleven_voices', nextMap);
        }
    };
    const setVoice = (code, voiceId) => {
        const nextMap = { ...voicesMap, [code]: voiceId };
        update('voice_ai_eleven_voices', nextMap);
        // La toute première langue active devient aussi la voix par défaut globale.
        if (!form.voice_ai_elevenlabs_voice_id && voiceId) {
            update('voice_ai_elevenlabs_voice_id', voiceId);
        }
    };

    return (
        <div style={{ marginTop: '0.85rem' }}>
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '0.4rem' }}>
                <label className="form-label" style={{ fontSize: '0.75rem', margin: 0 }}>
                    Langues actives & voix ElevenLabs
                </label>
                <span style={{ fontSize: '0.7rem', color: 'var(--text-muted)' }}>
                    {activeLangs.length} langue{activeLangs.length > 1 ? 's' : ''} active{activeLangs.length > 1 ? 's' : ''}
                </span>
            </div>

            <p style={{ margin: '0 0 0.55rem', fontSize: '0.72rem', color: 'var(--text-muted)' }}>
                Le bot détecte automatiquement la langue de l'appelant et répond avec la voix associée.
            </p>

            <div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.35rem', marginBottom: '0.7rem' }}>
                {ALL_LANGS.map(l => {
                    const on = activeLangs.includes(l.code);
                    return (
                        <button key={l.code} type="button" onClick={() => toggleLang(l.code)}
                            style={{
                                padding: '0.4rem 0.7rem',
                                border: `1px solid ${on ? 'var(--vocal-primary, #6366f1)' : 'var(--border)'}`,
                                background: on ? 'rgba(99,102,241,0.12)' : 'transparent',
                                borderRadius: 20,
                                fontSize: '0.78rem',
                                fontWeight: on ? 600 : 400,
                                cursor: 'pointer',
                                display: 'inline-flex',
                                alignItems: 'center',
                                gap: '0.35rem',
                            }}>
                            <span>{l.flag}</span>
                            <span>{l.label}</span>
                        </button>
                    );
                })}
            </div>

            {activeLangs.length === 0 && (
                <div style={{ padding: '0.7rem 0.9rem', background: '#fef3c7', borderRadius: 8, fontSize: '0.78rem', color: '#92400e' }}>
                    Sélectionnez au moins une langue pour que l'agent vocal puisse répondre.
                </div>
            )}

            {activeLangs.map(code => {
                const lang = ALL_LANGS.find(l => l.code === code);
                const presets = ELEVEN_VOICE_PRESETS[code] || [];
                const currentId = voicesMap[code] || '';
                const isPreset = presets.some(p => p.id === currentId);
                return (
                    <div key={code} style={{ marginBottom: '0.65rem', padding: '0.65rem 0.75rem', border: '1px solid var(--border)', borderRadius: 10, background: 'rgba(99,102,241,0.03)' }}>
                        <div style={{ fontSize: '0.78rem', fontWeight: 600, marginBottom: '0.4rem', display: 'flex', alignItems: 'center', gap: '0.4rem' }}>
                            <span>{lang?.flag}</span> {lang?.label}
                        </div>
                        {presets.length > 0 && (
                            <select className="form-input" value={isPreset ? currentId : '__custom__'}
                                onChange={e => {
                                    const v = e.target.value;
                                    if (v === '__custom__') setVoice(code, '');
                                    else setVoice(code, v);
                                }}
                                style={{ fontSize: '0.8rem', marginBottom: '0.4rem' }}>
                                {presets.map(p => (
                                    <option key={p.id} value={p.id}>{p.label} ({p.id.slice(0, 8)}…)</option>
                                ))}
                                <option value="__custom__">Voice ID personnalisé…</option>
                            </select>
                        )}
                        {(!isPreset || presets.length === 0) && (
                            <input className="form-input" type="text"
                                placeholder="Voice ID ElevenLabs (ex. pNInz6obpgDQGcFmaJgB)"
                                value={currentId}
                                onChange={e => setVoice(code, e.target.value.trim())}
                                style={{ fontSize: '0.8rem' }} />
                        )}
                        <a href={`https://elevenlabs.io/app/voice-library${currentId ? '?voiceId=' + currentId : ''}`}
                            target="_blank" rel="noopener noreferrer"
                            style={{ fontSize: '0.7rem', color: 'var(--vocal-primary, #6366f1)', display: 'inline-block', marginTop: '0.35rem' }}>
                            Parcourir la bibliothèque ElevenLabs →
                        </a>
                    </div>
                );
            })}
        </div>
    );
};

// Étape « Savoir de l'IA » : un gros textarea où le client colle toutes les
// infos métier brutes (horaires, tarifs, services, FAQ, adresses, notes).
// Bouton « ✨ Optimiser » envoie le texte à l'API qui restructure via
// gpt-4o-mini (AI Gateway), puis remplace le contenu après confirmation.
const Step3AiKnowledge = ({ form, update, line }) => {
    const { notify } = useApp();
    const [optimizing, setOptimizing] = useState(false);
    const text = form.ai_knowledge || '';
    const chars = text.length;

    const optimize = async () => {
        if (!text.trim()) {
            notify.info('Collez d\'abord vos informations dans le champ.');
            return;
        }
        if (chars > 20000) {
            notify.error('Texte trop long (max 20 000 caractères).');
            return;
        }
        setOptimizing(true);
        try {
            const r = await api.post(`/api/lines/${line.id}/ai-knowledge/optimize`, { text });
            if (r?.error) {
                notify.error(r.error + (r.detail ? ` — ${r.detail}` : ''));
                return;
            }
            const optimized = (r?.optimized || '').trim();
            if (!optimized) {
                notify.error('Optimisation vide, contenu inchangé.');
                return;
            }
            update('ai_knowledge', optimized);
            notify.success('Savoir optimisé ✨');
        } catch (e) {
            notify.error('Optimisation échouée : ' + (e?.message || 'inconnue'));
        } finally {
            setOptimizing(false);
        }
    };

    return (
        <div>
            <h2 style={{ margin: '0 0 0.35rem', fontSize: '1.05rem' }}>Savoir de l'IA</h2>
            <p style={{ margin: '0 0 1rem', color: 'var(--text-muted)', fontSize: '0.85rem' }}>
                Collez ici toutes les informations que l'agent doit connaître : horaires, tarifs, adresses,
                services, FAQ, règles internes, etc. Pas besoin de structurer — utilisez le bouton{' '}
                <strong>✨ Optimiser</strong> pour que l'IA réorganise automatiquement.
            </p>

            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '0.35rem', gap: '0.5rem', flexWrap: 'wrap' }}>
                <label className="form-label" style={{ fontSize: '0.75rem', margin: 0 }}>
                    Informations métier <span style={{ color: 'var(--text-muted)', fontWeight: 400 }}>(optionnel)</span>
                </label>
                <div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
                    <span style={{ fontSize: '0.72rem', color: chars > 20000 ? '#dc2626' : 'var(--text-muted)' }}>
                        {chars.toLocaleString()} / 20 000
                    </span>
                    <button type="button" className="btn btn-secondary btn-sm" onClick={optimize}
                        disabled={optimizing || !text.trim()}
                        style={{ fontSize: '0.78rem' }}>
                        {optimizing ? '⏳ Optimisation…' : '✨ Optimiser'}
                    </button>
                </div>
            </div>

            <textarea className="form-input" rows={14}
                placeholder={`Exemple :

Restaurant Mr. Pickwick, rue de la Confédération 12, 1204 Genève
Ouvert lundi à samedi 11h-23h, fermé le dimanche
Tél réservations : 022 311 60 80
Spécialités : burgers, fish & chips, brunch dominical (suspendu)
Pas de menu enfant. Terrasse en été.
Karaoké tous les jeudis 21h, entrée libre
Carte cadeau dispo : 25.- / 50.- / 100.-`}
                value={text}
                onChange={e => update('ai_knowledge', e.target.value)}
                style={{ fontFamily: 'ui-monospace, monospace', fontSize: '0.85rem', lineHeight: 1.5 }} />

            <div style={{ marginTop: '0.75rem', padding: '0.65rem 0.85rem', background: 'rgba(99,102,241,0.06)', border: '1px solid rgba(99,102,241,0.18)', borderRadius: 8, fontSize: '0.78rem', color: 'var(--text-muted)' }}>
                <strong style={{ color: 'var(--text)' }}>💡 Astuce :</strong> Plus l'IA a d'infos précises, mieux elle répondra. Évitez les phrases marketing ; préférez les faits (chiffres, horaires, noms).
                Le bouton ✨ Optimiser restructure votre texte en sections claires sans rien inventer — vous pourrez relire avant d'enregistrer.
            </div>
        </div>
    );
};

// Petit composant réutilisable : choix source audio (TTS texte vs upload MP3)
// avec onglets, preview audio existant, bouton 🔊 Écouter pour TTS, upload R2.
const AudioSourcePicker = ({
    sourceKey, urlKey, textKey, voiceKey,
    form, update,
    defaultVoice = 'aura-2-agathe-fr',
    textRows = 3,
    textPlaceholder = '',
    helperText = '',
}) => {
    const { notify } = useApp();
    const [uploading, setUploading] = useState(false);
    const [dragOver, setDragOver] = useState(false);
    const source = form[sourceKey] || 'tts';
    const url = form[urlKey] || '';

    const uploadFile = async (file) => {
        if (!file) return;
        if (file.size > 10 * 1024 * 1024) {
            notify.error('Fichier trop volumineux (max 10 MB)');
            return;
        }
        const ext = (file.name.split('.').pop() || '').toLowerCase();
        if (!['mp3', 'wav', 'ogg', 'm4a'].includes(ext)) {
            notify.error('Format invalide (mp3, wav, ogg, m4a)');
            return;
        }
        setUploading(true);
        try {
            const fd = new FormData();
            fd.append('file', file);
            const data = await api.upload('/api/audio/upload', fd);
            if (!data || data.error || !data.url) {
                notify.error('Échec de l\'envoi : ' + (data?.error || 'inconnue'));
                return;
            }
            update(urlKey, data.url);
            notify.success('Audio envoyé');
        } catch (err) {
            notify.error('Erreur : ' + err.message);
        } finally {
            setUploading(false);
        }
    };

    const handleFile = (e) => uploadFile(e.target.files && e.target.files[0]);

    const handleDrop = (e) => {
        e.preventDefault();
        e.stopPropagation();
        setDragOver(false);
        if (uploading) return;
        const file = e.dataTransfer?.files && e.dataTransfer.files[0];
        if (file) uploadFile(file);
    };
    const handleDragOver = (e) => {
        e.preventDefault();
        e.stopPropagation();
        if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy';
        if (!uploading && !dragOver) setDragOver(true);
    };
    const handleDragLeave = (e) => {
        e.preventDefault();
        e.stopPropagation();
        setDragOver(false);
    };

    const tabBtn = (id, label) => (
        <button type="button" onClick={() => update(sourceKey, id)}
            style={{
                padding: '0.5rem 0.9rem',
                fontSize: '0.8rem', fontWeight: 600,
                border: `1px solid ${source === id ? 'var(--vocal-primary, #6366f1)' : 'var(--border)'}`,
                background: source === id ? 'rgba(99,102,241,0.08)' : '#fff',
                color: source === id ? 'var(--vocal-primary, #6366f1)' : 'var(--text)',
                borderRadius: 8, cursor: 'pointer',
                display: 'inline-flex', alignItems: 'center', gap: '0.4rem',
            }}>
            {label}
        </button>
    );

    return (
        <div style={{ marginTop: '0.5rem' }}>
            <div style={{ display: 'flex', gap: '0.5rem', marginBottom: '0.85rem' }}>
                {tabBtn('tts', <><Icons.Mic className="w-4 h-4" /> Générer une voix</>)}
                {tabBtn('upload', <><Icons.Upload className="w-4 h-4" /> Importer un MP3</>)}
            </div>

            {source === 'tts' && (
                <>
                    <textarea className="form-input" rows={textRows}
                        placeholder={textPlaceholder}
                        value={form[textKey] || ''}
                        onChange={e => update(textKey, e.target.value)} />
                    {voiceKey && (
                        <div style={{ marginTop: '0.6rem' }}>
                            <label className="form-label" style={{ fontSize: '0.72rem', fontWeight: 600, marginBottom: '0.3rem', display: 'block' }}>Voix</label>
                            <TtsVoicePicker value={form[voiceKey] || defaultVoice}
                                onChange={v => update(voiceKey, v)}
                                text={form[textKey] || ''} />
                        </div>
                    )}
                    {helperText && (
                        <small style={{ color: 'var(--text-muted)', fontSize: '0.72rem', display: 'block', marginTop: '0.4rem' }}>{helperText}</small>
                    )}
                </>
            )}

            {source === 'upload' && (
                <div
                    onDrop={handleDrop}
                    onDragOver={handleDragOver}
                    onDragEnter={handleDragOver}
                    onDragLeave={handleDragLeave}
                    style={{
                        border: `2px dashed ${dragOver ? 'var(--vocal-primary, #6366f1)' : 'var(--border)'}`,
                        borderRadius: 12, padding: '1.5rem 1.25rem',
                        textAlign: 'center',
                        background: dragOver ? 'rgba(99,102,241,0.06)' : '#fafbfc',
                        transition: 'background 120ms, border-color 120ms',
                    }}>
                    {url ? (
                        <>
                            <div style={{ marginBottom: '0.85rem', color: '#10b981', fontWeight: 600, fontSize: '0.85rem', display: 'inline-flex', alignItems: 'center', gap: '0.35rem' }}>
                                <Icons.CheckCircle className="w-4 h-4" /> Fichier audio prêt
                            </div>
                            <div><audio src={url} controls style={{ width: '100%', maxWidth: 320, marginBottom: '0.85rem' }} /></div>
                            <div>
                                <button type="button" className="btn btn-secondary btn-sm"
                                    onClick={() => { update(urlKey, ''); }}>
                                    Remplacer
                                </button>
                            </div>
                        </>
                    ) : (
                        <>
                            <div style={{ marginBottom: '0.5rem', color: 'var(--vocal-primary, #6366f1)', display: 'flex', justifyContent: 'center' }}>
                                <Icons.FileAudio className="w-8 h-8" />
                            </div>
                            <div style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: '0.35rem' }}>
                                {uploading ? 'Envoi en cours…' : (dragOver ? 'Déposez le fichier ici' : 'Glissez-déposez un fichier audio')}
                            </div>
                            <div style={{ fontSize: '0.72rem', color: 'var(--text-muted)', marginBottom: '0.85rem' }}>
                                ou cliquez pour parcourir · MP3, WAV, OGG, M4A · Max 10 MB
                            </div>
                            <label style={{ display: 'inline-block', cursor: uploading ? 'not-allowed' : 'pointer' }}>
                                <input type="file" accept="audio/mpeg,audio/wav,audio/ogg,audio/mp4,audio/x-m4a,.mp3,.wav,.ogg,.m4a"
                                    onChange={handleFile} disabled={uploading} style={{ display: 'none' }} />
                                <span className="btn btn-primary btn-sm" style={{ pointerEvents: uploading ? 'none' : 'auto', opacity: uploading ? 0.6 : 1 }}>
                                    {uploading ? 'Envoi…' : 'Choisir un fichier'}
                                </span>
                            </label>
                        </>
                    )}
                </div>
            )}
        </div>
    );
};

const Step2Ivr = ({ form, update }) => {
    const opts = form.ivr_options || [];
    const setOpt = (i, patch) => {
        const next = opts.map((o, idx) => idx === i ? { ...o, ...patch } : o);
        update('ivr_options', next);
    };
    const addOpt = () => {
        const used = new Set(opts.map(o => String(o.digit)));
        let nextDigit = '';
        for (const d of ['1','2','3','4','5','6','7','8','9','0']) {
            if (!used.has(d)) { nextDigit = d; break; }
        }
        update('ivr_options', [...opts, { digit: nextDigit, label: '', action: 'forward', number: '' }]);
    };
    const delOpt = (i) => update('ivr_options', opts.filter((_, idx) => idx !== i));

    return (
        <div>
            <h2 style={{ margin: '0 0 0.35rem', fontSize: '1.05rem' }}>Configurez votre menu à touches</h2>
            <p style={{ margin: '0 0 1.25rem', color: 'var(--text-muted)', fontSize: '0.85rem' }}>
                Le bot dira par exemple : « Tapez 1 pour les ventes, tapez 2 pour le support… ».
                Chaque touche transfère vers un numéro (ou la messagerie).
            </p>
            <div style={{ display: 'flex', flexDirection: 'column', gap: '0.65rem' }}>
                {opts.map((o, i) => (
                    <div key={i} style={{
                        display: 'grid', gridTemplateColumns: '60px 1fr 1fr 100px auto',
                        gap: '0.5rem', alignItems: 'center',
                        padding: '0.65rem', background: '#fafbfc', border: '1px solid var(--border)', borderRadius: 10,
                    }}>
                        <input className="form-input" style={{ textAlign: 'center', fontWeight: 700, fontSize: '1.1rem' }}
                            value={o.digit} maxLength={1}
                            onChange={e => setOpt(i, { digit: e.target.value.replace(/[^0-9]/g, '') })} />
                        <input className="form-input" placeholder="Libellé (ex. Ventes)"
                            value={o.label} onChange={e => setOpt(i, { label: e.target.value })} />
                        <input className="form-input" placeholder="+41 79 ..."
                            value={o.number} onChange={e => setOpt(i, { number: e.target.value })}
                            disabled={o.action === 'voicemail'} />
                        <select className="form-input"
                            value={o.action} onChange={e => setOpt(i, { action: e.target.value })}>
                            <option value="forward">Transfert</option>
                            <option value="voicemail">Messagerie</option>
                        </select>
                        <button type="button" onClick={() => delOpt(i)}
                            style={{ background: 'transparent', border: 'none', color: '#ef4444', cursor: 'pointer', padding: '0.25rem 0.5rem', display: 'inline-flex', alignItems: 'center' }}
                            title="Supprimer"><Icons.X className="w-5 h-5" /></button>
                    </div>
                ))}
            </div>
            {opts.length < 10 && (
                <button type="button" onClick={addOpt}
                    style={{
                        marginTop: '0.85rem', padding: '0.55rem 1rem', fontSize: '0.85rem',
                        border: '1px dashed var(--border)', borderRadius: 8, background: 'transparent',
                        cursor: 'pointer', color: 'var(--vocal-primary, #6366f1)', fontWeight: 600,
                    }}>
                    + Ajouter une touche
                </button>
            )}
        </div>
    );
};

const Step3Welcome = ({ form, update, line }) => {
    const showVoice = form.welcome_audio_source === 'tts';
    return (
        <div>
            <h2 style={{ margin: '0 0 0.35rem', fontSize: '1.05rem' }}>Phrase d'accueil avant le transfert</h2>
            <p style={{ margin: '0 0 1.25rem', color: 'var(--text-muted)', fontSize: '0.85rem' }}>
                Diffusée à vos appelants avant que le téléphone ne sonne chez vous. Optionnelle mais recommandée pour rassurer.
            </p>

            <div style={{
                padding: '1rem 1.15rem', borderRadius: 12,
                border: `1px solid ${form.welcome_enabled ? 'var(--vocal-primary, #6366f1)' : 'var(--border)'}`,
                background: form.welcome_enabled ? 'rgba(99,102,241,0.04)' : '#fafbfc',
                marginBottom: form.welcome_enabled ? '1rem' : 0,
            }}>
                <label style={{ display: 'flex', alignItems: 'center', gap: '0.7rem', cursor: 'pointer' }}>
                    <input type="checkbox" checked={!!form.welcome_enabled}
                        onChange={e => update('welcome_enabled', e.target.checked)}
                        style={{ width: 18, height: 18 }} />
                    <span style={{ fontWeight: 600, fontSize: '0.9rem' }}>
                        Diffuser un message d'accueil
                        <span style={{ display: 'block', fontSize: '0.72rem', color: 'var(--text-muted)', fontWeight: 400, marginTop: 2 }}>
                            Vous pouvez l'écrire (TTS) ou importer un fichier audio existant.
                        </span>
                    </span>
                </label>
            </div>

            {form.welcome_enabled && (
                <AudioSourcePicker
                    sourceKey="welcome_audio_source"
                    urlKey="welcome_uploaded_url"
                    textKey="voice_welcome_message"
                    voiceKey="welcome_voice"
                    defaultVoice="aura-2-agathe-fr"
                    textRows={3}
                    textPlaceholder="Bonjour, vous êtes bien chez Vocal, ne quittez pas…"
                    helperText="Cliquez sur Écouter pour entendre l'annonce avec la voix choisie. L'audio sera généré à la validation."
                    form={form} update={update}
                />
            )}
        </div>
    );
};

const Step3Notif = ({ form, update }) => (
    <div>
        <h2 style={{ margin: '0 0 0.35rem', fontSize: '1.05rem' }}>Où voulez-vous recevoir vos notifications ?</h2>
        <p style={{ margin: '0 0 1.25rem', color: 'var(--text-muted)', fontSize: '0.85rem' }}>Email pour les messages vocaux, transcriptions et résumés d'appels IA.</p>
        <input type="email" className="form-input" placeholder="vous@exemple.com"
            value={form.notification_email} onChange={e => update('notification_email', e.target.value)} />
    </div>
);

const Step4Notif = Step3Notif;

const LineDetailView = () => {
    const { selectedLineId, navigate, notify } = useApp();
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);
    const [tab, setTab] = useState('summary');
    const [editMode, setEditMode] = useState(false);
    const [form, setForm] = useState({});
    const [saving, setSaving] = useState(false);
    const [mode, setMode] = useState(() => localStorage.getItem('vocal_my_mode') || 'easy');
    useEffect(() => { localStorage.setItem('vocal_my_mode', mode); }, [mode]);

    const load = useCallback(async () => {
        if (!selectedLineId) return;
        setLoading(true);
        try {
            const d = await api.get(`/my/lines/${selectedLineId}`);
            if (d?.error) { notify.error(d.error); return; }
            setData(d);
            const parsedHours = (() => {
                try { return d.line.business_hours ? JSON.parse(d.line.business_hours) : defaultBusinessHours(); }
                catch (e) { return defaultBusinessHours(); }
            })();
            setForm({
                label: d.line.label || '',
                description: d.line.description || '',
                forward_number: d.line.forward_number || '',
                welcome_enabled: !!d.line.welcome_enabled,
                voice_welcome_message: d.line.voice_welcome_message || '',
                welcome_audio: d.line.welcome_audio || '',
                voicemail_email: d.line.voicemail_email || '',
                voicemail_greeting: d.line.voicemail_greeting || '',
                voicemail_greeting_audio: d.line.voicemail_greeting_audio || '',
                notification_number: d.line.notification_number || '',
                webhook_url: d.line.webhook_url || '',
                webhook_url_fallback: d.line.webhook_url_fallback || '',
                webhook_timeout_ms: d.line.webhook_timeout_ms || 3000,
                link_url: d.line.link_url || '',
                link_email: d.line.link_email || '',
                link_sms_sender: d.line.link_sms_sender || '',
                record_calls: !!d.line.record_calls,
                recording_disclaimer_enabled: !!d.line.recording_disclaimer_enabled,
                recording_disclaimer_text: d.line.recording_disclaimer_text || '',
                transcription_enabled: !!d.line.transcription_enabled,
                transcription_email: d.line.transcription_email || '',
                transcription_language: d.line.transcription_language || 'fr',
                business_hours_enabled: !!d.line.business_hours_enabled,
                business_hours: parsedHours,
                timezone: d.line.timezone || 'Europe/Zurich',
                out_of_hours_action: d.line.out_of_hours_action || 'message',
                out_of_hours_forward_number: d.line.out_of_hours_forward_number || '',
                out_of_hours_message: d.line.out_of_hours_message || '',
                voice_bot_id: d.line.voice_bot_id || null,
                absence_action: d.line.absence_action || '',
                absence_menu_message: d.line.absence_menu_message || '',
                absence_sms_link_url: d.line.absence_sms_link_url || '',
                absence_sms_sender: d.line.absence_sms_sender || '',
                absence_forward_number: d.line.absence_forward_number || '',
            });
        } catch (e) { notify.error('Erreur chargement ligne'); }
        finally { setLoading(false); }
    }, [selectedLineId, notify]);

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

    const save = async () => {
        setSaving(true);
        try {
            const payload = { ...form };
            if (payload.business_hours && typeof payload.business_hours === 'object') {
                payload.business_hours = JSON.stringify(payload.business_hours);
            }
            if (payload.absence_action === '') payload.absence_action = null;
            const r = await api.put(`/my/lines/${selectedLineId}`, payload);
            if (r?.error) notify.error(r.error);
            else { notify.success('Ligne mise à jour'); setEditMode(false); load(); }
        } catch (e) { notify.error('Erreur'); }
        finally { setSaving(false); }
    };

    if (loading || !data) {
        return (
            <>
                <div className="page-header">
                    <div style={{ display: 'flex', alignItems: 'center', gap: '0.85rem' }}>
                        <button
                            type="button"
                            onClick={() => navigate('lines')}
                            title="Retour à Mes lignes"
                            aria-label="Retour à Mes lignes"
                            style={{
                                width: 44, height: 44, borderRadius: '50%',
                                border: '1px solid var(--border)', background: '#fff',
                                cursor: 'pointer', display: 'inline-flex',
                                alignItems: 'center', justifyContent: 'center',
                                fontSize: '1.25rem', fontWeight: 700, color: 'var(--text)',
                                boxShadow: '0 1px 2px rgba(0,0,0,0.04)', flexShrink: 0,
                            }}
                        >‹</button>
                        <h1 className="page-title" style={{ margin: 0 }}>Chargement…</h1>
                    </div>
                </div>
                <div className="page-content"><SkeletonStatsGrid count={3} /></div>
            </>
        );
    }

    const { line, stats, subscription, recent_calls } = data;
    const lineMeta = LINE_TYPES[line.type] || { label: line.type, color: 'badge-gray' };

    return (
        <>
            <div className="page-header">
                <div style={{ display: 'flex', alignItems: 'center', gap: '0.85rem', minWidth: 0 }}>
                    <button
                        type="button"
                        onClick={() => navigate('lines')}
                        title="Retour à Mes lignes"
                        aria-label="Retour à Mes lignes"
                        style={{
                            width: 44, height: 44, borderRadius: '50%',
                            border: '1px solid var(--border)', background: '#fff',
                            cursor: 'pointer', display: 'inline-flex',
                            alignItems: 'center', justifyContent: 'center',
                            fontSize: '1.4rem', fontWeight: 700, color: 'var(--text)',
                            boxShadow: '0 1px 2px rgba(0,0,0,0.04)', flexShrink: 0,
                            lineHeight: 1,
                        }}
                    >‹</button>
                    <div style={{ minWidth: 0 }}>
                        <h1 className="page-title" style={{ display: 'flex', alignItems: 'center', gap: '0.75rem', flexWrap: 'wrap', margin: 0 }}>
                            <span style={{ fontFamily: 'ui-monospace, monospace' }}>{line.phone_number}</span>
                            <span className={`badge ${line.is_active ? 'badge-success' : 'badge-gray'}`}>{line.is_active ? 'Active' : 'Inactive'}</span>
                            <span className={`badge ${lineMeta.color}`}>{lineMeta.label}</span>
                        </h1>
                        {line.label && <p className="page-subtitle" style={{ margin: '0.2rem 0 0' }}>{line.label}</p>}
                    </div>
                </div>
                <div className="page-actions" style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
                    <div style={{ display: 'inline-flex', background: '#f1f5f9', borderRadius: 8, padding: 3, gap: 2 }}>
                        <button onClick={() => setMode('easy')} style={{
                            padding: '0.4rem 0.85rem', fontSize: '0.78rem', fontWeight: 600,
                            border: 'none', borderRadius: 6, cursor: 'pointer',
                            background: mode === 'easy' ? '#fff' : 'transparent',
                            color: mode === 'easy' ? 'var(--text)' : 'var(--text-muted)',
                            boxShadow: mode === 'easy' ? '0 1px 2px rgba(0,0,0,0.06)' : 'none',
                        }}>Easy</button>
                        <button onClick={() => setMode('advanced')} style={{
                            padding: '0.4rem 0.85rem', fontSize: '0.78rem', fontWeight: 600,
                            border: 'none', borderRadius: 6, cursor: 'pointer',
                            background: mode === 'advanced' ? '#fff' : 'transparent',
                            color: mode === 'advanced' ? 'var(--text)' : 'var(--text-muted)',
                            boxShadow: mode === 'advanced' ? '0 1px 2px rgba(0,0,0,0.06)' : 'none',
                        }}>Avancé</button>
                    </div>
                    {mode === 'advanced' && (tab === 'config' || tab === 'notifications' || tab === 'hours' || tab === 'bot' || tab === 'dnd' || tab === 'vacation' || tab === 'callback' || tab === 'queue' || tab === 'absence') && !editMode && (
                        <button className="btn btn-primary btn-sm" onClick={() => setEditMode(true)}>
                            <Icons.Edit className="w-4 h-4" /> Modifier
                        </button>
                    )}
                </div>
            </div>

            {mode === 'easy' && (
                <div className="page-content">
                    <LineEasyWizard line={line} onSaved={() => { load(); }} onCancel={() => navigate('lines')} />
                </div>
            )}

            {mode === 'advanced' && (() => {
                // --- Option A : 5 onglets principaux + sous-tabs (pills) pour les groupes denses.
                // Chaque "tab" interne (config, hours, dnd, vacation, callback, absence, bot, routing,
                // nps, subscription, widget) est conservé pour ne pas casser le rendu existant.
                // Phase 2a — 5 onglets cibles : Résumé · Mode · Disponibilité · Modules · Notifications
                // Les ids internes (config, hours, bot, etc.) restent inchangés pour ne pas casser le rendu existant.
                const SUB_TABS = {
                    mode: [
                        { id: 'config',   label: 'Configuration',  icon: Icons.Settings },
                        { id: 'routing',  label: 'Routing avancé', icon: Icons.Sparkles },
                        { id: 'callback', label: 'Rappel auto',    icon: Icons.Refresh },
                    ],
                    availability: [
                        { id: 'hours',    label: 'Horaires',         icon: Icons.Calendar },
                        { id: 'dnd',      label: 'Ne pas déranger',  icon: Icons.Moon },
                        { id: 'vacation', label: 'Vacances',         icon: Icons.Briefcase },
                        { id: 'absence',  label: "En cas d'absence", icon: Icons.AlertCircle },
                        ...(line && line.type === 'queue' ? [{ id: 'queue', label: 'Agents', icon: Icons.Users }] : []),
                    ],
                    modules: [
                        { id: 'modules-overview', label: "Vue d'ensemble",   icon: Icons.Home },
                        { id: 'bot',              label: 'Bot vocal',        icon: Icons.Robot },
                        { id: 'nps',              label: 'Satisfaction (NPS)', icon: Icons.AlertCircle },
                        { id: 'widget',           label: 'Widget & API',     icon: Icons.Key },
                        { id: 'subscription',     label: 'Abonnement',       icon: Icons.Card },
                    ],
                    notifications: [
                        { id: 'notifications', label: 'Notifications', icon: Icons.AlertCircle },
                    ],
                };
                const TAB_TO_GROUP = {};
                Object.entries(SUB_TABS).forEach(([g, items]) => items.forEach(it => { TAB_TO_GROUP[it.id] = g; }));
                TAB_TO_GROUP.summary = 'summary';

                const TOP_TABS = [
                    { id: 'summary',       label: 'Résumé',        icon: Icons.Home },
                    { id: 'mode',          label: 'Mode',          icon: Icons.Settings },
                    { id: 'availability',  label: 'Disponibilité', icon: Icons.Calendar },
                    { id: 'modules',       label: 'Modules',       icon: Icons.Sparkles },
                    { id: 'notifications', label: 'Notifications', icon: Icons.AlertCircle },
                ];

                const currentGroup = TAB_TO_GROUP[tab] || 'summary';

                const goToGroup = (groupId) => {
                    if (groupId === 'summary') { setTab('summary'); return; }
                    // Si on est déjà dans le groupe, ne rien changer ; sinon prendre la 1ère sous-tab.
                    if (currentGroup === groupId) return;
                    const first = SUB_TABS[groupId][0]?.id;
                    if (first) setTab(first);
                };

                return (
                <div className="page-content">
                    {/* Top-level tabs (5 onglets) */}
                    <div style={{ display: 'flex', gap: '0.5rem', marginBottom: '0.75rem', borderBottom: '1px solid var(--border)', flexWrap: 'wrap' }}>
                        {TOP_TABS.map(t => {
                            const active = currentGroup === t.id;
                            return (
                                <button key={t.id} onClick={() => goToGroup(t.id)}
                                    style={{ background: 'none', border: 'none', padding: '0.75rem 1rem', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '0.4rem', fontSize: '0.875rem', fontWeight: active ? 700 : 500, color: active ? 'var(--primary)' : 'var(--text-muted)', borderBottom: active ? '2px solid var(--primary)' : '2px solid transparent', marginBottom: '-1px' }}>
                                    <t.icon className="w-4 h-4" /> {t.label}
                                </button>
                            );
                        })}
                    </div>

                    {/* Sub-tabs (pills) pour les groupes multi-sections */}
                    {SUB_TABS[currentGroup] && SUB_TABS[currentGroup].length > 1 && (
                        <div style={{ display: 'flex', gap: '0.4rem', flexWrap: 'wrap', marginBottom: '1.25rem' }}>
                            {SUB_TABS[currentGroup].map(st => {
                                const active = tab === st.id;
                                return (
                                    <button key={st.id} onClick={() => setTab(st.id)}
                                        style={{ background: active ? 'var(--primary)' : '#f1f5f9', color: active ? '#fff' : 'var(--text-secondary)', border: 'none', borderRadius: 999, padding: '0.45rem 0.9rem', cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: '0.35rem', fontSize: '0.78rem', fontWeight: 600 }}>
                                        <st.icon className="w-4 h-4" /> {st.label}
                                    </button>
                                );
                            })}
                        </div>
                    )}
                    {SUB_TABS[currentGroup] && SUB_TABS[currentGroup].length <= 1 && (
                        <div style={{ marginBottom: '0.75rem' }} />
                    )}

                {tab === 'summary' && (
                    <>
                        <div className="stats-grid" style={{ marginBottom: '1.5rem' }}>
                            <div className="stat-card"><div className="stat-icon blue"><Icons.PhoneIn className="w-6 h-6" /></div><div><div className="stat-label">Total appels</div><div className="stat-value">{stats.total_calls || 0}</div></div></div>
                            <div className="stat-card"><div className="stat-icon green"><Icons.Phone className="w-6 h-6" /></div><div><div className="stat-label">Ce mois</div><div className="stat-value">{stats.month_calls || 0}</div></div></div>
                            <div className="stat-card"><div className="stat-icon purple"><Icons.Clock className="w-6 h-6" /></div><div><div className="stat-label">Durée totale</div><div className="stat-value">{formatDuration(stats.total_duration)}</div></div></div>
                            <div className="stat-card"><div className="stat-icon orange"><Icons.Card className="w-6 h-6" /></div><div><div className="stat-label">Coût total</div><div className="stat-value">{formatCurrency(stats.total_cost)}</div></div></div>
                        </div>
                        <div className="card">
                            <div style={{ padding: '1rem 1.25rem', borderBottom: '1px solid var(--border)' }}>
                                <h3 style={{ fontSize: '0.95rem', fontWeight: 700, margin: 0 }}>Derniers appels</h3>
                            </div>
                            {(!recent_calls || recent_calls.length === 0) ? (
                                <div style={{ padding: '3rem 2rem', textAlign: 'center', color: 'var(--text-muted)' }}>Aucun appel</div>
                            ) : (
                                <div className="table-container">
                                    <table className="data-table">
                                        <thead><tr><th></th><th>Date</th><th>De</th><th>Vers</th><th>Statut</th><th style={{ textAlign: 'right' }}>Durée</th></tr></thead>
                                        <tbody>
                                            {recent_calls.map(c => {
                                                const st = CALL_STATUS[c.status] || { label: c.status, color: 'badge-gray' };
                                                return (
                                                    <tr key={c.id}>
                                                        <td style={{ width: 30 }}><CallDirectionIcon direction={c.direction} /></td>
                                                        <td style={{ fontSize: '0.8rem' }}>{formatDateTime(c.created_at)}</td>
                                                        <td style={{ fontFamily: 'ui-monospace, monospace', fontSize: '0.8rem' }}>{c.from_number || '-'}</td>
                                                        <td style={{ fontFamily: 'ui-monospace, monospace', fontSize: '0.8rem', color: 'var(--text-muted)' }}>{c.to_number || '-'}</td>
                                                        <td><div style={{ display: 'flex', alignItems: 'center', gap: '0.4rem' }}><CallStatusDot status={c.status} /><span style={{ fontSize: '0.8rem' }}>{st.label}</span></div></td>
                                                        <td style={{ textAlign: 'right', fontSize: '0.8rem', fontFeatureSettings: '"tnum"' }}>{formatDuration(c.duration)}</td>
                                                    </tr>
                                                );
                                            })}
                                        </tbody>
                                    </table>
                                </div>
                            )}
                        </div>
                    </>
                )}

                {tab === 'config' && (
                    <div className="card" style={{ padding: '1.5rem' }}>
                        <h3 style={{ margin: '0 0 1.25rem', fontSize: '1rem', fontWeight: 700 }}>Configuration de la ligne</h3>
                        <div className="form-group">
                            <label className="form-label">Libellé</label>
                            <input className="form-input" value={form.label} onChange={(e) => setForm({ ...form, label: e.target.value })} disabled={!editMode} placeholder="Nom interne (ex: Standard, Support, etc.)" />
                        </div>
                        {(line.type === 'forward' || line.type === 'voicemail' || line.type === 'ivr') && (
                            <div className="form-group">
                                <label className="form-label">Numéro de redirection</label>
                                <input className="form-input" value={form.forward_number} onChange={(e) => setForm({ ...form, forward_number: e.target.value })} disabled={!editMode} placeholder="+41..." />
                            </div>
                        )}
                        {line.type === 'forward' && (
                            <ForwardWelcomePanel form={form} setForm={setForm} editMode={editMode} notify={notify} />
                        )}
                        {line.type === 'voicemail' && (
                            <VoicemailGreetingPanel form={form} setForm={setForm} editMode={editMode} notify={notify} />
                        )}
                        {(line.type === 'webhook' || line.type === 'external_api') && (() => {
                            const fallbackOn = line.type === 'external_api';
                            return (
                            <>
                                <div className="form-group">
                                    <label className="form-label">URL du webhook{fallbackOn ? ' (primaire)' : ''}</label>
                                    <input className="form-input" value={form.webhook_url} onChange={(e) => setForm({ ...form, webhook_url: e.target.value })} disabled={!editMode} placeholder="https://..." />
                                </div>
                                <div className="form-group">
                                    <label style={{ display: 'flex', alignItems: 'center', gap: '0.6rem', cursor: editMode ? 'pointer' : 'default' }}>
                                        <input type="checkbox" checked={fallbackOn}
                                            disabled={!editMode}
                                            onChange={async (e) => {
                                                const newType = e.target.checked ? 'external_api' : 'webhook';
                                                try {
                                                    await api.put(`/my/lines/${line.id}`, { type: newType });
                                                    notify({ kind: 'success', title: e.target.checked ? 'Fallback activé' : 'Fallback désactivé' });
                                                    load();
                                                } catch (err) { notify({ kind: 'error', title: 'Erreur', message: err.message }); }
                                            }} />
                                        <span style={{ fontSize: '0.875rem' }}>
                                            <strong>Activer un URL de fallback</strong>
                                            <span style={{ display: 'block', fontSize: '0.75rem', color: 'var(--text-muted)' }}>
                                                Si l'URL principale ne répond pas dans le délai imparti, Vocal bascule automatiquement sur le fallback.
                                            </span>
                                        </span>
                                    </label>
                                </div>
                                {fallbackOn && (
                                    <>
                                        <div className="form-group">
                                            <label className="form-label">URL de fallback</label>
                                            <input className="form-input" value={form.webhook_url_fallback || ''} onChange={(e) => setForm({ ...form, webhook_url_fallback: e.target.value })} disabled={!editMode} placeholder="https://backup.example.com/voice" />
                                        </div>
                                        <div className="form-group">
                                            <label className="form-label">Timeout du healthcheck (ms)</label>
                                            <input type="number" min="500" max="5000" step="100" className="form-input" value={form.webhook_timeout_ms || 3000} onChange={(e) => setForm({ ...form, webhook_timeout_ms: parseInt(e.target.value) || 3000 })} disabled={!editMode} />
                                            <small style={{ color: 'var(--text-muted)', fontSize: '0.75rem' }}>
                                                Délai max (500-5000 ms) avant bascule sur le fallback. Défaut : 3000.
                                            </small>
                                        </div>
                                    </>
                                )}
                            </>
                            );
                        })()}
                        {line.type === 'link' && (
                            <>
                                <div className="form-group">
                                    <label className="form-label">URL à envoyer</label>
                                    <input className="form-input" value={form.link_url} onChange={(e) => setForm({ ...form, link_url: e.target.value })} disabled={!editMode} placeholder="https://..." />
                                </div>
                                <div className="form-group">
                                    <label className="form-label">Email destinataire (optionnel)</label>
                                    <input type="email" className="form-input" value={form.link_email} onChange={(e) => setForm({ ...form, link_email: e.target.value })} disabled={!editMode} />
                                </div>
                                <div className="form-group">
                                    <label className="form-label">Expéditeur SMS (max 11 caractères)</label>
                                    <input className="form-input" value={form.link_sms_sender} maxLength="11" onChange={(e) => setForm({ ...form, link_sms_sender: e.target.value })} disabled={!editMode} />
                                </div>
                            </>
                        )}
                        <div style={{ marginTop: '1.5rem', padding: '1rem 1.25rem', background: '#f8fafc', borderRadius: '0.75rem', border: '1px solid var(--border)' }}>
                            <h4 style={{ margin: '0 0 0.5rem', fontSize: '0.9rem', fontWeight: 700, color: 'var(--text)' }}>Enregistrement des appels</h4>
                            <p style={{ margin: '0 0 1rem', fontSize: '0.8rem', color: 'var(--text-muted)' }}>
                                Activez l'enregistrement pour archiver les appels. Pour recevoir la transcription par email, rendez-vous dans l'onglet <strong>Notifications</strong>.
                            </p>
                            <label style={{ display: 'flex', alignItems: 'center', gap: '0.6rem', padding: '0.4rem 0', cursor: editMode ? 'pointer' : 'default' }}>
                                <input type="checkbox" checked={!!form.record_calls} onChange={(e) => setForm({ ...form, record_calls: e.target.checked })} disabled={!editMode} />
                                <span style={{ fontSize: '0.875rem' }}>Enregistrer les appels de cette ligne</span>
                            </label>
                            {form.record_calls && form.type !== 'voicemail' && (
                                <div style={{ marginTop: '0.85rem', paddingTop: '0.85rem', borderTop: '1px dashed var(--border)' }}>
                                    <label style={{ display: 'flex', alignItems: 'flex-start', gap: '0.6rem', padding: '0.4rem 0', cursor: editMode ? 'pointer' : 'default' }}>
                                        <input type="checkbox" checked={!!form.recording_disclaimer_enabled}
                                            onChange={(e) => setForm({ ...form, recording_disclaimer_enabled: e.target.checked })}
                                            disabled={!editMode}
                                            style={{ marginTop: '0.2rem' }} />
                                        <span style={{ fontSize: '0.875rem' }}>
                                            <strong>Avertir l'appelant que l'appel est enregistré</strong>
                                            <span style={{ display: 'block', fontSize: '0.75rem', color: 'var(--text-muted)', marginTop: '0.15rem' }}>
                                                Recommandé pour être en règle avec la LPD (Suisse) et le RGPD (UE). Un court message vocal sera lu avant le transfert.
                                            </span>
                                        </span>
                                    </label>
                                    {form.recording_disclaimer_enabled && (
                                        <div style={{ marginTop: '0.5rem', paddingLeft: '1.5rem' }}>
                                            <label className="form-label" style={{ fontSize: '0.75rem' }}>Message d'avertissement (laisser vide = message par défaut)</label>
                                            <input className="form-input" value={form.recording_disclaimer_text || ''}
                                                placeholder="Cet appel peut être enregistré pour des raisons de qualité et de service."
                                                onChange={(e) => setForm({ ...form, recording_disclaimer_text: e.target.value })}
                                                disabled={!editMode} />
                                        </div>
                                    )}
                                </div>
                            )}
                        </div>

                        {editMode && (
                            <div style={{ display: 'flex', gap: '0.5rem', justifyContent: 'flex-end', marginTop: '1rem' }}>
                                <button className="btn btn-secondary" onClick={() => { setEditMode(false); load(); }}>Annuler</button>
                                <button className="btn btn-primary" onClick={save} disabled={saving}>{saving ? 'Enregistrement…' : 'Enregistrer'}</button>
                            </div>
                        )}
                        <div style={{ marginTop: '1.5rem', padding: '1rem', background: '#f8fafc', borderRadius: '0.5rem', fontSize: '0.8rem', color: 'var(--text-muted)' }}>
                            ℹ️ Le numéro, le type de ligne et certains paramètres avancés ne peuvent être modifiés que par l'équipe Vocal. <a href="mailto:contact@helvia.app" style={{ color: 'var(--primary)' }}>Nous contacter</a>.
                        </div>
                    </div>
                )}

                {tab === 'modules-overview' && (() => {
                    const modules = [
                        {
                            id: 'bot', tab: 'bot', icon: Icons.Robot, color: '#8b5cf6',
                            title: 'Bot vocal IA',
                            desc: "Agent conversationnel multilingue qui répond aux appels, prend des messages ou des RDV.",
                            active: !!(form.voice_bot_id || form.ai_mode || line.type === 'voice_ai'),
                            badge: line.type === 'voice_ai' ? 'Mode principal' : (form.voice_bot_id ? 'Bot lié' : null),
                        },
                        {
                            id: 'capture', tab: 'bot', icon: Icons.Sparkles, color: '#f59e0b',
                            title: 'Capture structurée',
                            desc: 'RDV, réservation taxi/transfert ou restaurant — recap envoyé par email après chaque appel.',
                            active: !!form.ai_capture_mode,
                            badge: form.ai_capture_mode === 'appointment' ? 'RDV'
                                : form.ai_capture_mode === 'reservation_taxi' ? 'Taxi'
                                : form.ai_capture_mode === 'reservation_restaurant' ? 'Restaurant' : null,
                        },
                        {
                            id: 'callback', tab: 'callback', icon: Icons.Refresh, color: '#06b6d4',
                            title: 'Rappel automatique',
                            desc: 'Si l\'appel n\'aboutit pas, propose à l\'appelant de demander un rappel.',
                            active: !!form.callback_enabled,
                        },
                        {
                            id: 'recording', tab: 'config', icon: Icons.Mic, color: '#ef4444',
                            title: 'Enregistrement des appels',
                            desc: 'Archive l\'audio de chaque appel pour consultation et transcription IA.',
                            active: !!form.record_calls,
                            badge: form.record_calls && form.recording_disclaimer_enabled ? 'Disclaimer LPD' : null,
                        },
                        {
                            id: 'transcription', tab: 'notifications', icon: Icons.Send, color: '#0ea5e9',
                            title: 'Transcription par email',
                            desc: 'Envoie la transcription de chaque appel enregistré à l\'email choisi.',
                            active: !!form.transcription_enabled,
                            disabled: !form.record_calls,
                            hint: !form.record_calls ? 'Nécessite enregistrement' : null,
                        },
                        {
                            id: 'nps', tab: 'nps', icon: Icons.AlertCircle, color: '#10b981',
                            title: 'Satisfaction NPS',
                            desc: 'Sondage de satisfaction joué à la fin de chaque appel.',
                            active: false,
                            badge: 'Bientôt',
                        },
                        {
                            id: 'widget', tab: 'widget', icon: Icons.Key, color: '#6366f1',
                            title: 'Widget & API',
                            desc: 'Intègre Vocal dans ton site ou ton application via widget web ou API REST.',
                            active: false,
                        },
                        {
                            id: 'subscription', tab: 'subscription', icon: Icons.Card, color: '#64748b',
                            title: 'Abonnement',
                            desc: 'Forfait, packs additionnels et solde de cette ligne.',
                            active: true,
                        },
                    ];
                    return (
                        <div>
                            <div className="card" style={{ padding: '1.25rem 1.5rem', marginBottom: '1rem', background: 'linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%)' }}>
                                <h3 style={{ margin: '0 0 0.35rem', fontSize: '1rem', fontWeight: 700 }}>Modules de la ligne</h3>
                                <p style={{ margin: 0, fontSize: '0.85rem', color: 'var(--text-muted)' }}>
                                    Active ou configure les modules disponibles. Clique sur une carte pour ouvrir ses réglages.
                                </p>
                            </div>
                            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(260px, 1fr))', gap: '0.85rem' }}>
                                {modules.map(m => {
                                    const Icon = m.icon;
                                    return (
                                        <button key={m.id}
                                            onClick={() => { if (!m.disabled && m.tab) setTab(m.tab); }}
                                            disabled={m.disabled}
                                            style={{
                                                textAlign: 'left',
                                                background: m.active ? '#fff' : '#fafbfc',
                                                border: `1px solid ${m.active ? m.color + '55' : 'var(--border)'}`,
                                                borderRadius: '0.85rem',
                                                padding: '1.1rem 1.15rem',
                                                cursor: m.disabled ? 'not-allowed' : 'pointer',
                                                opacity: m.disabled ? 0.55 : 1,
                                                transition: 'all 160ms',
                                                position: 'relative',
                                                boxShadow: m.active ? `0 1px 3px ${m.color}11` : 'none',
                                            }}>
                                            <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', marginBottom: '0.65rem' }}>
                                                <div style={{ width: 36, height: 36, borderRadius: 10, background: m.color + '18', color: m.color, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                                                    <Icon className="w-5 h-5" />
                                                </div>
                                                <div style={{
                                                    fontSize: '0.7rem', fontWeight: 700, padding: '0.2rem 0.55rem', borderRadius: '0.5rem',
                                                    background: m.active ? '#dcfce7' : '#f1f5f9',
                                                    color: m.active ? '#166534' : '#64748b',
                                                }}>
                                                    {m.active ? 'Actif' : 'Inactif'}
                                                </div>
                                            </div>
                                            <div style={{ fontWeight: 700, fontSize: '0.9rem', marginBottom: '0.25rem' }}>{m.title}</div>
                                            <div style={{ fontSize: '0.78rem', color: 'var(--text-muted)', lineHeight: 1.45 }}>{m.desc}</div>
                                            {(m.badge || m.hint) && (
                                                <div style={{ marginTop: '0.65rem', fontSize: '0.72rem', color: m.color, fontWeight: 600 }}>
                                                    {m.badge || m.hint}
                                                </div>
                                            )}
                                        </button>
                                    );
                                })}
                            </div>
                        </div>
                    );
                })()}

                {tab === 'notifications' && (
                    <div className="card" style={{ padding: '1.5rem' }}>
                        <h3 style={{ margin: '0 0 0.4rem', fontSize: '1rem', fontWeight: 700 }}>Notifications & alertes</h3>
                        <p style={{ margin: '0 0 1.5rem', fontSize: '0.85rem', color: 'var(--text-muted)' }}>
                            Définissez comment vous êtes informé des événements sur cette ligne : messages vocaux, alertes SMS, transcription d'appels.
                        </p>

                        <div className="form-group">
                            <label className="form-label">Numéro de notification (SMS d'alerte)</label>
                            <input className="form-input" value={form.notification_number} onChange={(e) => setForm({ ...form, notification_number: e.target.value })} disabled={!editMode} placeholder="+41 79 123 45 67" />
                            <small style={{ color: 'var(--text-muted)', fontSize: '0.75rem' }}>
                                Un SMS sera envoyé à ce numéro pour les événements importants (nouveau message vocal, rappel demandé, etc.). Laisser vide pour désactiver.
                            </small>
                        </div>

                        <div className="form-group">
                            <label className="form-label">Email pour les messages vocaux</label>
                            <input type="email" className="form-input" value={form.voicemail_email} onChange={(e) => setForm({ ...form, voicemail_email: e.target.value })} disabled={!editMode} placeholder="vous@exemple.ch" />
                            <small style={{ color: 'var(--text-muted)', fontSize: '0.75rem' }}>
                                Reçoit chaque nouveau message vocal avec l'audio en pièce jointe + transcription. Laisser vide = email du compte.
                            </small>
                        </div>

                        <div style={{ marginTop: '1.25rem', padding: '1rem 1.25rem', background: '#f8fafc', borderRadius: '0.75rem', border: '1px solid var(--border)' }}>
                            <h4 style={{ margin: '0 0 0.5rem', fontSize: '0.9rem', fontWeight: 700, color: 'var(--text)' }}>Transcription par email</h4>
                            <p style={{ margin: '0 0 1rem', fontSize: '0.8rem', color: 'var(--text-muted)' }}>
                                Recevez automatiquement la transcription de chaque appel enregistré (nécessite l'enregistrement activé dans l'onglet Configuration).
                            </p>
                            <label style={{ display: 'flex', alignItems: 'center', gap: '0.6rem', padding: '0.4rem 0', cursor: editMode && form.record_calls ? 'pointer' : 'default' }}>
                                <input type="checkbox" checked={!!form.transcription_enabled} onChange={(e) => setForm({ ...form, transcription_enabled: e.target.checked })} disabled={!editMode || !form.record_calls} />
                                <span style={{ fontSize: '0.875rem' }}>Recevoir la transcription par email</span>
                            </label>
                            {!form.record_calls && (
                                <div style={{ fontSize: '0.75rem', color: 'var(--text-muted)', fontStyle: 'italic', paddingLeft: '1.5rem' }}>
                                    ↳ Activez d'abord l'enregistrement dans l'onglet Configuration.
                                </div>
                            )}
                            {form.transcription_enabled && (
                                <div style={{ marginTop: '0.75rem', display: 'grid', gridTemplateColumns: '2fr 1fr', gap: '0.75rem' }}>
                                    <div>
                                        <label className="form-label" style={{ fontSize: '0.75rem' }}>Email destinataire (laisser vide = email du compte)</label>
                                        <input type="email" className="form-input" value={form.transcription_email} onChange={(e) => setForm({ ...form, transcription_email: e.target.value })} disabled={!editMode} placeholder="vous@exemple.ch" />
                                    </div>
                                    <div>
                                        <label className="form-label" style={{ fontSize: '0.75rem' }}>Langue</label>
                                        <select className="form-input" value={form.transcription_language} onChange={(e) => setForm({ ...form, transcription_language: e.target.value })} disabled={!editMode}>
                                            <option value="fr">Français</option>
                                            <option value="en">English</option>
                                            <option value="de">Deutsch</option>
                                            <option value="it">Italiano</option>
                                            <option value="es">Español</option>
                                        </select>
                                    </div>
                                </div>
                            )}
                        </div>

                        {editMode && (
                            <div style={{ display: 'flex', gap: '0.5rem', justifyContent: 'flex-end', marginTop: '1.25rem' }}>
                                <button className="btn btn-secondary" onClick={() => { setEditMode(false); load(); }}>Annuler</button>
                                <button className="btn btn-primary" onClick={save} disabled={saving}>{saving ? 'Enregistrement…' : 'Enregistrer'}</button>
                            </div>
                        )}
                    </div>
                )}

                {tab === 'hours' && (
                    <BusinessHoursPanel form={form} setForm={setForm} editMode={editMode} saving={saving} onSave={save} onCancel={() => { setEditMode(false); load(); }} />
                )}

                {tab === 'dnd' && (
                    <DndPanel line={line} onChanged={load} />
                )}

                {tab === 'vacation' && (
                    <VacationPanel form={form} setForm={setForm} editMode={editMode} setEditMode={setEditMode} saving={saving} onSave={save} onCancel={() => { setEditMode(false); load(); }} />
                )}

                {tab === 'callback' && (
                    <CallbackPanel form={form} setForm={setForm} editMode={editMode} setEditMode={setEditMode} saving={saving} onSave={save} onCancel={() => { setEditMode(false); load(); }} />
                )}

                {tab === 'absence' && (
                    <AbsencePanel line={line} form={form} setForm={setForm} editMode={editMode} saving={saving} onSave={save} onCancel={() => { setEditMode(false); load(); }} />
                )}

                {tab === 'queue' && (
                    <LineAgentsPanel lineId={line.id} />
                )}

                {tab === 'bot' && (
                    <LineBotPanel line={line} form={form} setForm={setForm} editMode={editMode} saving={saving} onSave={save} onCancel={() => { setEditMode(false); load(); }} />
                )}

                {tab === 'routing' && (
                    <RoutingPanel lineId={line.id} />
                )}

                {tab === 'nps' && (
                    <NpsPanel line={line} onSaved={load} />
                )}

                {tab === 'subscription' && (
                    <SubscriptionCard line={line} subscription={subscription} onChanged={load} />
                )}

                {tab === 'widget' && (
                    <div className="card" style={{ padding: '1.5rem' }}>
                        <h3 style={{ margin: '0 0 0.75rem', fontSize: '1rem', fontWeight: 700 }}>Intégration Widget & API</h3>
                        <p style={{ margin: '0 0 1rem', color: 'var(--text-muted)', fontSize: '0.875rem' }}>
                            Pour intégrer cette ligne dans votre site web ou application, créez une clé API et ajoutez le script Vocal Widget.
                        </p>
                        <div style={{ background: '#f8fafc', padding: '1rem', borderRadius: '0.5rem', fontFamily: 'ui-monospace, monospace', fontSize: '0.8rem', overflow: 'auto', marginBottom: '1rem' }}>
                            <code>{`<script src="https://widget.helvia.app/vocal-widget.js"\n    data-vocal-key="VOTRE_CLE_API"\n    data-vocal-line="${line.phone_number}"\n    async></script>`}</code>
                        </div>
                        <button className="btn btn-primary btn-sm" onClick={() => navigate('api-keys')}>
                            <Icons.Key className="w-4 h-4" /> Gérer mes clés API
                        </button>
                    </div>
                )}
                </div>
                );
            })()}
        </>
    );
};

// ==================== SUBSCRIPTION CARD (utilisée dans LineDetail) ====================
const SubscriptionCard = ({ line, subscription, onChanged }) => {
    const { notify } = useApp();
    const [busy, setBusy] = useState(false);
    const [period, setPeriod] = useState('monthly');

    const subscribe = async () => {
        setBusy(true);
        try {
            const r = await api.post('/my/billing/subscribe', { line_id: line.id, period });
            if (r?.error) { notify.error(r.error); return; }
            if (r.checkout_url) window.location.href = r.checkout_url;
        } catch (e) { notify.error(e.message); }
        finally { setBusy(false); }
    };

    const cancel = async () => {
        if (!confirm('Annuler cet abonnement à la fin de la période en cours ?')) return;
        setBusy(true);
        try {
            const r = await api.post(`/my/billing/subscriptions/${subscription.id}/cancel`);
            if (r?.error) notify.error(r.error);
            else { notify.success('Annulation programmée'); onChanged?.(); }
        } catch (e) { notify.error(e.message); }
        finally { setBusy(false); }
    };

    const reactivate = async () => {
        setBusy(true);
        try {
            const r = await api.post(`/my/billing/subscriptions/${subscription.id}/reactivate`);
            if (r?.error) notify.error(r.error);
            else { notify.success('Abonnement réactivé'); onChanged?.(); }
        } catch (e) { notify.error(e.message); }
        finally { setBusy(false); }
    };

    if (subscription && subscription.status !== 'canceled') {
        const isAnnual = subscription.billing_period === 'yearly';
        const isPastDue = subscription.status === 'past_due';
        return (
            <div className="card" style={{ padding: '1.5rem' }}>
                <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', flexWrap: 'wrap', gap: '1rem', marginBottom: '1rem' }}>
                    <div>
                        <h3 style={{ margin: '0 0 0.5rem', fontSize: '1.1rem', fontWeight: 700 }}>
                            Abonnement {isAnnual ? 'annuel' : 'mensuel'} actif
                        </h3>
                        <div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap' }}>
                            <span className={`badge ${subscription.status === 'active' || subscription.status === 'trialing' ? 'badge-success' : 'badge-warning'}`}>
                                {subscription.status}
                            </span>
                            {subscription.cancel_at_period_end ? <span className="badge badge-warning">Annulation programmée</span> : null}
                            {isPastDue && <span className="badge badge-danger">Paiement échoué</span>}
                        </div>
                    </div>
                    <div style={{ textAlign: 'right' }}>
                        <div style={{ fontSize: '1.5rem', fontWeight: 800, color: 'var(--primary)' }}>{formatCurrency(subscription.amount, subscription.currency)}</div>
                        <div style={{ fontSize: '0.75rem', color: 'var(--text-muted)' }}>par {isAnnual ? 'an' : 'mois'}</div>
                    </div>
                </div>
                <div style={{ background: '#f8fafc', padding: '1rem', borderRadius: '0.5rem', marginBottom: '1.25rem', fontSize: '0.85rem', color: 'var(--text-muted)' }}>
                    {subscription.current_period_end && (
                        <div>
                            {subscription.cancel_at_period_end
                                ? <>L'abonnement sera <strong style={{ color: 'var(--text-main)' }}>annulé le {formatDate(subscription.current_period_end)}</strong>.</>
                                : <>Prochain renouvellement automatique le <strong style={{ color: 'var(--text-main)' }}>{formatDate(subscription.current_period_end)}</strong>.</>}
                        </div>
                    )}
                    {subscription.last_payment_succeeded_at && (
                        <div style={{ marginTop: '0.4rem' }}>Dernier paiement réussi : {formatRelative(subscription.last_payment_succeeded_at)}</div>
                    )}
                </div>
                <div style={{ display: 'flex', gap: '0.5rem' }}>
                    {subscription.cancel_at_period_end ? (
                        <button className="btn btn-primary" onClick={reactivate} disabled={busy}>Réactiver l'abonnement</button>
                    ) : (
                        <button className="btn btn-secondary" onClick={cancel} disabled={busy}>Annuler</button>
                    )}
                </div>
            </div>
        );
    }

    return (
        <div className="card" style={{ padding: '1.5rem' }}>
            <h3 style={{ margin: '0 0 0.5rem', fontSize: '1.1rem', fontWeight: 700 }}>Souscrire un abonnement</h3>
            <p style={{ margin: '0 0 1.5rem', color: 'var(--text-muted)', fontSize: '0.9rem' }}>
                Activez votre numéro <strong style={{ color: 'var(--text-main)', fontFamily: 'ui-monospace, monospace' }}>{line.phone_number}</strong> avec un abonnement mensuel ou annuel.
            </p>
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: '1rem', marginBottom: '1.5rem' }}>
                <button onClick={() => setPeriod('monthly')}
                    style={{ background: period === 'monthly' ? '#eef2ff' : '#fff', border: `2px solid ${period === 'monthly' ? 'var(--primary)' : 'var(--border)'}`, borderRadius: '0.75rem', padding: '1.25rem', cursor: 'pointer', textAlign: 'left' }}>
                    <div style={{ fontSize: '0.75rem', color: 'var(--text-muted)', textTransform: 'uppercase', fontWeight: 600 }}>Mensuel</div>
                    <div style={{ fontSize: '1.75rem', fontWeight: 800, color: 'var(--text-main)', margin: '0.25rem 0' }}>9.90 CHF<span style={{ fontSize: '0.8rem', fontWeight: 500, color: 'var(--text-muted)' }}>/mois</span></div>
                    <div style={{ fontSize: '0.8rem', color: 'var(--text-muted)' }}>Sans engagement</div>
                </button>
                <button onClick={() => setPeriod('yearly')}
                    style={{ background: period === 'yearly' ? '#eef2ff' : '#fff', border: `2px solid ${period === 'yearly' ? 'var(--primary)' : 'var(--border)'}`, borderRadius: '0.75rem', padding: '1.25rem', cursor: 'pointer', position: 'relative', textAlign: 'left' }}>
                    <span style={{ position: 'absolute', top: 8, right: 8, background: 'var(--primary)', color: '#fff', fontSize: '0.65rem', fontWeight: 700, padding: '0.2rem 0.5rem', borderRadius: '999px' }}>-40%</span>
                    <div style={{ fontSize: '0.75rem', color: 'var(--text-muted)', textTransform: 'uppercase', fontWeight: 600 }}>Annuel</div>
                    <div style={{ fontSize: '1.75rem', fontWeight: 800, color: 'var(--text-main)', margin: '0.25rem 0' }}>5.90 CHF<span style={{ fontSize: '0.8rem', fontWeight: 500, color: 'var(--text-muted)' }}>/mois</span></div>
                    <div style={{ fontSize: '0.8rem', color: 'var(--text-muted)' }}>70.80 CHF facturés une fois par an</div>
                </button>
            </div>
            <button className="btn btn-primary" onClick={subscribe} disabled={busy} style={{ width: '100%' }}>
                {busy ? 'Redirection vers Stripe…' : `S'abonner (${period === 'yearly' ? '70.80 CHF/an' : '9.90 CHF/mois'})`}
            </button>
            <p style={{ marginTop: '0.75rem', fontSize: '0.75rem', color: 'var(--text-muted)', textAlign: 'center' }}>
                Paiement sécurisé par Stripe • annulable à tout moment depuis votre espace.
            </p>
        </div>
    );
};

// ==================== BUSINESS HOURS PANEL ====================
const BusinessHoursPanel = ({ form, setForm, editMode, saving, onSave, onCancel }) => {
    const hours = form.business_hours || defaultBusinessHours();
    const setDay = (dayId, patch) => {
        const next = { ...hours, [dayId]: { ...(hours[dayId] || { open: false, from: '09:00', to: '18:00' }), ...patch } };
        setForm(f => ({ ...f, business_hours: next }));
    };
    const setHolidays = (val) => {
        const list = val.split('\n').map(s => s.trim()).filter(Boolean);
        setForm(f => ({ ...f, business_hours: { ...hours, holidays: list } }));
    };

    return (
        <div className="grid-cols-1" style={{ display: 'grid', gap: '1rem' }}>
            <div className="card-flat">
                <div className="card-flat-header">
                    <Icons.Calendar className="w-5 h-5" />
                    <h2>Heures d'ouverture</h2>
                </div>
                <div className="card-flat-content" style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
                    <label className="toggle-row">
                        <input
                            type="checkbox"
                            disabled={!editMode}
                            checked={!!form.business_hours_enabled}
                            onChange={e => setForm(f => ({ ...f, business_hours_enabled: e.target.checked }))}
                        />
                        <span>Activer le routage horaire (hors plage = action ci-dessous)</span>
                    </label>

                    <div className="form-row">
                        <label>Fuseau horaire</label>
                        <input
                            type="text"
                            value={form.timezone || 'Europe/Zurich'}
                            disabled={!editMode}
                            onChange={e => setForm(f => ({ ...f, timezone: e.target.value }))}
                            placeholder="Europe/Zurich"
                        />
                    </div>

                    <div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
                        {DAY_LABELS.map(d => {
                            const day = hours[d.id] || { open: false, from: '09:00', to: '18:00' };
                            return (
                                <div key={d.id} style={{ display: 'grid', gridTemplateColumns: '1.5rem 6rem 1fr 1fr', gap: '0.5rem', alignItems: 'center' }}>
                                    <input
                                        type="checkbox"
                                        disabled={!editMode}
                                        checked={!!day.open}
                                        onChange={e => setDay(d.id, { open: e.target.checked })}
                                    />
                                    <span style={{ fontWeight: 600 }}>{d.label}</span>
                                    <input
                                        type="time"
                                        disabled={!editMode || !day.open}
                                        value={day.from || '09:00'}
                                        onChange={e => setDay(d.id, { from: e.target.value })}
                                    />
                                    <input
                                        type="time"
                                        disabled={!editMode || !day.open}
                                        value={day.to || '18:00'}
                                        onChange={e => setDay(d.id, { to: e.target.value })}
                                    />
                                </div>
                            );
                        })}
                    </div>

                    <div className="form-row">
                        <label>Jours fériés (1 par ligne, format YYYY-MM-DD)</label>
                        <textarea
                            rows={4}
                            disabled={!editMode}
                            value={(hours.holidays || []).join('\n')}
                            onChange={e => setHolidays(e.target.value)}
                            placeholder="2026-01-01&#10;2026-12-25"
                        />
                    </div>
                </div>
            </div>

            <div className="card-flat">
                <div className="card-flat-header">
                    <Icons.Settings className="w-5 h-5" />
                    <h2>Hors heures d'ouverture</h2>
                </div>
                <div className="card-flat-content" style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
                    <div className="form-row">
                        <label>Action</label>
                        <select
                            value={form.out_of_hours_action || 'message'}
                            disabled={!editMode}
                            onChange={e => setForm(f => ({ ...f, out_of_hours_action: e.target.value }))}
                        >
                            <option value="message">Lire un message puis raccrocher</option>
                            <option value="voicemail">Lire un message puis prendre un message vocal</option>
                            <option value="forward">Transférer vers un autre numéro</option>
                            <option value="hangup">Raccrocher immédiatement</option>
                        </select>
                    </div>

                    {(form.out_of_hours_action === 'forward') && (
                        <div className="form-row">
                            <label>Numéro de transfert hors heures</label>
                            <input
                                type="text"
                                value={form.out_of_hours_forward_number || ''}
                                disabled={!editMode}
                                onChange={e => setForm(f => ({ ...f, out_of_hours_forward_number: e.target.value }))}
                                placeholder="+41 79 ..."
                            />
                        </div>
                    )}

                    {(form.out_of_hours_action === 'message' || form.out_of_hours_action === 'voicemail') && (
                        <div className="form-row">
                            <label>Message à diffuser</label>
                            <textarea
                                rows={3}
                                disabled={!editMode}
                                value={form.out_of_hours_message || ''}
                                onChange={e => setForm(f => ({ ...f, out_of_hours_message: e.target.value }))}
                                placeholder="Nos bureaux sont actuellement fermés. Merci de rappeler aux heures d'ouverture."
                            />
                        </div>
                    )}
                </div>
            </div>

            {editMode && (
                <div style={{ display: 'flex', gap: '0.5rem', justifyContent: 'flex-end' }}>
                    <button className="btn btn-secondary" onClick={onCancel} disabled={saving}>Annuler</button>
                    <button className="btn btn-primary" onClick={onSave} disabled={saving}>
                        {saving ? 'Enregistrement…' : 'Enregistrer'}
                    </button>
                </div>
            )}
        </div>
    );
};

// ==================== ABSENCE FALLBACK PANEL ====================
const AbsencePanel = ({ line, form, setForm, editMode, saving, onSave, onCancel }) => {
    const action = form.absence_action || '';
    const defaultMenuMsg = "Je suis désolé, mais aucun agent n'est disponible pour le moment. Appuyer sur la touche 1 pour laisser un message après le bip. La touche 2 pour recevoir un sms avec le lien pour démarrer une discussion, ou 3 pour demander a être rappelé.";
    const hasBot = !!form.voice_bot_id;

    return (
        <div className="grid-cols-1" style={{ display: 'grid', gap: '1rem' }}>
            <div className="card-flat">
                <div className="card-flat-header">
                    <Icons.AlertCircle className="w-5 h-5" />
                    <h2>En cas d'absence (personne ne répond)</h2>
                </div>
                <div className="card-flat-content" style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
                    <p style={{ margin: 0, fontSize: '0.85rem', color: 'var(--text-muted)' }}>
                        Choisissez ce que doit faire l'appel quand <strong>aucun agent ne répond</strong> (queue, IVR, redirection ou ligne classique). S'applique à tous les modes.
                    </p>

                    <div className="form-row">
                        <label>Action de fallback</label>
                        <select
                            value={action}
                            disabled={!editMode}
                            onChange={e => setForm(f => ({ ...f, absence_action: e.target.value }))}
                        >
                            <option value="">— Comportement par défaut (legacy) —</option>
                            <option value="menu">Menu vocal (1=message, 2=SMS lien, 3=rappel)</option>
                            <option value="bot">Bot vocal {hasBot ? '' : '(aucun bot lié)'}</option>
                            <option value="voicemail">Message vocal direct</option>
                            <option value="callback">Programmer un rappel</option>
                            <option value="forward">Rediriger vers un autre numéro</option>
                            <option value="hangup">Raccrocher</option>
                        </select>
                    </div>

                    {action === 'menu' && (
                        <>
                            <div className="form-row">
                                <label>Message du menu</label>
                                <textarea
                                    rows={4}
                                    disabled={!editMode}
                                    value={form.absence_menu_message || ''}
                                    onChange={e => setForm(f => ({ ...f, absence_menu_message: e.target.value }))}
                                    placeholder={defaultMenuMsg}
                                />
                                <small style={{ color: 'var(--text-muted)', fontSize: '0.75rem' }}>
                                    Laisser vide pour utiliser le message par défaut.
                                </small>
                            </div>
                            <div className="form-row">
                                <label>Lien envoyé par SMS (touche 2)</label>
                                <input
                                    type="text"
                                    disabled={!editMode}
                                    value={form.absence_sms_link_url || ''}
                                    onChange={e => setForm(f => ({ ...f, absence_sms_link_url: e.target.value }))}
                                    placeholder={form.link_url || 'https://chat.exemple.ch/...'}
                                />
                                <small style={{ color: 'var(--text-muted)', fontSize: '0.75rem' }}>
                                    Si vide, utilise le lien configuré sur la ligne ({form.link_url ? <code>{form.link_url}</code> : 'aucun'}).
                                </small>
                            </div>
                            <div className="form-row">
                                <label>Expéditeur SMS (max 11 caractères)</label>
                                <input
                                    type="text"
                                    maxLength={11}
                                    disabled={!editMode}
                                    value={form.absence_sms_sender || ''}
                                    onChange={e => setForm(f => ({ ...f, absence_sms_sender: e.target.value }))}
                                    placeholder={form.link_sms_sender || 'Vocal'}
                                />
                            </div>
                            <div style={{ padding: '0.75rem 1rem', background: '#f8fafc', borderRadius: '0.5rem', fontSize: '0.8rem', color: 'var(--text-muted)' }}>
                                ℹ️ <strong>Comportement :</strong> Touche <strong>1</strong> = laisser un message vocal. Touche <strong>2</strong> = SMS avec le lien (facturé au tarif SMS standard). Touche <strong>3</strong> = enregistre une demande de rappel dans l'onglet <strong>Rappels</strong> + email de notification (à <code>{form.voicemail_email || 'l\'email du compte'}</code>).
                            </div>
                        </>
                    )}

                    {action === 'bot' && (
                        <div style={{ padding: '0.75rem 1rem', background: hasBot ? '#ecfdf5' : '#fef2f2', borderRadius: '0.5rem', fontSize: '0.85rem', color: hasBot ? '#065f46' : '#991b1b' }}>
                            {hasBot
                                ? <>✅ Le bot vocal lié à cette ligne prendra l'appel quand personne ne répond. Configurez son comportement dans l'onglet <strong>Bot vocal</strong>.</>
                                : <>⚠️ Aucun bot vocal n'est lié à cette ligne. Allez dans l'onglet <strong>Bot vocal</strong> pour en sélectionner un, sinon le fallback sera ignoré.</>
                            }
                        </div>
                    )}

                    {action === 'forward' && (
                        <div className="form-row">
                            <label>Numéro de transfert</label>
                            <input
                                type="text"
                                disabled={!editMode}
                                value={form.absence_forward_number || ''}
                                onChange={e => setForm(f => ({ ...f, absence_forward_number: e.target.value }))}
                                placeholder="+41 79 ..."
                            />
                        </div>
                    )}

                    {action === '' && (
                        <div style={{ padding: '0.75rem 1rem', background: '#f8fafc', borderRadius: '0.5rem', fontSize: '0.8rem', color: 'var(--text-muted)' }}>
                            ℹ️ Aucun fallback personnalisé : on garde le comportement actuel de la ligne (boîte vocale par défaut pour les queues, raccrochage pour les IVR).
                        </div>
                    )}
                </div>
            </div>

            {editMode && (
                <div style={{ display: 'flex', gap: '0.5rem', justifyContent: 'flex-end' }}>
                    <button className="btn btn-secondary" onClick={onCancel} disabled={saving}>Annuler</button>
                    <button className="btn btn-primary" onClick={onSave} disabled={saving}>
                        {saving ? 'Enregistrement…' : 'Enregistrer'}
                    </button>
                </div>
            )}
        </div>
    );
};

// ==================== LINE BOT PANEL ====================
const LineBotPanel = ({ line, form, setForm, editMode, saving, onSave, onCancel }) => {
    const { notify, navigate } = useApp();
    const [bots, setBots] = useState([]);
    const [loading, setLoading] = useState(true);
    useEffect(() => {
        (async () => {
            try { const r = await api.get('/my/bots'); setBots(r?.bots || []); }
            catch (e) {} finally { setLoading(false); }
        })();
    }, []);

    const selected = bots.find(b => b.id === form.voice_bot_id);

    return (
        <div className="grid-cols-1" style={{ display: 'grid', gap: '1rem' }}>
            <div className="card-flat">
                <div className="card-flat-header">
                    <Icons.Robot className="w-5 h-5" />
                    <h2>Bot vocal sur cette ligne</h2>
                </div>
                <div className="card-flat-content" style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
                    {loading ? (
                        <p style={{ color: 'var(--text-muted)' }}>Chargement…</p>
                    ) : bots.length === 0 ? (
                        <div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
                            <p style={{ color: 'var(--text-muted)', margin: 0 }}>
                                Aucun bot vocal créé pour le moment.
                            </p>
                            <button className="btn btn-primary btn-sm" style={{ alignSelf: 'flex-start' }}
                                onClick={() => navigate('bots')}>
                                <Icons.Plus className="w-4 h-4" /> Créer un bot
                            </button>
                        </div>
                    ) : (
                        <>
                            <div className="form-row">
                                <label>Bot associé</label>
                                <select
                                    value={form.voice_bot_id || ''}
                                    disabled={!editMode}
                                    onChange={e => setForm(f => ({ ...f, voice_bot_id: e.target.value ? Number(e.target.value) : null }))}
                                >
                                    <option value="">- Aucun (mode standard) -</option>
                                    {bots.map(b => (
                                        <option key={b.id} value={b.id}>{b.name}{b.is_active ? '' : ' (inactif)'}</option>
                                    ))}
                                </select>
                            </div>
                            {selected && (
                                <div style={{ padding: '0.75rem', background: 'var(--bg-soft)', borderRadius: '0.5rem', fontSize: '0.875rem', display: 'flex', flexDirection: 'column', gap: '0.25rem' }}>
                                    <div><strong>{selected.name}</strong></div>
                                    {selected.description && <div style={{ color: 'var(--text-muted)' }}>{selected.description}</div>}
                                    <div style={{ color: 'var(--text-muted)', fontSize: '0.75rem' }}>
                                        Voix: {selected.voice} · Langue: {selected.language} · {selected.is_active ? 'Actif' : 'Inactif'}
                                    </div>
                                    <button className="btn btn-secondary btn-sm" style={{ alignSelf: 'flex-start', marginTop: '0.5rem' }}
                                        onClick={() => navigate('bots', { botId: selected.id })}>
                                        Configurer ce bot
                                    </button>
                                </div>
                            )}
                            <p style={{ fontSize: '0.75rem', color: 'var(--text-muted)', margin: 0 }}>
                                Quand un bot est associé, les appels entrants sur cette ligne sont pris en charge automatiquement par l'IA. Les enregistrements et transcriptions restent disponibles.
                            </p>
                        </>
                    )}
                </div>
            </div>

            {editMode && (
                <div style={{ display: 'flex', gap: '0.5rem', justifyContent: 'flex-end' }}>
                    <button className="btn btn-secondary" onClick={onCancel} disabled={saving}>Annuler</button>
                    <button className="btn btn-primary" onClick={onSave} disabled={saving}>
                        {saving ? 'Enregistrement…' : 'Enregistrer'}
                    </button>
                </div>
            )}
        </div>
    );
};

// ==================== DND PANEL ====================
const DndPanel = ({ line, onChanged }) => {
    const { notify } = useApp();
    const [busy, setBusy] = useState(false);
    const isActive = line.dnd_until && new Date(line.dnd_until).getTime() > Date.now();
    const enableFor = async (minutes, message) => {
        setBusy(true);
        try {
            await api.post(`/my/lines/${line.id}/dnd`, { duration_minutes: minutes, message: message || line.dnd_message });
            notify.success(`Ne pas déranger activé pour ${minutes < 60 ? minutes + ' min' : Math.round(minutes / 60) + 'h'}`);
            onChanged && onChanged();
        } catch (e) { notify.error('Erreur'); } finally { setBusy(false); }
    };
    const disable = async () => {
        setBusy(true);
        try {
            await api.post(`/my/lines/${line.id}/dnd`, { until: null });
            notify.success('Ne pas déranger désactivé');
            onChanged && onChanged();
        } catch (e) { notify.error('Erreur'); } finally { setBusy(false); }
    };
    return (
        <div className="card-flat">
            <div className="card-flat-header">
                <Icons.Moon className="w-5 h-5" />
                <h2>Mode "Ne pas déranger"</h2>
            </div>
            <div className="card-flat-content" style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
                {isActive ? (
                    <div style={{ padding: '0.75rem 1rem', background: '#fef3c7', border: '1px solid #fcd34d', borderRadius: '0.5rem', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
                        <div>
                            <div style={{ fontWeight: 600, color: '#854d0e' }}>Actuellement actif</div>
                            <div style={{ fontSize: '0.8rem', color: '#92400e' }}>Jusqu'à {new Date(line.dnd_until).toLocaleString('fr-CH')}</div>
                        </div>
                        <button className="btn btn-secondary btn-sm" onClick={disable} disabled={busy}>Désactiver</button>
                    </div>
                ) : (
                    <p style={{ color: 'var(--text-muted)', margin: 0 }}>
                        Bloque temporairement les appels entrants. Les appelants entendent un message court (configurable ci-dessous) et l'appel est terminé proprement. Si "Rappel auto" est aussi activé, les appelants seront rappelés automatiquement à la fin de la période.
                    </p>
                )}
                <div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap' }}>
                    <button className="btn btn-primary btn-sm" onClick={() => enableFor(30)} disabled={busy}>30 min</button>
                    <button className="btn btn-primary btn-sm" onClick={() => enableFor(60)} disabled={busy}>1 h</button>
                    <button className="btn btn-primary btn-sm" onClick={() => enableFor(120)} disabled={busy}>2 h</button>
                    <button className="btn btn-primary btn-sm" onClick={() => enableFor(240)} disabled={busy}>4 h</button>
                    <button className="btn btn-primary btn-sm" onClick={() => {
                        const tomorrow = new Date(); tomorrow.setHours(8, 0, 0, 0);
                        if (tomorrow.getTime() < Date.now()) tomorrow.setDate(tomorrow.getDate() + 1);
                        const mins = Math.round((tomorrow.getTime() - Date.now()) / 60000);
                        enableFor(mins);
                    }} disabled={busy}>Jusqu'à demain 8h</button>
                </div>
                <div className="form-row">
                    <label>Message lu pendant le DND (optionnel)</label>
                    <input className="form-input" defaultValue={line.dnd_message || ''} placeholder="Je suis indisponible pour le moment, merci de rappeler plus tard."
                        onBlur={async (e) => {
                            try { await api.put(`/my/lines/${line.id}`, { dnd_message: e.target.value || null }); } catch (err) {}
                        }} />
                </div>
            </div>
        </div>
    );
};

// ==================== VACATION PANEL ====================
const VacationPanel = ({ form, setForm, editMode, setEditMode, saving, onSave, onCancel }) => {
    const update = (fn) => {
        if (!editMode && setEditMode) setEditMode(true);
        setForm(fn);
    };
    return (
        <div className="card-flat">
            <div className="card-flat-header">
                <Icons.Briefcase className="w-5 h-5" />
                <h2>Mode Vacances</h2>
            </div>
            <div className="card-flat-content" style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
                <p style={{ color: 'var(--text-muted)', margin: 0, fontSize: '0.875rem' }}>
                    Pendant les vacances, les appelants entendent votre message et peuvent laisser un message vocal qui sera transmis par email. La transcription IA est conservée si activée.
                </p>
                <label className="toggle-row">
                    <input type="checkbox" checked={!!form.vacation_enabled}
                        onChange={e => update(f => ({ ...f, vacation_enabled: e.target.checked }))} />
                    <span>Activer le mode vacances</span>
                </label>
                {form.vacation_enabled && (
                    <>
                        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '0.75rem' }}>
                            <div className="form-row">
                                <label>Du</label>
                                <input type="datetime-local" className="form-input" value={form.vacation_from || ''}
                                    onChange={e => update(f => ({ ...f, vacation_from: e.target.value }))} />
                            </div>
                            <div className="form-row">
                                <label>Au</label>
                                <input type="datetime-local" className="form-input" value={form.vacation_to || ''}
                                    onChange={e => update(f => ({ ...f, vacation_to: e.target.value }))} />
                            </div>
                        </div>
                        <div className="form-row">
                            <label>Message lu aux appelants</label>
                            <textarea className="form-input" rows={3} value={form.vacation_message || ''}
                                placeholder="Nous sommes en congés du X au Y. Vous pouvez laisser un message après le bip ou nous écrire à contact@…"
                                onChange={e => update(f => ({ ...f, vacation_message: e.target.value }))} />
                        </div>
                        <div className="form-row">
                            <label>Email pour transmission des messages vocaux (optionnel)</label>
                            <input type="email" className="form-input" value={form.vacation_forward_email || ''}
                                placeholder="vous@exemple.ch"
                                onChange={e => update(f => ({ ...f, vacation_forward_email: e.target.value }))} />
                        </div>
                    </>
                )}
                {editMode && (
                    <div style={{ display: 'flex', gap: '0.5rem', justifyContent: 'flex-end' }}>
                        <button className="btn btn-secondary" onClick={onCancel} disabled={saving}>Annuler</button>
                        <button className="btn btn-primary" onClick={onSave} disabled={saving}>{saving ? 'Enregistrement…' : 'Enregistrer'}</button>
                    </div>
                )}
            </div>
        </div>
    );
};

// ==================== CALLBACK PANEL ====================
const CallbackPanel = ({ form, setForm, editMode, setEditMode, saving, onSave, onCancel }) => {
    const update = (fn) => {
        if (!editMode && setEditMode) setEditMode(true);
        setForm(fn);
    };
    return (
        <div className="card-flat">
            <div className="card-flat-header">
                <Icons.Refresh className="w-5 h-5" />
                <h2>Rappel automatique des appels manqués</h2>
            </div>
            <div className="card-flat-content" style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
                <p style={{ color: 'var(--text-muted)', margin: 0, fontSize: '0.875rem' }}>
                    Quand un appel n'est pas pris (hors horaires, DND, file d'attente vide), Vocal rappelle automatiquement l'appelant après un délai. Pratique pour ne perdre aucun lead.
                </p>
                <label className="toggle-row">
                    <input type="checkbox" checked={!!form.callback_enabled}
                        onChange={e => update(f => ({ ...f, callback_enabled: e.target.checked }))} />
                    <span>Activer le rappel automatique</span>
                </label>
                {form.callback_enabled && (
                    <>
                        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '0.75rem' }}>
                            <div className="form-row">
                                <label>Délai avant rappel (secondes)</label>
                                <input type="number" min="15" max="3600" className="form-input"
                                    value={form.callback_delay_seconds ?? 60}
                                    onChange={e => update(f => ({ ...f, callback_delay_seconds: parseInt(e.target.value) || 60 }))} />
                            </div>
                            <div className="form-row">
                                <label>Tentatives max</label>
                                <input type="number" min="1" max="5" className="form-input"
                                    value={form.callback_max_attempts ?? 1}
                                    onChange={e => update(f => ({ ...f, callback_max_attempts: parseInt(e.target.value) || 1 }))} />
                            </div>
                        </div>
                        <div className="form-row">
                            <label>Message lu en début de rappel</label>
                            <textarea className="form-input" rows={2} value={form.callback_message || ''}
                                placeholder="Bonjour, vous nous avez appelé tout à l'heure, nous vous rappelons. Merci de patienter…"
                                onChange={e => update(f => ({ ...f, callback_message: e.target.value }))} />
                        </div>
                    </>
                )}
                {editMode && (
                    <div style={{ display: 'flex', gap: '0.5rem', justifyContent: 'flex-end' }}>
                        <button className="btn btn-secondary" onClick={onCancel} disabled={saving}>Annuler</button>
                        <button className="btn btn-primary" onClick={onSave} disabled={saving}>{saving ? 'Enregistrement…' : 'Enregistrer'}</button>
                    </div>
                )}
            </div>
        </div>
    );
};

// ==================== LINE AGENTS PANEL (queue) ====================
const LineAgentsPanel = ({ lineId }) => {
    const { notify } = useApp();
    const [agents, setAgents] = useState([]);
    const [loading, setLoading] = useState(true);
    const [show, setShow] = useState(false);
    const [draft, setDraft] = useState({ label: '', agent_type: 'number', target: '', priority: 0, timeout_seconds: 25 });
    const load = useCallback(async () => {
        setLoading(true);
        try { const r = await api.get(`/my/lines/${lineId}/agents`); setAgents(r?.agents || []); }
        catch (e) { notify.error('Erreur chargement agents'); } finally { setLoading(false); }
    }, [lineId, notify]);
    useEffect(() => { load(); }, [load]);
    const create = async () => {
        if (!draft.target.trim()) return notify.error('Indiquez un numéro / identité');
        try {
            const r = await api.post(`/my/lines/${lineId}/agents`, draft);
            if (r?.error) return notify.error(r.error);
            notify.success('Agent ajouté'); setShow(false);
            setDraft({ label: '', agent_type: 'number', target: '', priority: 0, timeout_seconds: 25 });
            load();
        } catch (e) { notify.error('Erreur: ' + e.message); }
    };
    const update = async (a, patch) => {
        try { await api.put(`/my/lines/${lineId}/agents/${a.id}`, patch); load(); }
        catch (e) { notify.error('Erreur'); }
    };
    const remove = async (a) => {
        if (!confirm(`Supprimer l'agent ${a.label || a.target} ?`)) return;
        try { await api.del(`/my/lines/${lineId}/agents/${a.id}`); load(); }
        catch (e) { notify.error('Erreur'); }
    };
    return (
        <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
            <div className="card-flat">
                <div className="card-flat-header">
                    <Icons.Users className="w-5 h-5" />
                    <h2>Agents du standard</h2>
                    <button className="btn btn-primary btn-sm" style={{ marginLeft: 'auto' }} onClick={() => setShow(true)}>
                        <Icons.Plus className="w-4 h-4" /> Ajouter
                    </button>
                </div>
                <div className="card-flat-content">
                    {loading ? <p>Chargement…</p> : agents.length === 0 ? (
                        <p style={{ color: 'var(--text-muted)' }}>Aucun agent. Ajoutez les téléphones de votre équipe pour qu'ils sonnent en parallèle ou en cascade.</p>
                    ) : (
                        <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '0.875rem' }}>
                            <thead><tr style={{ textAlign: 'left', color: 'var(--text-muted)', fontSize: '0.75rem' }}>
                                <th style={{ padding: '0.5rem 0' }}>Nom</th><th>Type</th><th>Cible</th><th>Priorité</th><th>Timeout</th><th>Actif</th><th></th>
                            </tr></thead>
                            <tbody>
                                {agents.map(a => (
                                    <tr key={a.id} style={{ borderTop: '1px solid var(--border)' }}>
                                        <td style={{ padding: '0.5rem 0' }}>{a.label || '-'}</td>
                                        <td>{a.agent_type}</td>
                                        <td style={{ fontFamily: 'ui-monospace, monospace' }}>{a.target}</td>
                                        <td>{a.priority === 0 ? 'parallèle' : `seq #${a.priority}`}</td>
                                        <td>{a.timeout_seconds}s</td>
                                        <td><input type="checkbox" checked={!!a.is_active} onChange={e => update(a, { is_active: e.target.checked })} /></td>
                                        <td><button className="btn btn-secondary btn-sm" onClick={() => remove(a)}><Icons.Trash className="w-4 h-4" /></button></td>
                                    </tr>
                                ))}
                            </tbody>
                        </table>
                    )}
                </div>
            </div>
            {show && (
                <div className="card" style={{ padding: '1rem', border: '2px solid var(--primary-light)' }}>
                    <h3 style={{ margin: '0 0 0.75rem' }}>Nouvel agent</h3>
                    <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '0.75rem' }}>
                        <div className="form-row">
                            <label>Nom (Anna, Pierre…)</label>
                            <input className="form-input" value={draft.label} onChange={e => setDraft({ ...draft, label: e.target.value })} />
                        </div>
                        <div className="form-row">
                            <label>Type</label>
                            <select className="form-input" value={draft.agent_type} onChange={e => setDraft({ ...draft, agent_type: e.target.value })}>
                                <option value="number">Numéro de téléphone</option>
                                <option value="client">Client VoIP (app Vocal)</option>
                                <option value="sip">SIP URI</option>
                            </select>
                        </div>
                        <div className="form-row">
                            <label>Cible</label>
                            <input className="form-input" value={draft.target} onChange={e => setDraft({ ...draft, target: e.target.value })}
                                placeholder={draft.agent_type === 'number' ? '+41791234567' : draft.agent_type === 'client' ? 'identifier-vocal' : 'sip:user@host'} />
                        </div>
                        <div className="form-row">
                            <label>Priorité (0 = parallèle, &gt;0 = ordre séquentiel)</label>
                            <input type="number" className="form-input" value={draft.priority} onChange={e => setDraft({ ...draft, priority: parseInt(e.target.value) || 0 })} />
                        </div>
                        <div className="form-row">
                            <label>Timeout (s)</label>
                            <input type="number" className="form-input" value={draft.timeout_seconds} onChange={e => setDraft({ ...draft, timeout_seconds: parseInt(e.target.value) || 25 })} />
                        </div>
                    </div>
                    <div style={{ display: 'flex', gap: '0.5rem', justifyContent: 'flex-end', marginTop: '0.75rem' }}>
                        <button className="btn btn-secondary" onClick={() => setShow(false)}>Annuler</button>
                        <button className="btn btn-primary" onClick={create}>Ajouter</button>
                    </div>
                </div>
            )}
        </div>
    );
};

// ==================== BOTS VIEW ====================
const VOICES = [
    { id: 'alloy',   label: 'Alloy (neutre)' },
    { id: 'echo',    label: 'Echo (homme)' },
    { id: 'fable',   label: 'Fable (homme doux)' },
    { id: 'onyx',    label: 'Onyx (homme grave)' },
    { id: 'nova',    label: 'Nova (femme)' },
    { id: 'shimmer', label: 'Shimmer (femme douce)' },
];
const LANGS = [
    { id: 'fr', label: 'Français' },
    { id: 'en', label: 'Anglais' },
    { id: 'de', label: 'Allemand' },
    { id: 'it', label: 'Italien' },
    { id: 'es', label: 'Espagnol' },
];

const BotsView = () => {
    const { notify, selectedBotId, setSelectedBotId } = useApp();
    const [bots, setBots] = useState([]);
    const [loading, setLoading] = useState(true);
    const [creating, setCreating] = useState(false);
    const [newName, setNewName] = useState('');

    const load = useCallback(async () => {
        try { const r = await api.get('/my/bots'); setBots(r?.bots || []); }
        catch (e) { notify.error('Erreur chargement'); }
        finally { setLoading(false); }
    }, []);
    useEffect(() => { load(); }, [load]);

    const create = async () => {
        if (!newName.trim()) { notify.error('Nom requis'); return; }
        setCreating(true);
        try {
            const r = await api.post('/my/bots', { name: newName.trim() });
            if (r?.error) notify.error(r.error);
            else { notify.success('Bot créé'); setNewName(''); setSelectedBotId(r.bot?.id || null); load(); }
        } catch (e) { notify.error('Erreur'); }
        finally { setCreating(false); }
    };

    if (selectedBotId) {
        return <BotDetailView botId={selectedBotId} onBack={() => { setSelectedBotId(null); load(); }} />;
    }

    const openInStudio = (botId) => {
        const tok = localStorage.getItem('vocal_my_token') || '';
        const url = botId
            ? `https://bots.helvia.app/?token=${encodeURIComponent(tok)}#bot-${botId}/overview`
            : `https://bots.helvia.app/?token=${encodeURIComponent(tok)}`;
        window.open(url, '_blank', 'noopener');
    };

    return (
        <div className="page-container">
            <div className="page-header" style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', flexWrap: 'wrap', gap: '1rem' }}>
                <div>
                    <h1><Icons.Robot className="w-6 h-6" /> Bots vocaux</h1>
                    <p style={{ color: 'var(--text-muted)' }}>Créez un assistant IA capable de répondre à vos appels, d'apprendre de vos sites et de progresser à chaque conversation.</p>
                </div>
                <button className="btn btn-primary btn-sm" onClick={() => openInStudio(null)} title="Console dédiée avec sidebar complète, Simulator live, A/B testing, marketplace voix">
                    <Icons.Sparkles className="w-4 h-4" /> Ouvrir Bot Studio
                </button>
            </div>

            <div className="card-flat" style={{ marginBottom: '1rem', background: 'linear-gradient(135deg, #fdfdff, #f5f3ff)', borderColor: 'var(--primary-light)' }}>
                <div className="card-flat-content" style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
                    <Icons.Sparkles className="w-5 h-5" style={{ color: 'var(--primary)' }} />
                    <div style={{ flex: 1, fontSize: '0.85rem' }}>
                        <strong>Nouveau : Bot Studio</strong> — console dédiée sur <code>bots.helvia.app</code> avec sidebar complète, Simulator chat live, versioning du prompt, A/B testing et marketplace de voix.
                    </div>
                    <button className="btn btn-primary btn-sm" onClick={() => openInStudio(null)}>
                        Découvrir <Icons.ChevronRight className="w-4 h-4" />
                    </button>
                </div>
            </div>

            <div className="card-flat" style={{ marginBottom: '1rem' }}>
                <div className="card-flat-header">
                    <Icons.Plus className="w-5 h-5" />
                    <h2>Nouveau bot</h2>
                </div>
                <div className="card-flat-content" style={{ display: 'flex', gap: '0.5rem' }}>
                    <input type="text" placeholder="Nom du bot (ex: Assistant standard)" value={newName} onChange={e => setNewName(e.target.value)} style={{ flex: 1 }} />
                    <button className="btn btn-primary" onClick={create} disabled={creating}>{creating ? 'Création…' : 'Créer'}</button>
                </div>
            </div>

            <div className="card-flat">
                <div className="card-flat-header">
                    <Icons.Robot className="w-5 h-5" />
                    <h2>Mes bots</h2>
                </div>
                <div className="card-flat-content">
                    {loading ? (
                        <p style={{ color: 'var(--text-muted)' }}>Chargement…</p>
                    ) : bots.length === 0 ? (
                        <p style={{ color: 'var(--text-muted)' }}>Aucun bot pour le moment. Créez-en un ci-dessus.</p>
                    ) : (
                        <div style={{ display: 'grid', gap: '0.5rem', gridTemplateColumns: 'repeat(auto-fill, minmax(260px, 1fr))' }}>
                            {bots.map(b => (
                                <button key={b.id} onClick={() => setSelectedBotId(b.id)} className="card" style={{ textAlign: 'left', cursor: 'pointer', padding: '1rem' }}>
                                    <div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', marginBottom: '0.4rem' }}>
                                        <Icons.Robot className="w-5 h-5" />
                                        <strong>{b.name}</strong>
                                        {!b.is_active && <span className="badge badge-gray">inactif</span>}
                                    </div>
                                    {b.description && <div style={{ fontSize: '0.8rem', color: 'var(--text-muted)', marginBottom: '0.4rem' }}>{b.description}</div>}
                                    <div style={{ fontSize: '0.7rem', color: 'var(--text-muted)' }}>
                                        Voix: {b.voice} · {b.language?.toUpperCase()}
                                    </div>
                                </button>
                            ))}
                        </div>
                    )}
                </div>
            </div>
        </div>
    );
};

const BotDetailView = ({ botId, onBack }) => {
    const { notify } = useApp();
    const [bot, setBot] = useState(null);
    const [loading, setLoading] = useState(true);
    const [tab, setTab] = useState('config');
    const [editMode, setEditMode] = useState(false);
    const [saving, setSaving] = useState(false);
    const [form, setForm] = useState({});

    const load = useCallback(async () => {
        setLoading(true);
        try {
            const r = await api.get(`/my/bots/${botId}`);
            if (r?.error) { notify.error(r.error); return; }
            setBot(r.bot);
            setForm({
                name: r.bot.name || '',
                description: r.bot.description || '',
                instructions: r.bot.instructions || '',
                voice: r.bot.voice || 'alloy',
                language: r.bot.language || 'fr',
                greeting: r.bot.greeting || '',
                human_transfer_number: r.bot.human_transfer_number || '',
                learn_from_calls: !!r.bot.learn_from_calls,
                max_call_duration: r.bot.max_call_duration || 600,
                is_active: !!r.bot.is_active,
                appointments_enabled: !!r.bot.appointments_enabled,
                appointment_duration_minutes: r.bot.appointment_duration_minutes || 30,
                appointment_buffer_minutes: r.bot.appointment_buffer_minutes || 0,
                appointment_business_hours: r.bot.appointment_business_hours || '',
                appointment_notify_email: r.bot.appointment_notify_email || '',
                appointment_max_days_ahead: r.bot.appointment_max_days_ahead || 30,
                lead_scoring_enabled: !!r.bot.lead_scoring_enabled,
                lead_scoring_prompt: r.bot.lead_scoring_prompt || '',
                custom_voice_provider: r.bot.custom_voice_provider || '',
                custom_voice_id: r.bot.custom_voice_id || '',
            });
        } catch (e) { notify.error('Erreur'); }
        finally { setLoading(false); }
    }, [botId]);
    useEffect(() => { load(); }, [load]);

    const save = async () => {
        setSaving(true);
        try {
            const r = await api.put(`/my/bots/${botId}`, form);
            if (r?.error) notify.error(r.error);
            else { notify.success('Bot mis à jour'); setEditMode(false); load(); }
        } catch (e) { notify.error('Erreur'); }
        finally { setSaving(false); }
    };

    const remove = async () => {
        if (!confirm('Supprimer ce bot ?')) return;
        try {
            const r = await api.del(`/my/bots/${botId}`);
            if (r?.error) notify.error(r.error);
            else { notify.success('Bot supprimé'); onBack(); }
        } catch (e) { notify.error('Erreur'); }
    };

    if (loading) return <div className="page-container"><p>Chargement…</p></div>;
    if (!bot) return <div className="page-container"><p>Bot introuvable</p></div>;

    return (
        <div className="page-container">
            <div className="page-header" style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
                <div>
                    <button className="btn btn-secondary btn-sm" onClick={onBack} style={{ marginBottom: '0.5rem' }}>
                        <Icons.ChevronLeft className="w-4 h-4" /> Retour
                    </button>
                    <h1><Icons.Robot className="w-6 h-6" /> {bot.name}</h1>
                </div>
                <div className="page-actions" style={{ display: 'flex', gap: '0.5rem' }}>
                    <button className="btn btn-secondary btn-sm" onClick={() => {
                        const tok = localStorage.getItem('vocal_my_token') || '';
                        window.open(`https://bots.helvia.app/?token=${encodeURIComponent(tok)}#bot-${botId}/overview`, '_blank', 'noopener');
                    }} title="Ouvrir dans la console dédiée Bot Studio (sidebar complète, Simulator, versioning, A/B)">
                        <Icons.Sparkles className="w-4 h-4" /> Bot Studio
                    </button>
                    {tab === 'config' && !editMode && (
                        <button className="btn btn-primary btn-sm" onClick={() => setEditMode(true)}>
                            <Icons.Edit className="w-4 h-4" /> Modifier
                        </button>
                    )}
                    <button className="btn btn-secondary btn-sm" onClick={remove} style={{ color: 'var(--danger)' }}>
                        <Icons.Trash className="w-4 h-4" /> Supprimer
                    </button>
                </div>
            </div>

            <div className="tabs" style={{ display: 'flex', gap: '0.25rem', borderBottom: '1px solid var(--border)', marginBottom: '1rem', overflowX: 'auto' }}>
                {[
                    { id: 'config', label: 'Configuration', icon: Icons.Settings },
                    { id: 'sources', label: 'Sources', icon: Icons.Globe },
                    { id: 'knowledge', label: 'Connaissances', icon: Icons.Document },
                    { id: 'learnings', label: 'Apprentissages', icon: Icons.Sparkles },
                    { id: 'conversations', label: 'Conversations', icon: Icons.Phone },
                    { id: 'appointments', label: 'Rendez-vous', icon: Icons.Calendar },
                    { id: 'leads', label: 'Lead scoring', icon: Icons.AlertCircle },
                    { id: 'voice', label: 'Voix', icon: Icons.Sparkles },
                ].map(t => (
                    <button key={t.id}
                        onClick={() => { setTab(t.id); if (editMode) { setEditMode(false); load(); } }}
                        className={`tab ${tab === t.id ? 'active' : ''}`}
                        style={{ padding: '0.5rem 0.75rem', border: 'none', background: 'transparent', borderBottom: tab === t.id ? '2px solid var(--primary)' : '2px solid transparent', color: tab === t.id ? 'var(--primary)' : 'var(--text-muted)', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '0.4rem', fontWeight: 500 }}>
                        <t.icon className="w-4 h-4" /> {t.label}
                    </button>
                ))}
            </div>

            {tab === 'config' && (
                <div className="card-flat">
                    <div className="card-flat-content" style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
                        <div className="form-row"><label>Nom</label>
                            <input type="text" disabled={!editMode} value={form.name} onChange={e => setForm(f => ({ ...f, name: e.target.value }))} />
                        </div>
                        <div className="form-row"><label>Description</label>
                            <input type="text" disabled={!editMode} value={form.description} onChange={e => setForm(f => ({ ...f, description: e.target.value }))} />
                        </div>
                        <div className="form-row"><label>Voix</label>
                            <select disabled={!editMode} value={form.voice} onChange={e => setForm(f => ({ ...f, voice: e.target.value }))}>
                                {VOICES.map(v => <option key={v.id} value={v.id}>{v.label}</option>)}
                            </select>
                        </div>
                        <div className="form-row"><label>Langue</label>
                            <select disabled={!editMode} value={form.language} onChange={e => setForm(f => ({ ...f, language: e.target.value }))}>
                                {LANGS.map(l => <option key={l.id} value={l.id}>{l.label}</option>)}
                            </select>
                        </div>
                        <div className="form-row"><label>Message d'accueil</label>
                            <textarea rows={2} disabled={!editMode} value={form.greeting} onChange={e => setForm(f => ({ ...f, greeting: e.target.value }))} />
                        </div>
                        <div className="form-row"><label>Instructions / personnalité</label>
                            <textarea rows={5} disabled={!editMode} value={form.instructions} onChange={e => setForm(f => ({ ...f, instructions: e.target.value }))}
                                placeholder="Tu es un assistant pour [entreprise]. Tu es professionnel, concis. Si tu ne sais pas, tu proposes de transférer à un humain." />
                        </div>
                        <div className="form-row"><label>Numéro de transfert humain</label>
                            <input type="text" disabled={!editMode} value={form.human_transfer_number} onChange={e => setForm(f => ({ ...f, human_transfer_number: e.target.value }))} placeholder="+41 79 ..." />
                        </div>
                        <div className="form-row"><label>Durée max d'appel (secondes)</label>
                            <input type="number" min="60" max="3600" disabled={!editMode} value={form.max_call_duration} onChange={e => setForm(f => ({ ...f, max_call_duration: parseInt(e.target.value || '600') }))} />
                        </div>
                        <label className="toggle-row">
                            <input type="checkbox" disabled={!editMode} checked={form.learn_from_calls} onChange={e => setForm(f => ({ ...f, learn_from_calls: e.target.checked }))} />
                            <span>Apprendre automatiquement des conversations (extraction Q/R)</span>
                        </label>
                        <label className="toggle-row">
                            <input type="checkbox" disabled={!editMode} checked={form.is_active} onChange={e => setForm(f => ({ ...f, is_active: e.target.checked }))} />
                            <span>Bot actif</span>
                        </label>

                        {editMode && (
                            <div style={{ display: 'flex', gap: '0.5rem', justifyContent: 'flex-end', marginTop: '0.5rem' }}>
                                <button className="btn btn-secondary" onClick={() => { setEditMode(false); load(); }} disabled={saving}>Annuler</button>
                                <button className="btn btn-primary" onClick={save} disabled={saving}>{saving ? 'Enregistrement…' : 'Enregistrer'}</button>
                            </div>
                        )}
                    </div>
                </div>
            )}

            {tab === 'sources' && <BotSourcesPanel botId={botId} />}
            {tab === 'knowledge' && <BotKnowledgePanel botId={botId} />}
            {tab === 'learnings' && <BotLearningsPanel botId={botId} />}
            {tab === 'conversations' && <BotConversationsPanel botId={botId} />}
            {tab === 'appointments' && <BotAppointmentsPanel botId={botId} form={form} setForm={setForm} editMode={editMode} setEditMode={setEditMode} saving={saving} onSave={save} onCancel={() => { setEditMode(false); load(); }} />}
            {tab === 'leads' && <BotLeadPanel botId={botId} form={form} setForm={setForm} editMode={editMode} setEditMode={setEditMode} saving={saving} onSave={save} onCancel={() => { setEditMode(false); load(); }} />}
            {tab === 'voice' && <BotVoicePanel form={form} setForm={setForm} editMode={editMode} setEditMode={setEditMode} saving={saving} onSave={save} onCancel={() => { setEditMode(false); load(); }} />}
        </div>
    );
};

// ==================== BOT APPOINTMENTS PANEL ====================
const BotAppointmentsPanel = ({ botId, form, setForm, editMode, setEditMode, saving, onSave, onCancel }) => {
    const { notify } = useApp();
    const [appts, setAppts] = useState([]);
    const [loading, setLoading] = useState(true);
    const load = useCallback(async () => {
        setLoading(true);
        try { const r = await api.get(`/my/bots/${botId}/appointments`); setAppts(r?.appointments || []); }
        catch (e) {} finally { setLoading(false); }
    }, [botId]);
    useEffect(() => { load(); }, [load]);
    return (
        <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
            <div className="card-flat">
                <div className="card-flat-header">
                    <Icons.Calendar className="w-5 h-5" />
                    <h2>Configuration RDV</h2>
                    {!editMode && <button className="btn btn-primary btn-sm" style={{ marginLeft: 'auto' }} onClick={() => setEditMode(true)}>Modifier</button>}
                </div>
                <div className="card-flat-content" style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
                    <label className="toggle-row">
                        <input type="checkbox" disabled={!editMode} checked={form.appointments_enabled}
                            onChange={e => setForm(f => ({ ...f, appointments_enabled: e.target.checked }))} />
                        <span>Le bot peut prendre des rendez-vous (tool calling)</span>
                    </label>
                    {form.appointments_enabled && (
                        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '0.75rem' }}>
                            <div className="form-row"><label>Durée (min)</label><input type="number" min="5" max="240" disabled={!editMode} value={form.appointment_duration_minutes} onChange={e => setForm(f => ({ ...f, appointment_duration_minutes: parseInt(e.target.value) || 30 }))} /></div>
                            <div className="form-row"><label>Pause entre RDV (min)</label><input type="number" min="0" max="60" disabled={!editMode} value={form.appointment_buffer_minutes} onChange={e => setForm(f => ({ ...f, appointment_buffer_minutes: parseInt(e.target.value) || 0 }))} /></div>
                            <div className="form-row"><label>Email de notification</label><input type="email" disabled={!editMode} value={form.appointment_notify_email} onChange={e => setForm(f => ({ ...f, appointment_notify_email: e.target.value }))} placeholder="vous@exemple.ch" /></div>
                            <div className="form-row"><label>Jours max d'avance</label><input type="number" min="1" max="120" disabled={!editMode} value={form.appointment_max_days_ahead} onChange={e => setForm(f => ({ ...f, appointment_max_days_ahead: parseInt(e.target.value) || 30 }))} /></div>
                            <div className="form-row" style={{ gridColumn: '1 / -1' }}>
                                <label>Plages horaires (JSON, ex: {'{"monday":{"open":true,"from":"09:00","to":"18:00"},...}'} - vide = 9h-18h L-V)</label>
                                <textarea rows={3} disabled={!editMode} value={form.appointment_business_hours} onChange={e => setForm(f => ({ ...f, appointment_business_hours: e.target.value }))} />
                            </div>
                        </div>
                    )}
                    {editMode && (
                        <div style={{ display: 'flex', gap: '0.5rem', justifyContent: 'flex-end' }}>
                            <button className="btn btn-secondary" onClick={onCancel} disabled={saving}>Annuler</button>
                            <button className="btn btn-primary" onClick={onSave} disabled={saving}>{saving ? 'Enregistrement…' : 'Enregistrer'}</button>
                        </div>
                    )}
                </div>
            </div>
            <div className="card-flat">
                <div className="card-flat-header"><Icons.Calendar className="w-5 h-5" /><h2>RDV pris ({appts.length})</h2><button className="btn btn-secondary btn-sm" style={{ marginLeft: 'auto' }} onClick={load}><Icons.Refresh className="w-4 h-4" /></button></div>
                <div className="card-flat-content">
                    {loading ? <p>Chargement…</p> : appts.length === 0 ? <p style={{ color: 'var(--text-muted)' }}>Aucun rendez-vous pour l'instant.</p> : (
                        <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '0.875rem' }}>
                            <thead><tr style={{ textAlign: 'left', color: 'var(--text-muted)' }}><th style={{ padding: '0.5rem 0' }}>Date</th><th>Personne</th><th>Téléphone</th><th>Motif</th><th>Statut</th><th></th></tr></thead>
                            <tbody>
                                {appts.map(a => (
                                    <tr key={a.id} style={{ borderTop: '1px solid var(--border)' }}>
                                        <td style={{ padding: '0.5rem 0' }}><strong>{new Date(a.slot_start).toLocaleString('fr-CH', { weekday: 'short', day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' })}</strong></td>
                                        <td>{a.attendee_name}</td>
                                        <td style={{ fontFamily: 'ui-monospace, monospace', color: 'var(--text-muted)' }}>{a.attendee_phone || '-'}</td>
                                        <td style={{ color: 'var(--text-muted)' }}>{a.purpose || '-'}</td>
                                        <td>{a.status}</td>
                                        <td><button className="btn btn-secondary btn-sm" onClick={async () => { if (!confirm('Annuler ce RDV ?')) return; await api.del(`/my/bots/${botId}/appointments/${a.id}`); load(); }}><Icons.Trash className="w-4 h-4" /></button></td>
                                    </tr>
                                ))}
                            </tbody>
                        </table>
                    )}
                </div>
            </div>
        </div>
    );
};

// ==================== BOT LEAD SCORING PANEL ====================
const BotLeadPanel = ({ botId, form, setForm, editMode, setEditMode, saving, onSave, onCancel }) => {
    const { notify } = useApp();
    const [stats, setStats] = useState({ stats: {}, top_leads: [] });
    const [loading, setLoading] = useState(true);
    const load = useCallback(async () => {
        setLoading(true);
        try { const r = await api.get(`/my/bots/${botId}/lead-stats`); setStats(r); }
        catch (e) {} finally { setLoading(false); }
    }, [botId]);
    useEffect(() => { load(); }, [load]);
    return (
        <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
            <div className="card-flat">
                <div className="card-flat-header">
                    <Icons.AlertCircle className="w-5 h-5" />
                    <h2>Configuration Lead Scoring (BANT)</h2>
                    {!editMode && <button className="btn btn-primary btn-sm" style={{ marginLeft: 'auto' }} onClick={() => setEditMode(true)}>Modifier</button>}
                </div>
                <div className="card-flat-content" style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
                    <label className="toggle-row">
                        <input type="checkbox" disabled={!editMode} checked={form.lead_scoring_enabled}
                            onChange={e => setForm(f => ({ ...f, lead_scoring_enabled: e.target.checked }))} />
                        <span>Activer le scoring BANT (Budget / Authority / Need / Timing)</span>
                    </label>
                    {form.lead_scoring_enabled && (
                        <div className="form-row">
                            <label>Prompt personnalisé (optionnel, sinon BANT classique)</label>
                            <textarea rows={4} disabled={!editMode} value={form.lead_scoring_prompt} onChange={e => setForm(f => ({ ...f, lead_scoring_prompt: e.target.value }))}
                                placeholder="Tu analyses la conversation et tu attribues 0-25 points sur chaque critère…" />
                        </div>
                    )}
                    {editMode && (
                        <div style={{ display: 'flex', gap: '0.5rem', justifyContent: 'flex-end' }}>
                            <button className="btn btn-secondary" onClick={onCancel} disabled={saving}>Annuler</button>
                            <button className="btn btn-primary" onClick={onSave} disabled={saving}>{saving ? 'Enregistrement…' : 'Enregistrer'}</button>
                        </div>
                    )}
                </div>
            </div>
            {form.lead_scoring_enabled && (
                <>
                    <div className="card-flat">
                        <div className="card-flat-header"><Icons.AlertCircle className="w-5 h-5" /><h2>Statistiques</h2><button className="btn btn-secondary btn-sm" style={{ marginLeft: 'auto' }} onClick={load}><Icons.Refresh className="w-4 h-4" /></button></div>
                        <div className="card-flat-content">
                            {loading ? <p>Chargement…</p> : (
                                <div className="stats-grid">
                                    <div className="stat-card"><div className="stat-icon orange"><Icons.AlertCircle className="w-6 h-6" /></div><div><div className="stat-label">Total scorés</div><div className="stat-value">{stats.stats?.total || 0}</div></div></div>
                                    <div className="stat-card"><div className="stat-icon" style={{ background: '#fee2e2', color: '#b91c1c' }}><Icons.Sparkles className="w-6 h-6" /></div><div><div className="stat-label">Hot (≥70)</div><div className="stat-value" style={{ color: '#b91c1c' }}>{stats.stats?.hot || 0}</div></div></div>
                                    <div className="stat-card"><div className="stat-icon orange"><Icons.Sparkles className="w-6 h-6" /></div><div><div className="stat-label">Warm (40-69)</div><div className="stat-value" style={{ color: '#f59e0b' }}>{stats.stats?.warm || 0}</div></div></div>
                                    <div className="stat-card"><div className="stat-icon blue"><Icons.Sparkles className="w-6 h-6" /></div><div><div className="stat-label">Cold (&lt;40)</div><div className="stat-value">{stats.stats?.cold || 0}</div></div></div>
                                </div>
                            )}
                        </div>
                    </div>
                    <div className="card-flat">
                        <div className="card-flat-header"><Icons.Sparkles className="w-5 h-5" /><h2>Top leads</h2></div>
                        <div className="card-flat-content">
                            {(stats.top_leads || []).length === 0 ? <p style={{ color: 'var(--text-muted)' }}>Aucun lead scoré.</p> : (
                                <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '0.875rem' }}>
                                    <thead><tr style={{ textAlign: 'left', color: 'var(--text-muted)' }}><th>Téléphone</th><th>Score</th><th>BANT</th><th>Date</th></tr></thead>
                                    <tbody>
                                        {(stats.top_leads || []).map(l => {
                                            let bd = {}; try { bd = JSON.parse(l.lead_breakdown || '{}'); } catch (e) {}
                                            return (
                                                <tr key={l.id} style={{ borderTop: '1px solid var(--border)' }}>
                                                    <td style={{ padding: '0.5rem 0', fontFamily: 'ui-monospace, monospace' }}>{l.from_number}</td>
                                                    <td><strong style={{ color: l.lead_score >= 70 ? '#b91c1c' : l.lead_score >= 40 ? '#f59e0b' : '#475569' }}>{l.lead_score}</strong></td>
                                                    <td style={{ fontSize: '0.75rem', color: 'var(--text-muted)' }}>B:{bd.budget||0} A:{bd.authority||0} N:{bd.need||0} T:{bd.timing||0}</td>
                                                    <td style={{ color: 'var(--text-muted)' }}>{parseUtcDate(l.created_at)?.toLocaleDateString('fr-CH') || '-'}</td>
                                                </tr>
                                            );
                                        })}
                                    </tbody>
                                </table>
                            )}
                        </div>
                    </div>
                </>
            )}
        </div>
    );
};

// ==================== BOT VOICE PANEL ====================
const BotVoicePanel = ({ form, setForm, editMode, setEditMode, saving, onSave, onCancel }) => {
    const customVoices = ['alloy', 'ash', 'ballad', 'coral', 'echo', 'sage', 'shimmer', 'verse'];
    return (
        <div className="card-flat">
            <div className="card-flat-header">
                <Icons.Sparkles className="w-5 h-5" />
                <h2>Voix personnalisée</h2>
                {!editMode && <button className="btn btn-primary btn-sm" style={{ marginLeft: 'auto' }} onClick={() => setEditMode(true)}>Modifier</button>}
            </div>
            <div className="card-flat-content" style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
                <p style={{ color: 'var(--text-muted)', margin: 0, fontSize: '0.875rem' }}>
                    Au-delà des 6 voix par défaut, vous pouvez activer la gamme de voix Vocal étendue. Le clonage de voix sur mesure sera disponible prochainement.
                </p>
                <div className="form-row">
                    <label>Catalogue</label>
                    <select disabled={!editMode} value={form.custom_voice_provider || ''} onChange={e => setForm(f => ({ ...f, custom_voice_provider: e.target.value }))}>
                        <option value="">- Voix par défaut (config standard) -</option>
                        <option value="openai">Vocal Voice — gamme étendue</option>
                    </select>
                </div>
                {form.custom_voice_provider === 'openai' && (
                    <div className="form-row">
                        <label>Voix</label>
                        <select disabled={!editMode} value={form.custom_voice_id || ''} onChange={e => setForm(f => ({ ...f, custom_voice_id: e.target.value }))}>
                            <option value="">- Choisir -</option>
                            {customVoices.map(v => <option key={v} value={v}>{v}</option>)}
                        </select>
                    </div>
                )}
                {editMode && (
                    <div style={{ display: 'flex', gap: '0.5rem', justifyContent: 'flex-end' }}>
                        <button className="btn btn-secondary" onClick={onCancel} disabled={saving}>Annuler</button>
                        <button className="btn btn-primary" onClick={onSave} disabled={saving}>{saving ? 'Enregistrement…' : 'Enregistrer'}</button>
                    </div>
                )}
            </div>
        </div>
    );
};

const BotSourcesPanel = ({ botId }) => {
    const { notify } = useApp();
    const [items, setItems] = useState([]);
    const [loading, setLoading] = useState(true);
    const [busy, setBusy] = useState(false);
    const [form, setForm] = useState({ type: 'url', label: '', source_url: '', source_text: '', refresh_interval_minutes: 1440 });

    const load = useCallback(async () => {
        setLoading(true);
        try { const r = await api.get(`/my/bots/${botId}/sources`); setItems(r?.sources || []); }
        catch (e) {} finally { setLoading(false); }
    }, [botId]);
    useEffect(() => { load(); }, [load]);

    const add = async () => {
        if (form.type === 'url' && !form.source_url.trim()) { notify.error('URL requise'); return; }
        if (form.type === 'text' && !form.source_text.trim()) { notify.error('Texte requis'); return; }
        setBusy(true);
        try {
            const r = await api.post(`/my/bots/${botId}/sources`, form);
            if (r?.error) notify.error(r.error);
            else { notify.success('Source ajoutée'); setForm({ type: 'url', label: '', source_url: '', source_text: '', refresh_interval_minutes: 1440 }); load(); }
        } catch (e) { notify.error('Erreur'); }
        finally { setBusy(false); }
    };

    const refresh = async (id) => {
        try {
            const r = await api.post(`/my/bots/${botId}/sources/${id}/refresh`);
            if (r?.error) notify.error(r.error);
            else { notify.success('Scraping déclenché'); setTimeout(load, 1500); }
        } catch (e) { notify.error('Erreur'); }
    };

    const remove = async (id) => {
        if (!confirm('Supprimer cette source ?')) return;
        try { await api.del(`/my/bots/${botId}/sources/${id}`); load(); } catch (e) {}
    };

    return (
        <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
            <div className="card-flat">
                <div className="card-flat-header"><Icons.Plus className="w-5 h-5" /><h2>Nouvelle source</h2></div>
                <div className="card-flat-content" style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
                    <div className="form-row"><label>Type</label>
                        <select value={form.type} onChange={e => setForm(f => ({ ...f, type: e.target.value }))}>
                            <option value="url">Site web (scraping périodique)</option>
                            <option value="text">Texte libre</option>
                        </select>
                    </div>
                    <div className="form-row"><label>Libellé</label>
                        <input type="text" value={form.label} onChange={e => setForm(f => ({ ...f, label: e.target.value }))} placeholder="ex: Page tarifs" />
                    </div>
                    {form.type === 'url' && (
                        <>
                            <div className="form-row"><label>URL</label>
                                <input type="url" value={form.source_url} onChange={e => setForm(f => ({ ...f, source_url: e.target.value }))} placeholder="https://..." />
                            </div>
                            <div className="form-row"><label>Rafraîchissement (minutes)</label>
                                <input type="number" min="60" value={form.refresh_interval_minutes} onChange={e => setForm(f => ({ ...f, refresh_interval_minutes: parseInt(e.target.value || '1440') }))} />
                            </div>
                        </>
                    )}
                    {form.type === 'text' && (
                        <div className="form-row"><label>Texte</label>
                            <textarea rows={6} value={form.source_text} onChange={e => setForm(f => ({ ...f, source_text: e.target.value }))} placeholder="Coller le contenu à apprendre…" />
                        </div>
                    )}
                    <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
                        <button className="btn btn-primary" onClick={add} disabled={busy}>{busy ? 'Ajout…' : 'Ajouter la source'}</button>
                    </div>
                </div>
            </div>

            <div className="card-flat">
                <div className="card-flat-header"><Icons.Globe className="w-5 h-5" /><h2>Sources existantes</h2></div>
                <div className="card-flat-content">
                    {loading ? <p style={{ color: 'var(--text-muted)' }}>Chargement…</p> :
                     items.length === 0 ? <p style={{ color: 'var(--text-muted)' }}>Aucune source.</p> :
                     <div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
                        {items.map(s => (
                            <div key={s.id} style={{ padding: '0.75rem', border: '1px solid var(--border)', borderRadius: 8, display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
                                <div style={{ flex: 1, minWidth: 0 }}>
                                    <div style={{ display: 'flex', alignItems: 'center', gap: '0.4rem', marginBottom: '0.25rem' }}>
                                        <strong style={{ fontSize: '0.9rem' }}>{s.label || (s.type === 'url' ? s.source_url : 'Texte libre')}</strong>
                                        <span className={`badge ${s.scrape_status === 'ok' ? 'badge-green' : s.scrape_status === 'error' ? 'badge-red' : 'badge-gray'}`} style={{ fontSize: '0.65rem' }}>{s.scrape_status}</span>
                                        {!s.is_active && <span className="badge badge-gray" style={{ fontSize: '0.65rem' }}>inactif</span>}
                                    </div>
                                    {s.source_url && <div style={{ fontSize: '0.75rem', color: 'var(--text-muted)', overflow: 'hidden', textOverflow: 'ellipsis' }}>{s.source_url}</div>}
                                    <div style={{ fontSize: '0.7rem', color: 'var(--text-muted)' }}>
                                        Type: {s.type} · Refresh: {s.refresh_interval_minutes}min
                                        {s.last_scraped_at && ` · Dernier scraping: ${formatDateTime(s.last_scraped_at)}`}
                                    </div>
                                    {s.scrape_error && <div style={{ fontSize: '0.7rem', color: 'var(--danger)' }}>Erreur: {s.scrape_error}</div>}
                                </div>
                                {s.type === 'url' && (
                                    <button className="btn btn-secondary btn-sm" onClick={() => refresh(s.id)} title="Forcer le scraping">
                                        <Icons.Refresh className="w-4 h-4" />
                                    </button>
                                )}
                                <button className="btn btn-secondary btn-sm" onClick={() => remove(s.id)} style={{ color: 'var(--danger)' }} title="Supprimer">
                                    <Icons.Trash className="w-4 h-4" />
                                </button>
                            </div>
                        ))}
                     </div>
                    }
                </div>
            </div>
        </div>
    );
};

const BotKnowledgePanel = ({ botId }) => {
    const { notify } = useApp();
    const [items, setItems] = useState([]);
    const [loading, setLoading] = useState(true);
    const [form, setForm] = useState({ label: '', content: '' });
    const [busy, setBusy] = useState(false);

    const load = useCallback(async () => {
        setLoading(true);
        try { const r = await api.get(`/my/bots/${botId}/knowledge`); setItems(r?.knowledge || []); }
        catch (e) {} finally { setLoading(false); }
    }, [botId]);
    useEffect(() => { load(); }, [load]);

    const add = async () => {
        if (!form.content.trim()) { notify.error('Contenu requis'); return; }
        setBusy(true);
        try {
            const r = await api.post(`/my/bots/${botId}/knowledge`, form);
            if (r?.error) notify.error(r.error);
            else { notify.success('Ajouté'); setForm({ label: '', content: '' }); load(); }
        } catch (e) { notify.error('Erreur'); }
        finally { setBusy(false); }
    };

    const remove = async (id) => {
        if (!confirm('Supprimer ?')) return;
        try { await api.del(`/my/bots/${botId}/knowledge/${id}`); load(); } catch (e) {}
    };

    return (
        <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
            <div className="card-flat">
                <div className="card-flat-header"><Icons.Plus className="w-5 h-5" /><h2>Ajouter une connaissance manuelle</h2></div>
                <div className="card-flat-content" style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
                    <div className="form-row"><label>Libellé</label>
                        <input type="text" value={form.label} onChange={e => setForm(f => ({ ...f, label: e.target.value }))} placeholder="ex: Tarifs 2026" />
                    </div>
                    <div className="form-row"><label>Contenu</label>
                        <textarea rows={5} value={form.content} onChange={e => setForm(f => ({ ...f, content: e.target.value }))} />
                    </div>
                    <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
                        <button className="btn btn-primary" onClick={add} disabled={busy}>{busy ? 'Ajout…' : 'Ajouter'}</button>
                    </div>
                </div>
            </div>

            <div className="card-flat">
                <div className="card-flat-header"><Icons.Document className="w-5 h-5" /><h2>Base de connaissance ({items.length})</h2></div>
                <div className="card-flat-content">
                    {loading ? <p style={{ color: 'var(--text-muted)' }}>Chargement…</p> :
                     items.length === 0 ? <p style={{ color: 'var(--text-muted)' }}>Aucun chunk.</p> :
                     <div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
                        {items.map(k => (
                            <div key={k.id} style={{ padding: '0.75rem', border: '1px solid var(--border)', borderRadius: 8 }}>
                                <div style={{ display: 'flex', alignItems: 'center', gap: '0.4rem', marginBottom: '0.3rem' }}>
                                    {k.label && <strong style={{ fontSize: '0.85rem' }}>{k.label}</strong>}
                                    <span className={`badge ${k.origin === 'manual' ? 'badge-blue' : k.origin === 'scrape' ? 'badge-green' : 'badge-gray'}`} style={{ fontSize: '0.65rem' }}>{k.origin}</span>
                                    <button className="btn btn-secondary btn-sm" style={{ marginLeft: 'auto', color: 'var(--danger)' }} onClick={() => remove(k.id)}>
                                        <Icons.Trash className="w-4 h-4" />
                                    </button>
                                </div>
                                <div style={{ fontSize: '0.8rem', whiteSpace: 'pre-wrap', color: 'var(--text)', maxHeight: 120, overflow: 'auto' }}>{k.content}</div>
                            </div>
                        ))}
                     </div>
                    }
                </div>
            </div>
        </div>
    );
};

const BotLearningsPanel = ({ botId }) => {
    const { notify } = useApp();
    const [items, setItems] = useState([]);
    const [loading, setLoading] = useState(true);
    const [filter, setFilter] = useState('pending');

    const load = useCallback(async () => {
        setLoading(true);
        try { const r = await api.get(`/my/bots/${botId}/learnings?filter=${filter}`); setItems(r?.learnings || []); }
        catch (e) {} finally { setLoading(false); }
    }, [botId, filter]);
    useEffect(() => { load(); }, [load]);

    const validate = async (id, is_validated, is_active = true) => {
        try { await api.put(`/my/bots/${botId}/learnings/${id}`, { is_validated: is_validated ? 1 : 0, is_active: is_active ? 1 : 0 }); load(); } catch (e) {}
    };
    const remove = async (id) => {
        if (!confirm('Supprimer ?')) return;
        try { await api.del(`/my/bots/${botId}/learnings/${id}`); load(); } catch (e) {}
    };

    return (
        <div className="card-flat">
            <div className="card-flat-header">
                <Icons.Sparkles className="w-5 h-5" />
                <h2>Apprentissages automatiques</h2>
                <select value={filter} onChange={e => setFilter(e.target.value)} style={{ marginLeft: 'auto' }}>
                    <option value="pending">À valider</option>
                    <option value="validated">Validés</option>
                    <option value="all">Tous</option>
                </select>
            </div>
            <div className="card-flat-content">
                {loading ? <p style={{ color: 'var(--text-muted)' }}>Chargement…</p> :
                 items.length === 0 ? <p style={{ color: 'var(--text-muted)' }}>Aucun apprentissage.</p> :
                 <div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
                    {items.map(l => (
                        <div key={l.id} style={{ padding: '0.75rem', border: '1px solid var(--border)', borderRadius: 8 }}>
                            <div style={{ marginBottom: '0.4rem' }}>
                                <strong style={{ fontSize: '0.8rem', color: 'var(--text-muted)' }}>Q :</strong>
                                <div style={{ fontSize: '0.9rem' }}>{l.question}</div>
                            </div>
                            <div style={{ marginBottom: '0.4rem' }}>
                                <strong style={{ fontSize: '0.8rem', color: 'var(--text-muted)' }}>R :</strong>
                                <div style={{ fontSize: '0.9rem' }}>{l.answer}</div>
                            </div>
                            <div style={{ display: 'flex', gap: '0.4rem', alignItems: 'center' }}>
                                {!l.is_validated ? (
                                    <button className="btn btn-primary btn-sm" onClick={() => validate(l.id, true, true)}>Valider</button>
                                ) : (
                                    <span className="badge badge-green">Validé</span>
                                )}
                                <button className="btn btn-secondary btn-sm" onClick={() => remove(l.id)} style={{ color: 'var(--danger)', marginLeft: 'auto' }}>
                                    <Icons.Trash className="w-4 h-4" />
                                </button>
                            </div>
                        </div>
                    ))}
                 </div>
                }
            </div>
        </div>
    );
};

const BotConversationsPanel = ({ botId }) => {
    const [items, setItems] = useState([]);
    const [loading, setLoading] = useState(true);
    const [open, setOpen] = useState(null);

    const load = useCallback(async () => {
        setLoading(true);
        try { const r = await api.get(`/my/bots/${botId}/conversations`); setItems(r?.conversations || []); }
        catch (e) {} finally { setLoading(false); }
    }, [botId]);
    useEffect(() => { load(); }, [load]);

    return (
        <div className="card-flat">
            <div className="card-flat-header"><Icons.Phone className="w-5 h-5" /><h2>Conversations ({items.length})</h2></div>
            <div className="card-flat-content">
                {loading ? <p style={{ color: 'var(--text-muted)' }}>Chargement…</p> :
                 items.length === 0 ? <p style={{ color: 'var(--text-muted)' }}>Aucune conversation pour le moment.</p> :
                 <div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
                    {items.map(c => {
                        const isOpen = open === c.id;
                        return (
                            <div key={c.id} style={{ border: '1px solid var(--border)', borderRadius: 8 }}>
                                <button onClick={() => setOpen(isOpen ? null : c.id)}
                                    style={{ width: '100%', padding: '0.75rem', background: 'transparent', border: 'none', textAlign: 'left', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
                                    <Icons.Phone className="w-4 h-4" />
                                    <div style={{ flex: 1 }}>
                                        <div style={{ fontWeight: 600, fontSize: '0.85rem' }}>{c.from_number || '-'} → {c.to_number || '-'}</div>
                                        <div style={{ fontSize: '0.75rem', color: 'var(--text-muted)' }}>
                                            {formatDateTime(c.created_at)} · {formatDuration(c.duration || 0)} · {c.status}
                                        </div>
                                    </div>
                                </button>
                                {isOpen && (
                                    <div style={{ padding: '0.75rem', borderTop: '1px solid var(--border)', background: '#f8fafc' }}>
                                        {c.summary && (
                                            <div style={{ marginBottom: '0.5rem' }}>
                                                <strong style={{ fontSize: '0.75rem', color: 'var(--text-muted)' }}>Résumé</strong>
                                                <div style={{ fontSize: '0.85rem' }}>{c.summary}</div>
                                            </div>
                                        )}
                                        {c.transcript && (
                                            <div>
                                                <strong style={{ fontSize: '0.75rem', color: 'var(--text-muted)' }}>Transcript</strong>
                                                <pre style={{ fontSize: '0.8rem', whiteSpace: 'pre-wrap', fontFamily: 'inherit', margin: 0, marginTop: '0.25rem', maxHeight: 300, overflow: 'auto' }}>{c.transcript}</pre>
                                            </div>
                                        )}
                                    </div>
                                )}
                            </div>
                        );
                    })}
                 </div>
                }
            </div>
        </div>
    );
};

// ==================== BILLING (Crédits) ====================
const BillingView = () => {
    const { notify } = useApp();
    const [balance, setBalance] = useState(null);
    const [transactions, setTransactions] = useState([]);
    const [recharges, setRecharges] = useState([]);
    const [selectedPackId, setSelectedPackId] = useState(null);
    const [loading, setLoading] = useState(true);
    const [customAmount, setCustomAmount] = useState('');
    const [busy, setBusy] = useState(false);
    const [tab, setTab] = useState('packs');

    const load = useCallback(async () => {
        setLoading(true);
        try {
            const [b, t, r] = await Promise.all([
                api.get('/my/billing/balance'),
                api.get('/my/billing/transactions?limit=100'),
                api.get('/my/billing/recharges'),
            ]);
            if (b?.error) notify.error(b.error);
            setBalance(b);
            setTransactions(t?.transactions || []);
            setRecharges(r?.recharges || []);
        } catch (e) { notify.error('Erreur'); }
        finally { setLoading(false); }
    }, [notify]);

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

    const recharge = async (payload) => {
        setBusy(true);
        try {
            const r = await api.post('/my/billing/recharge', payload);
            if (r?.error) { notify.error(r.error); return; }
            if (r.checkout_url) window.location.href = r.checkout_url;
        } catch (e) { notify.error(e.message); }
        finally { setBusy(false); }
    };

    const balanceColor = balance?.balance < (balance?.critical_threshold || 1) ? '#dc2626'
        : balance?.balance < (balance?.low_threshold || 5) ? '#d97706' : '#16a34a';

    const txTypeLabel = (t) => ({
        recharge: 'Recharge', bonus: 'Bonus offert',
        debit_call: 'Appel', debit_sms: 'SMS', debit_whatsapp: 'WhatsApp',
        refund: 'Remboursement', adjustment: 'Ajustement',
    })[t] || t;

    const txTypeBadge = (t) => ({
        recharge: 'badge-success', bonus: 'badge-success',
        debit_call: 'badge-info', debit_sms: 'badge-info', debit_whatsapp: 'badge-info',
        refund: 'badge-warning', adjustment: 'badge-gray',
    })[t] || 'badge-gray';

    return (
        <>
            <div className="page-header">
                <div>
                    <h1 className="page-title">Crédits</h1>
                    <p className="page-subtitle">Rechargez votre wallet pour passer des appels et envoyer des SMS</p>
                </div>
                <div className="page-actions">
                    <button className="btn btn-secondary btn-sm" onClick={load}><Icons.Refresh className="w-4 h-4" /> Actualiser</button>
                </div>
            </div>
            <div className="page-content">
                {/* Balance card */}
                <div className="card" style={{ padding: '2rem', marginBottom: '1.5rem', background: 'linear-gradient(135deg, #eef2ff 0%, #f0f9ff 100%)', border: '1px solid #c7d2fe' }}>
                    <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', flexWrap: 'wrap', gap: '1rem' }}>
                        <div>
                            <div style={{ fontSize: '0.875rem', color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.05em', fontWeight: 600 }}>Solde disponible</div>
                            {loading ? (
                                <Skeleton width={200} height={48} style={{ marginTop: '0.5rem' }} />
                            ) : (
                                <div style={{ fontSize: '3rem', fontWeight: 800, color: balanceColor, marginTop: '0.25rem', lineHeight: 1.1 }}>
                                    {(balance?.balance || 0).toFixed(2)} <span style={{ fontSize: '1.5rem', color: 'var(--text-muted)' }}>{balance?.currency || 'CHF'}</span>
                                </div>
                            )}
                            {balance && balance.balance <= balance.critical_threshold && (
                                <div style={{ marginTop: '0.75rem', display: 'flex', alignItems: 'center', gap: '0.4rem', color: '#dc2626', fontSize: '0.875rem', fontWeight: 600 }}>
                                    <Icons.AlertTriangle className="w-4 h-4" /> Solde critique • rechargez pour éviter toute interruption
                                </div>
                            )}
                            {balance && balance.balance > balance.critical_threshold && balance.balance <= balance.low_threshold && (
                                <div style={{ marginTop: '0.75rem', color: '#d97706', fontSize: '0.875rem', fontWeight: 500 }}>
                                    ⚠ Solde faible • pensez à recharger
                                </div>
                            )}
                        </div>
                        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, auto)', gap: '0.5rem 1.5rem', textAlign: 'right' }}>
                            <div style={{ fontSize: '0.75rem', color: 'var(--text-muted)' }}>Total rechargé</div>
                            <div style={{ fontWeight: 600 }}>{formatCurrency(balance?.total_recharged || 0)}</div>
                            <div style={{ fontSize: '0.75rem', color: 'var(--text-muted)' }}>Total consommé</div>
                            <div style={{ fontWeight: 600 }}>{formatCurrency(balance?.total_consumed || 0)}</div>
                        </div>
                    </div>
                </div>

                <div style={{ display: 'flex', gap: '0.5rem', marginBottom: '1.25rem', borderBottom: '1px solid var(--border)' }}>
                    {[
                        { id: 'packs', label: 'Recharger' },
                        { id: 'transactions', label: 'Mouvements' },
                        { id: 'recharges', label: 'Mes recharges' },
                    ].map(t => (
                        <button key={t.id} onClick={() => setTab(t.id)}
                            style={{ background: 'none', border: 'none', padding: '0.75rem 1rem', cursor: 'pointer', fontSize: '0.875rem', fontWeight: 500, color: tab === t.id ? 'var(--primary)' : 'var(--text-muted)', borderBottom: tab === t.id ? '2px solid var(--primary)' : '2px solid transparent', marginBottom: '-1px' }}>
                            {t.label}
                        </button>
                    ))}
                </div>

                {tab === 'packs' && balance && (
                    <>
                        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(180px, 1fr))', gap: '1rem', marginBottom: '1rem' }}>
                            {(balance.packs || []).map(p => {
                                const popular = p.id === 'pack_45';
                                const selected = selectedPackId === p.id;
                                const total = p.amount + (p.bonus || 0);
                                const borderColor = selected ? 'var(--primary)' : (popular ? 'var(--primary)' : 'var(--border)');
                                return (
                                    <button key={p.id} onClick={() => setSelectedPackId(p.id)} disabled={busy}
                                        style={{
                                            background: selected ? 'rgba(99, 102, 241, 0.06)' : '#fff',
                                            border: `2px solid ${borderColor}`,
                                            borderRadius: '0.75rem',
                                            padding: '1.25rem',
                                            cursor: busy ? 'not-allowed' : 'pointer',
                                            textAlign: 'center',
                                            position: 'relative',
                                            transition: 'all 0.15s',
                                            transform: selected ? 'translateY(-2px)' : 'none',
                                            boxShadow: selected ? '0 8px 24px rgba(99, 102, 241, 0.2)' : 'none',
                                        }}>
                                        {popular && <span style={{ position: 'absolute', top: -10, left: '50%', transform: 'translateX(-50%)', background: 'var(--primary)', color: '#fff', fontSize: '0.65rem', fontWeight: 700, padding: '0.2rem 0.6rem', borderRadius: '999px', whiteSpace: 'nowrap' }}>POPULAIRE</span>}
                                        {selected && <span style={{ position: 'absolute', top: 8, right: 8, color: 'var(--primary)', fontSize: '1.1rem' }}>✓</span>}
                                        <div style={{ fontSize: '2rem', fontWeight: 800, color: 'var(--text-main)' }}>{p.amount}<span style={{ fontSize: '0.85rem', fontWeight: 500, color: 'var(--text-muted)' }}> CHF</span></div>
                                        {p.bonus > 0 && <div style={{ color: '#16a34a', fontWeight: 600, fontSize: '0.8rem', marginTop: '0.4rem' }}>+ {p.bonus} CHF offerts</div>}
                                        <div style={{ marginTop: '0.5rem', fontSize: '0.7rem', color: 'var(--text-muted)' }}>= {total} CHF de crédit</div>
                                    </button>
                                );
                            })}
                        </div>
                        {selectedPackId && (() => {
                            const sel = (balance.packs || []).find(p => p.id === selectedPackId);
                            if (!sel) return null;
                            const total = sel.amount + (sel.bonus || 0);
                            return (
                                <div className="card" style={{ padding: '1.25rem', marginBottom: '1.5rem', background: 'linear-gradient(135deg, #6366f10d, #8b5cf60d)', border: '1px solid var(--primary)' }}>
                                    <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '1rem', flexWrap: 'wrap' }}>
                                        <div>
                                            <div style={{ fontSize: '0.75rem', color: 'var(--text-muted)', textTransform: 'uppercase', fontWeight: 700, letterSpacing: '0.05em', marginBottom: '0.3rem' }}>Pack sélectionné</div>
                                            <div style={{ fontSize: '1.05rem', fontWeight: 700 }}>
                                                {sel.amount} CHF
                                                {sel.bonus > 0 && <span style={{ color: '#16a34a', marginLeft: '0.5rem', fontSize: '0.9rem' }}>+ {sel.bonus} CHF offerts</span>}
                                                <span style={{ color: 'var(--text-muted)', marginLeft: '0.5rem', fontSize: '0.85rem', fontWeight: 500 }}>→ {total} CHF de crédit</span>
                                            </div>
                                        </div>
                                        <div style={{ display: 'flex', gap: '0.5rem' }}>
                                            <button className="btn btn-secondary" onClick={() => setSelectedPackId(null)} disabled={busy}>Annuler</button>
                                            <button className="btn btn-primary" onClick={() => recharge({ pack_id: sel.id })} disabled={busy} style={{ minWidth: 200 }}>
                                                {busy ? 'Redirection…' : `💳 Procéder au paiement (${sel.amount} CHF)`}
                                            </button>
                                        </div>
                                    </div>
                                </div>
                            );
                        })()}
                        <div className="card" style={{ padding: '1.25rem' }}>
                            <h3 style={{ margin: '0 0 0.5rem', fontSize: '0.95rem', fontWeight: 700 }}>Montant personnalisé</h3>
                            <p style={{ margin: '0 0 0.75rem', color: 'var(--text-muted)', fontSize: '0.8rem' }}>Min. 5 CHF • Max. 1000 CHF</p>
                            <div style={{ display: 'flex', gap: '0.5rem' }}>
                                <input type="number" min="5" max="1000" step="5" className="form-input" value={customAmount} onChange={(e) => setCustomAmount(e.target.value)} placeholder="Ex: 30" style={{ flex: 1, marginBottom: 0 }} />
                                <button className="btn btn-primary" disabled={busy || !customAmount || customAmount < 5} onClick={() => recharge({ amount: parseFloat(customAmount) })}>
                                    {busy ? '…' : 'Recharger'}
                                </button>
                            </div>
                        </div>
                    </>
                )}

                {tab === 'transactions' && (
                    <div className="card" style={{ overflow: 'hidden' }}>
                        {loading ? <SkeletonTable rows={8} cols={4} /> : transactions.length === 0 ? (
                            <div style={{ padding: '3rem 2rem', textAlign: 'center', color: 'var(--text-muted)' }}>Aucun mouvement</div>
                        ) : (
                            <div className="table-container">
                                <table className="data-table">
                                    <thead><tr><th>Date</th><th>Type</th><th>Description</th><th style={{ textAlign: 'right' }}>Montant</th><th style={{ textAlign: 'right' }}>Solde après</th></tr></thead>
                                    <tbody>
                                        {transactions.map(t => (
                                            <tr key={t.id}>
                                                <td style={{ fontSize: '0.8rem' }}>{formatDateTime(t.created_at)}</td>
                                                <td><span className={`badge ${txTypeBadge(t.type)}`}>{txTypeLabel(t.type)}</span></td>
                                                <td style={{ fontSize: '0.8rem', color: 'var(--text-muted)', maxWidth: 320, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{t.description || '-'}</td>
                                                <td style={{ textAlign: 'right', fontWeight: 700, fontSize: '0.85rem', color: t.amount >= 0 ? '#16a34a' : '#dc2626', fontFeatureSettings: '"tnum"' }}>{formatAmount(t.amount, t.currency, true)}</td>
                                                <td style={{ textAlign: 'right', fontSize: '0.8rem', fontFeatureSettings: '"tnum"' }}>{formatCurrency(t.balance_after, t.currency)}</td>
                                            </tr>
                                        ))}
                                    </tbody>
                                </table>
                            </div>
                        )}
                    </div>
                )}

                {tab === 'recharges' && (
                    <div className="card" style={{ overflow: 'hidden' }}>
                        {loading ? <SkeletonTable rows={6} cols={5} /> : recharges.length === 0 ? (
                            <div style={{ padding: '3rem 2rem', textAlign: 'center', color: 'var(--text-muted)' }}>Aucune recharge effectuée</div>
                        ) : (
                            <div className="table-container">
                                <table className="data-table">
                                    <thead><tr><th>Date</th><th>Pack</th><th style={{ textAlign: 'right' }}>Payé</th><th style={{ textAlign: 'right' }}>Bonus</th><th style={{ textAlign: 'right' }}>Crédité</th><th>Statut</th></tr></thead>
                                    <tbody>
                                        {recharges.map(r => (
                                            <tr key={r.id}>
                                                <td style={{ fontSize: '0.8rem' }}>{formatDateTime(r.created_at)}</td>
                                                <td style={{ fontSize: '0.8rem' }}>{r.pack_id || 'Custom'}</td>
                                                <td style={{ textAlign: 'right', fontWeight: 600, fontSize: '0.85rem' }}>{formatCurrency(r.amount, r.currency)}</td>
                                                <td style={{ textAlign: 'right', fontSize: '0.8rem', color: '#16a34a', fontWeight: 600 }}>{r.bonus > 0 ? `+${r.bonus.toFixed(2)}` : '-'}</td>
                                                <td style={{ textAlign: 'right', fontWeight: 700, fontSize: '0.85rem' }}>{formatCurrency(r.total_credited, r.currency)}</td>
                                                <td><span className={`badge ${r.status === 'succeeded' ? 'badge-success' : (r.status === 'pending' ? 'badge-warning' : 'badge-danger')}`}>{r.status}</span></td>
                                            </tr>
                                        ))}
                                    </tbody>
                                </table>
                            </div>
                        )}
                    </div>
                )}
            </div>
        </>
    );
};

// ==================== SUBSCRIPTIONS (vue par ligne) ====================
const SubscriptionsView = () => {
    const { notify, navigate } = useApp();
    const [lines, setLines] = useState([]);
    const [subs, setSubs] = useState([]);
    const [loading, setLoading] = useState(true);

    const load = useCallback(async () => {
        setLoading(true);
        try {
            const [l, s] = await Promise.all([api.get('/my/lines'), api.get('/my/subscriptions')]);
            if (l?.error) notify.error(l.error);
            setLines(l?.lines || []);
            setSubs(s?.subscriptions || []);
        } catch (e) { notify.error('Erreur'); }
        finally { setLoading(false); }
    }, [notify]);

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

    const subByLine = useMemo(() => {
        const m = {};
        for (const s of subs) {
            if (s.status === 'canceled') continue;
            if (!m[s.line_id] || parseUtcDate(s.created_at) > parseUtcDate(m[s.line_id].created_at)) m[s.line_id] = s;
        }
        return m;
    }, [subs]);

    const active = lines.filter(l => subByLine[l.id]);
    const inactive = lines.filter(l => !subByLine[l.id]);

    return (
        <>
            <div className="page-header">
                <div>
                    <h1 className="page-title">Abonnements</h1>
                    <p className="page-subtitle">Gérez les abonnements de location de vos numéros</p>
                </div>
                <div className="page-actions">
                    <button className="btn btn-secondary btn-sm" onClick={load}><Icons.Refresh className="w-4 h-4" /> Actualiser</button>
                </div>
            </div>
            <div className="page-content">
                {loading ? <SkeletonCards count={3} columns={1} /> : (
                    <>
                        {active.length > 0 && (
                            <>
                                <h3 style={{ margin: '0 0 0.75rem', fontSize: '0.9rem', fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.05em', color: 'var(--text-muted)' }}>Lignes avec abonnement actif ({active.length})</h3>
                                <div style={{ display: 'grid', gap: '0.75rem', marginBottom: '2rem' }}>
                                    {active.map(l => {
                                        const s = subByLine[l.id];
                                        const isPastDue = s.status === 'past_due';
                                        return (
                                            <div key={l.id} className="card" style={{ padding: '1.25rem', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '1rem', flexWrap: 'wrap', cursor: 'pointer' }}
                                                onClick={() => navigate('line-detail', { lineId: l.id })}>
                                                <div style={{ flex: 1, minWidth: 240 }}>
                                                    <div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', marginBottom: '0.25rem' }}>
                                                        <span style={{ fontWeight: 700, fontFamily: 'ui-monospace, monospace', fontSize: '0.95rem' }}>{l.phone_number}</span>
                                                        {l.label && <span style={{ color: 'var(--text-muted)', fontSize: '0.8rem' }}>· {l.label}</span>}
                                                    </div>
                                                    <div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap' }}>
                                                        <span className={`badge ${s.status === 'active' || s.status === 'trialing' ? 'badge-success' : 'badge-warning'}`}>
                                                            {s.billing_period === 'yearly' ? 'Annuel' : 'Mensuel'} • {s.status}
                                                        </span>
                                                        {s.cancel_at_period_end ? <span className="badge badge-warning">Annulation programmée</span> : null}
                                                        {isPastDue && <span className="badge badge-danger">Paiement échoué</span>}
                                                    </div>
                                                </div>
                                                <div style={{ textAlign: 'right' }}>
                                                    <div style={{ fontWeight: 700, fontSize: '0.95rem' }}>{formatCurrency(s.amount, s.currency)}</div>
                                                    <div style={{ fontSize: '0.75rem', color: 'var(--text-muted)' }}>par {s.billing_period === 'yearly' ? 'an' : 'mois'}</div>
                                                    {s.current_period_end && <div style={{ fontSize: '0.7rem', color: 'var(--text-muted)', marginTop: '0.25rem' }}>Renouv. {formatDate(s.current_period_end)}</div>}
                                                </div>
                                            </div>
                                        );
                                    })}
                                </div>
                            </>
                        )}

                        {inactive.length > 0 && (
                            <>
                                <h3 style={{ margin: '0 0 0.75rem', fontSize: '0.9rem', fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.05em', color: 'var(--text-muted)' }}>Lignes sans abonnement ({inactive.length})</h3>
                                <div style={{ display: 'grid', gap: '0.75rem' }}>
                                    {inactive.map(l => (
                                        <div key={l.id} className="card" style={{ padding: '1.25rem', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '1rem', flexWrap: 'wrap' }}>
                                            <div>
                                                <div style={{ fontWeight: 700, fontFamily: 'ui-monospace, monospace', fontSize: '0.95rem' }}>{l.phone_number}</div>
                                                {l.label && <div style={{ color: 'var(--text-muted)', fontSize: '0.8rem' }}>{l.label}</div>}
                                            </div>
                                            <button className="btn btn-primary btn-sm" onClick={() => navigate('line-detail', { lineId: l.id })}>
                                                <Icons.Plus className="w-4 h-4" /> Souscrire
                                            </button>
                                        </div>
                                    ))}
                                </div>
                            </>
                        )}

                        {lines.length === 0 && (
                            <div className="card" style={{ padding: '4rem 2rem', textAlign: 'center' }}>
                                <Icons.Card className="w-12 h-12" style={{ margin: '0 auto 1rem', opacity: 0.4, color: 'var(--text-muted)' }} />
                                <p style={{ margin: 0, color: 'var(--text-muted)' }}>Vous n'avez pas encore de ligne. Contactez Vocal pour en obtenir une.</p>
                            </div>
                        )}
                    </>
                )}
            </div>
        </>
    );
};

// ==================== CALLS ====================
const CallsView = () => {
    const { notify, user } = useApp();
    const [calls, setCalls] = useState({ calls: [], total: 0, page: 1, total_pages: 1 });
    const [lines, setLines] = useState([]);
    const [loading, setLoading] = useState(true);
    const [refreshing, setRefreshing] = useState(false);
    const [page, setPage] = useState(1);
    const [filterLine, setFilterLine] = useState('');
    const [filterStatus, setFilterStatus] = useState('');
    const [filterDirection, setFilterDirection] = useState('');
    const [search, setSearch] = useState('');
    const debouncedSearch = useDebouncedValue(search, 350);
    const [expanded, setExpanded] = useState(null);
    const reqIdRef = useRef(0);
    const initialRef = useRef(true);

    const buildUrl = useCallback((p) => {
        const params = new URLSearchParams();
        params.set('page', String(p));
        params.set('limit', '30');
        if (filterLine) params.set('line_id', filterLine);
        if (filterStatus) params.set('status', filterStatus);
        if (filterDirection) params.set('direction', filterDirection);
        if (debouncedSearch.trim()) params.set('q', debouncedSearch.trim());
        return `/my/calls?${params.toString()}`;
    }, [filterLine, filterStatus, filterDirection, debouncedSearch]);

    const load = useCallback(async (p, opts = {}) => {
        const id = ++reqIdRef.current;
        if (opts.background) setRefreshing(true); else setLoading(true);
        try {
            const data = await api.get(buildUrl(p));
            if (id !== reqIdRef.current) return;
            if (data?.error) notify.error(data.error);
            setCalls(data || { calls: [], total: 0, page: 1, total_pages: 1 });
        } catch (e) { if (id === reqIdRef.current) notify.error('Erreur chargement appels'); }
        finally { if (id === reqIdRef.current) { setLoading(false); setRefreshing(false); } }
    }, [buildUrl, notify]);

    useEffect(() => {
        (async () => {
            try { const data = await api.get('/my/lines'); setLines(data?.lines || []); } catch (e) {}
        })();
    }, []);

    useEffect(() => {
        if (page !== 1) { setPage(1); return; }
        load(1);
    }, [filterLine, filterStatus, filterDirection, debouncedSearch]);

    useEffect(() => { load(page, { background: !initialRef.current }); initialRef.current = false; }, [page]);

    // Auto-ouvre un appel si ?call=<id> est dans l'URL (ex. depuis email transcription).
    useEffect(() => {
        try {
            const sp = new URLSearchParams(window.location.search);
            const callId = sp.get('call');
            if (callId) {
                const id = parseInt(callId);
                if (!Number.isNaN(id)) {
                    setExpanded(id);
                    // Nettoie l'URL pour ne pas répéter au refresh.
                    sp.delete('call');
                    sp.delete('view');
                    const qs = sp.toString();
                    window.history.replaceState({}, '', window.location.pathname + (qs ? '?' + qs : '') + window.location.hash);
                    // Scroll vers la card quand elle apparaît.
                    setTimeout(() => {
                        const el = document.querySelector(`[data-call-id="${id}"]`);
                        if (el && el.scrollIntoView) el.scrollIntoView({ behavior: 'smooth', block: 'center' });
                    }, 600);
                }
            }
        } catch {}
    }, []);

    const visible = calls.calls || [];
    const handlePage = (p) => { if (p >= 1 && p <= calls.total_pages) setPage(p); };

    return (
        <>
            <div className="page-header">
                <div>
                    <h1 className="page-title">Appels</h1>
                    <p className="page-subtitle">{calls.total} appels au total</p>
                </div>
                <div className="page-actions">
                    <button className="btn btn-secondary btn-sm" onClick={() => load(page, { background: true })} disabled={refreshing}>
                        <Icons.Refresh className="w-4 h-4" /> {refreshing ? 'Mise à jour...' : 'Actualiser'}
                    </button>
                </div>
            </div>
            <div className="page-content">
                {(user?.client?.level ?? 1) >= 2 && (
                    <div className="card-flat" style={{ marginBottom: '1rem', background: 'linear-gradient(135deg, #fdfdff, #f5f3ff)', borderColor: 'var(--primary-light)' }}>
                        <div className="card-flat-content" style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
                            <Icons.Phone className="w-5 h-5" style={{ color: 'var(--primary)' }} />
                            <div style={{ flex: 1, fontSize: '0.85rem' }}>
                                <strong>Live Center</strong> — supervisez vos appels en temps réel sur <code>center.helvia.app</code> : tableau live, indicateurs en direct, et historique consolidé pour votre équipe.
                            </div>
                            <button className="btn btn-primary btn-sm" onClick={() => window.open('https://center.helvia.app', '_blank', 'noopener')}>
                                Ouvrir Live Center <Icons.ChevronRight className="w-4 h-4" />
                            </button>
                        </div>
                    </div>
                )}
                <div className="card" style={{ overflow: 'hidden' }}>
                    <div style={{ padding: '1rem 1.25rem', borderBottom: '1px solid var(--border)', display: 'flex', gap: '0.75rem', flexWrap: 'wrap', alignItems: 'center' }}>
                        <div style={{ position: 'relative', flex: '1 1 200px', minWidth: '160px' }}>
                            <Icons.Search className="w-4 h-4" style={{ position: 'absolute', left: '0.75rem', top: '50%', transform: 'translateY(-50%)', color: 'var(--text-muted)' }} />
                            <input className="form-input" placeholder="Rechercher numéro ou référence (CAxxxx…)" value={search} onChange={(e) => setSearch(e.target.value)}
                                style={{ paddingLeft: '2.25rem', marginBottom: 0 }} />
                        </div>
                        <select className="form-input" style={{ flex: '0 0 auto', width: 'auto', minWidth: '160px', marginBottom: 0 }}
                            value={filterLine} onChange={(e) => setFilterLine(e.target.value)}>
                            <option value="">Toutes mes lignes</option>
                            {lines.map(l => <option key={l.id} value={l.id}>{l.phone_number} {l.label ? `(${l.label})` : ''}</option>)}
                        </select>
                        <select className="form-input" style={{ flex: '0 0 auto', width: 'auto', minWidth: '140px', marginBottom: 0 }}
                            value={filterStatus} onChange={(e) => setFilterStatus(e.target.value)}>
                            <option value="">Tous statuts</option>
                            <option value="missed">Manqués</option>
                            <option value="transfer_failed">Transferts manqués</option>
                            {Object.entries(CALL_STATUS).map(([k, v]) => <option key={k} value={k}>{v.label}</option>)}
                        </select>
                        <select className="form-input" style={{ flex: '0 0 auto', width: 'auto', minWidth: '130px', marginBottom: 0 }}
                            value={filterDirection} onChange={(e) => setFilterDirection(e.target.value)}>
                            <option value="">Entrants + Sortants</option>
                            <option value="inbound">Entrants</option>
                            <option value="outbound">Sortants</option>
                        </select>
                    </div>

                    {loading ? (
                        <SkeletonTable rows={10} cols={7} />
                    ) : visible.length === 0 ? (
                        <div style={{ padding: '4rem 2rem', textAlign: 'center' }}>
                            <Icons.Phone className="w-10 h-10" style={{ margin: '0 auto 0.75rem', opacity: 0.4, color: 'var(--text-muted)' }} />
                            <p style={{ fontWeight: 600, fontSize: '0.95rem', margin: '0 0 0.25rem' }}>Aucun appel trouvé</p>
                            <p style={{ fontSize: '0.8rem', color: 'var(--text-muted)', margin: 0 }}>Ajustez vos filtres ou attendez de nouveaux appels</p>
                        </div>
                    ) : (
                        <div className="table-container">
                            <table className="data-table">
                                <thead>
                                    <tr>
                                        <th style={{ width: 36 }}></th>
                                        <th>Date</th>
                                        <th>Appelant</th>
                                        <th>Destination</th>
                                        <th>Ligne</th>
                                        <th>Statut</th>
                                        <th style={{ textAlign: 'right' }}>Durée</th>
                                        <th style={{ textAlign: 'right' }}>Coût</th>
                                    </tr>
                                </thead>
                                <tbody style={{ opacity: refreshing ? 0.6 : 1, transition: 'opacity 0.15s ease' }}>
                                    {visible.map(call => {
                                        const isTransferFailed = call.call_outcome === 'transfer_failed';
                                        const st = isTransferFailed
                                            ? { label: 'Transfert manqué', color: 'badge-warning' }
                                            : (CALL_STATUS[call.status] || { label: call.status, color: 'badge-gray' });
                                        const isExp = expanded === call.id;
                                        return (
                                            <Fragment key={call.id}>
                                                <tr data-call-id={call.id} onClick={() => setExpanded(isExp ? null : call.id)} style={{ cursor: 'pointer' }}>
                                                    <td style={{ textAlign: 'center' }}><CallDirectionIcon direction={call.direction} /></td>
                                                    <td>
                                                        <div style={{ fontWeight: 500, fontSize: '0.8rem' }}>{formatDateShort(call.created_at)}</div>
                                                        <div style={{ fontSize: '0.7rem', color: 'var(--text-muted)' }}>{formatTimeOnly(call.created_at)}</div>
                                                    </td>
                                                    <td style={{ fontFamily: 'ui-monospace, monospace', fontSize: '0.8rem', fontWeight: 600 }}>{call.from_number || '-'}</td>
                                                    <td style={{ fontFamily: 'ui-monospace, monospace', fontSize: '0.8rem', color: 'var(--text-muted)' }}>{call.to_number || '-'}</td>
                                                    <td style={{ fontSize: '0.8rem' }}>{call.line_label || call.line_phone || '-'}</td>
                                                    <td><div style={{ display: 'flex', alignItems: 'center', gap: '0.4rem' }}>
                                                        {isTransferFailed
                                                            ? <span title="Transfert non abouti (no-answer ou occupé)" style={{ display: 'inline-block', width: 8, height: 8, borderRadius: '50%', background: '#f59e0b' }} />
                                                            : <CallStatusDot status={call.status} />}
                                                        <span style={{ fontSize: '0.8rem' }}>{st.label}</span>
                                                    </div></td>
                                                    <td style={{ textAlign: 'right', fontWeight: 600, fontSize: '0.85rem', fontFeatureSettings: '"tnum"' }}>{formatDuration(call.duration)}</td>
                                                    <td style={{ textAlign: 'right', fontSize: '0.8rem' }}>
                                                        {call.is_price_synced ? (call.billed_price > 0 ? formatCurrency(call.billed_price, call.billed_currency || 'CHF') : 'Gratuit') : <span style={{ color: 'var(--text-muted)', fontSize: '0.75rem' }}>-</span>}
                                                    </td>
                                                </tr>
                                                {isExp && (
                                                    <tr>
                                                        <td colSpan="8" style={{ padding: 0 }}>
                                                            <div style={{ background: '#f8fafc', padding: '1rem 1.5rem', display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))', gap: '1rem', fontSize: '0.8rem', borderTop: '1px solid var(--border)', borderBottom: '1px solid var(--border)' }}>
                                                                <div>
                                                                    <div style={{ color: 'var(--text-muted)', fontSize: '0.7rem', textTransform: 'uppercase', fontWeight: 600 }}>Direction</div>
                                                                    <div>{call.direction === 'outbound' ? 'Sortant' : 'Entrant'}</div>
                                                                </div>
                                                                <div>
                                                                    <div style={{ color: 'var(--text-muted)', fontSize: '0.7rem', textTransform: 'uppercase', fontWeight: 600 }}>Date complète</div>
                                                                    <div>{formatDateTime(call.created_at)}</div>
                                                                </div>
                                                                <div>
                                                                    <div style={{ color: 'var(--text-muted)', fontSize: '0.7rem', textTransform: 'uppercase', fontWeight: 600 }}>Identifiant</div>
                                                                    <div style={{ fontFamily: 'monospace', fontSize: '0.7rem', wordBreak: 'break-all' }}>{call.call_sid || '-'}</div>
                                                                </div>
                                                                {call.is_price_synced && call.billed_price > 0 && (
                                                                    <div style={{ gridColumn: '1 / -1', background: '#fff', border: '1px solid var(--border)', borderRadius: 8, padding: '0.75rem' }}>
                                                                        <div style={{ display: 'flex', alignItems: 'center', gap: '0.4rem', marginBottom: '0.5rem' }}>
                                                                            <Icons.Wallet className="w-4 h-4" />
                                                                            <strong style={{ fontSize: '0.75rem' }}>Détail du coût</strong>
                                                                        </div>
                                                                        <div style={{ display: 'flex', flexDirection: 'column', gap: '0.35rem', fontSize: '0.78rem' }}>
                                                                            {call.billed_voice_price > 0 && (
                                                                                <div style={{ display: 'flex', justifyContent: 'space-between' }}>
                                                                                    <span>Appel ({formatDuration(call.duration)})</span>
                                                                                    <span style={{ fontFeatureSettings: '"tnum"' }}>{formatCurrency(call.billed_voice_price, call.billed_currency || 'CHF')}</span>
                                                                                </div>
                                                                            )}
                                                                            {call.billed_recording_price > 0 && (
                                                                                <div style={{ display: 'flex', justifyContent: 'space-between' }}>
                                                                                    <span>Enregistrement MP3</span>
                                                                                    <span style={{ fontFeatureSettings: '"tnum"' }}>{formatCurrency(call.billed_recording_price, call.billed_currency || 'CHF')}</span>
                                                                                </div>
                                                                            )}
                                                                            {call.billed_transcription_price > 0 && (
                                                                                <div style={{ display: 'flex', justifyContent: 'space-between' }}>
                                                                                    <span>Transcription IA</span>
                                                                                    <span style={{ fontFeatureSettings: '"tnum"' }}>{formatCurrency(call.billed_transcription_price, call.billed_currency || 'CHF')}</span>
                                                                                </div>
                                                                            )}
                                                                            <div style={{ display: 'flex', justifyContent: 'space-between', borderTop: '1px solid var(--border)', paddingTop: '0.35rem', fontWeight: 700 }}>
                                                                                <span>Total</span>
                                                                                <span style={{ fontFeatureSettings: '"tnum"' }}>{formatCurrency(call.billed_price, call.billed_currency || 'CHF')}</span>
                                                                            </div>
                                                                        </div>
                                                                    </div>
                                                                )}
                                                                {call.recording_url && (
                                                                    <div style={{ gridColumn: '1 / -1' }}>
                                                                        <div style={{ color: 'var(--text-muted)', fontSize: '0.7rem', textTransform: 'uppercase', fontWeight: 600, marginBottom: '0.3rem' }}>Enregistrement</div>
                                                                        <audio controls preload="none" src={call.recording_url} style={{ width: '100%', maxWidth: 500, height: 36 }} />
                                                                        <div style={{ marginTop: '0.25rem' }}>
                                                                            <a href={call.recording_url} target="_blank" rel="noreferrer" style={{ fontSize: '0.7rem', color: 'var(--text-muted)' }}>Télécharger MP3</a>
                                                                        </div>
                                                                    </div>
                                                                )}
                                                                {call.ai_summary && (
                                                                    <div style={{ gridColumn: '1 / -1', background: '#fff', border: '1px solid var(--border)', borderRadius: 8, padding: '0.75rem' }}>
                                                                        <div style={{ display: 'flex', alignItems: 'center', gap: '0.4rem', marginBottom: '0.3rem' }}>
                                                                            <Icons.Sparkles className="w-4 h-4" />
                                                                            <strong style={{ fontSize: '0.75rem' }}>Résumé IA</strong>
                                                                            {call.ai_sentiment && (
                                                                                <span className={`badge ${call.ai_sentiment === 'positive' ? 'badge-green' : call.ai_sentiment === 'negative' ? 'badge-red' : 'badge-gray'}`} style={{ fontSize: '0.65rem' }}>{call.ai_sentiment}</span>
                                                                            )}
                                                                            {call.ai_intent && (
                                                                                <span className="badge badge-blue" style={{ fontSize: '0.65rem' }}>{call.ai_intent}</span>
                                                                            )}
                                                                        </div>
                                                                        <div style={{ fontSize: '0.8rem', whiteSpace: 'pre-wrap', lineHeight: 1.4 }}>{call.ai_summary}</div>
                                                                        {call.ai_tags && (() => {
                                                                            try {
                                                                                const tags = JSON.parse(call.ai_tags);
                                                                                if (Array.isArray(tags) && tags.length) {
                                                                                    return (
                                                                                        <div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.25rem', marginTop: '0.4rem' }}>
                                                                                            {tags.map((t, i) => <span key={i} className="badge badge-gray" style={{ fontSize: '0.65rem' }}>{t}</span>)}
                                                                                        </div>
                                                                                    );
                                                                                }
                                                                            } catch (e) {}
                                                                            return null;
                                                                        })()}
                                                                    </div>
                                                                )}
                                                                {call.transcription_text && (
                                                                    <div style={{ gridColumn: '1 / -1', background: '#fff', border: '1px solid var(--border)', borderRadius: 8, padding: '0.75rem' }}>
                                                                        <div style={{ display: 'flex', alignItems: 'center', gap: '0.4rem', marginBottom: '0.3rem' }}>
                                                                            <Icons.Document className="w-4 h-4" />
                                                                            <strong style={{ fontSize: '0.75rem' }}>Transcription</strong>
                                                                        </div>
                                                                        <div style={{ fontSize: '0.8rem', whiteSpace: 'pre-wrap', lineHeight: 1.5, color: 'var(--text)' }}>{call.transcription_text}</div>
                                                                    </div>
                                                                )}
                                                            </div>
                                                        </td>
                                                    </tr>
                                                )}
                                            </Fragment>
                                        );
                                    })}
                                </tbody>
                            </table>
                        </div>
                    )}

                    {calls.total_pages > 1 && (
                        <div style={{ padding: '0.75rem 1.25rem', borderTop: '1px solid var(--border)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', background: '#f8fafc' }}>
                            <span style={{ fontSize: '0.8rem', color: 'var(--text-muted)' }}>{visible.length} sur {calls.total} appels</span>
                            <div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
                                <button className="btn btn-secondary btn-sm" onClick={() => handlePage(page - 1)} disabled={page <= 1} style={{ padding: '0.3rem 0.6rem' }}><Icons.ChevronLeft className="w-4 h-4" /></button>
                                <span style={{ fontSize: '0.8rem', fontWeight: 600, minWidth: '80px', textAlign: 'center' }}>{page} / {calls.total_pages}</span>
                                <button className="btn btn-secondary btn-sm" onClick={() => handlePage(page + 1)} disabled={page >= calls.total_pages} style={{ padding: '0.3rem 0.6rem' }}><Icons.ChevronRight className="w-4 h-4" /></button>
                            </div>
                        </div>
                    )}
                </div>
            </div>
        </>
    );
};

// ==================== SMS ====================
// ==================== WHATSAPP — wrapper avec onglets Messages / Comptes Business ====================
// Aiguillage selon le `view` courant (sous-items du sidebar).
// 'whatsapp' (parent) ouvre les Conversations par défaut.
const WhatsappRouter = ({ subview = 'inbox' }) => {
    if (subview === 'messages') return <MessagesView kind="whatsapp" />;
    if (subview === 'senders')  return <WhatsappSendersView />;
    return <WhatsappInboxView />;
};

// ==================== WHATSAPP INBOX ====================
// Layout 2 colonnes : liste conversations à gauche, fil sélectionné à droite.
// Réutilise nos endpoints /my/whatsapp/conversations[/:id/{messages,reply,bot}].
// Formate un prix Twilio (généralement USD) pour affichage compact.
const fmtWaMsgPrice = (price, unit) => {
    if (price === null || price === undefined) return null;
    const v = parseFloat(price);
    if (Number.isNaN(v)) return null;
    return `${v.toFixed(4)} ${unit || 'USD'}`;
};

const WhatsappInboxView = () => {
    const { notify } = useApp();
    const [conversations, setConversations] = useState([]);
    const [loading, setLoading] = useState(true);
    const [selectedId, setSelectedId] = useState(null);
    const [messages, setMessages] = useState([]);
    const [selected, setSelected] = useState(null);
    const [totals, setTotals] = useState(null);
    const [loadingMessages, setLoadingMessages] = useState(false);
    const [draft, setDraft] = useState('');
    const [sending, setSending] = useState(false);
    const [takeOver, setTakeOver] = useState(false);
    const [lastRefresh, setLastRefresh] = useState(null);
    const scrollRef = useRef(null);
    // Mémorise la conversation actuellement chargée pour décider si on doit
    // re-scroller en bas (changement) ou non (simple refresh polling).
    const lastLoadedConvRef = useRef(null);
    // Garde l'ID du dernier message connu pour scroller seulement si nouveau.
    const lastMessageIdRef = useRef(null);

    // `silent=true` quand c'est un refresh automatique (polling) : pas de spinner,
    // pas de toast d'erreur intrusif, mise à jour douce.
    const loadConversations = useCallback(async (silent = false) => {
        if (!silent) setLoading(true);
        try {
            const r = await api.get('/my/whatsapp/conversations');
            if (r?.error) { if (!silent) notify.error(r.error); return; }
            const next = r.conversations || [];
            setConversations(prev => {
                // Évite un re-render si rien n'a changé (compare ids + last_message_at + unread)
                if (prev.length === next.length) {
                    const same = prev.every((p, i) =>
                        p.id === next[i].id &&
                        p.last_message_at === next[i].last_message_at &&
                        p.unread_count === next[i].unread_count &&
                        p.bot_enabled === next[i].bot_enabled
                    );
                    if (same) return prev;
                }
                return next;
            });
            if (!selectedId && next.length > 0) setSelectedId(next[0].id);
            setLastRefresh(new Date());
        } catch (e) { if (!silent) notify.error('Erreur chargement conversations'); }
        finally { if (!silent) setLoading(false); }
    }, [notify, selectedId]);

    const loadMessages = useCallback(async (convId, silent = false) => {
        if (!convId) return;
        if (!silent) setLoadingMessages(true);
        try {
            const r = await api.get(`/my/whatsapp/conversations/${convId}/messages`);
            if (r?.error) { if (!silent) notify.error(r.error); return; }
            const nextMsgs = r.messages || [];
            const convChanged = lastLoadedConvRef.current !== convId;
            const lastMsg = nextMsgs[nextMsgs.length - 1];
            const newLastId = lastMsg?.id || null;
            const hasNewMessage = newLastId && newLastId !== lastMessageIdRef.current;

            setMessages(prev => {
                // Refresh polling sans changement : on garde l'array de référence
                // (évite re-render -> évite saut).
                if (!convChanged && prev.length === nextMsgs.length) {
                    const same = prev.every((p, i) =>
                        p.id === nextMsgs[i].id &&
                        p.status === nextMsgs[i].status &&
                        p.body === nextMsgs[i].body
                    );
                    if (same) return prev;
                }
                return nextMsgs;
            });
            setSelected(prev => {
                const next = r.conversation || null;
                if (!next) return prev;
                if (prev && prev.id === next.id && prev.bot_enabled === next.bot_enabled && prev.contact_name === next.contact_name) return prev;
                return next;
            });
            setTotals(r.totals || null);
            setTakeOver(r.conversation?.bot_enabled === 0);
            setLastRefresh(new Date());

            // Scroll automatique uniquement si on a changé de conversation OU
            // un nouveau message est arrivé. Et seulement si l'utilisateur était
            // déjà près du bas (à 100px du fond), sinon on respecte sa position.
            if (convChanged || hasNewMessage) {
                setTimeout(() => {
                    const el = scrollRef.current; if (!el) return;
                    const nearBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 100;
                    if (convChanged || nearBottom) {
                        el.scrollTop = el.scrollHeight;
                    }
                }, 50);
            }
            lastLoadedConvRef.current = convId;
            lastMessageIdRef.current = newLastId;
        } catch (e) { if (!silent) notify.error('Erreur chargement messages'); }
        finally { if (!silent) setLoadingMessages(false); }
    }, [notify]);

    useEffect(() => { loadConversations(); }, []);
    useEffect(() => { if (selectedId) loadMessages(selectedId); }, [selectedId, loadMessages]);

    useEffect(() => {
        const id = setInterval(() => {
            loadConversations(true);
            if (selectedId) loadMessages(selectedId, true);
        }, 15000);
        return () => clearInterval(id);
    }, [loadConversations, loadMessages, selectedId]);

    const sendReply = async () => {
        const text = draft.trim();
        if (!text || !selectedId) return;
        setSending(true);
        try {
            const r = await api.post(`/my/whatsapp/conversations/${selectedId}/reply`, {
                body: text, take_over: takeOver ? 1 : 0,
            });
            if (r?.error) { notify.error(r.error); return; }
            setDraft('');
            await loadMessages(selectedId);
            await loadConversations();
        } catch (e) { notify.error('Erreur envoi'); }
        finally { setSending(false); }
    };

    const toggleBot = async (enabled) => {
        if (!selectedId) return;
        try {
            const r = await api.post(`/my/whatsapp/conversations/${selectedId}/bot`, { enabled: enabled ? 1 : 0 });
            if (r?.error) { notify.error(r.error); return; }
            setTakeOver(!enabled);
            setSelected(s => s ? { ...s, bot_enabled: enabled ? 1 : 0 } : s);
            notify.success(enabled ? 'Bot IA réactivé' : 'Bot désactivé pour cette conversation');
        } catch (e) { notify.error('Erreur'); }
    };

    const fmtTime = (s) => {
        if (!s) return '';
        try {
            const d = new Date(s.endsWith('Z') ? s : s + 'Z');
            const today = new Date();
            const sameDay = d.toDateString() === today.toDateString();
            return sameDay
                ? d.toLocaleTimeString('fr-CH', { hour: '2-digit', minute: '2-digit' })
                : d.toLocaleString('fr-CH', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' });
        } catch { return s; }
    };

    return (
        <div style={{ padding: '1rem 1.5rem', display: 'grid', gridTemplateColumns: 'minmax(280px, 360px) 1fr', gap: '1rem', height: '100vh', boxSizing: 'border-box' }}>
            {/* Liste conversations */}
            <div className="card" style={{ overflow: 'hidden', display: 'flex', flexDirection: 'column' }}>
                <div style={{ padding: '0.75rem 1rem', borderBottom: '1px solid var(--border)', display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: '0.5rem' }}>
                    <strong>Conversations</strong>
                    <div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
                        {lastRefresh && (
                            <span style={{ fontSize: '0.7rem', color: 'var(--text-secondary)' }} title="Dernière mise à jour automatique">
                                ↻ {lastRefresh.toLocaleTimeString('fr-CH', { hour: '2-digit', minute: '2-digit', second: '2-digit' })}
                            </span>
                        )}
                        <button className="btn btn-sm btn-secondary" onClick={() => loadConversations(false)}>↻</button>
                    </div>
                </div>
                <div style={{ overflowY: 'auto', flex: 1 }}>
                    {loading && <div style={{ padding: '1rem', color: 'var(--text-secondary)' }}>Chargement…</div>}
                    {!loading && conversations.length === 0 && (
                        <div style={{ padding: '1.5rem', color: 'var(--text-secondary)', fontSize: '0.9rem' }}>
                            Aucune conversation pour l'instant. Quand un client vous écrira sur WhatsApp, elle apparaîtra ici.
                        </div>
                    )}
                    {conversations.map(c => {
                        const isSel = selectedId === c.id;
                        return (
                        <div key={c.id}
                             onClick={() => setSelectedId(c.id)}
                             style={{
                                padding: '0.75rem 1rem', cursor: 'pointer',
                                borderBottom: '1px solid var(--border)',
                                background: isSel ? 'linear-gradient(135deg, #dbeafe, #bfdbfe)' : 'transparent',
                                borderLeft: isSel ? '3px solid #3b82f6' : '3px solid transparent',
                             }}>
                            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: '0.5rem' }}>
                                <strong style={{ fontSize: '0.9rem' }}>{c.contact_name || c.contact_number}</strong>
                                <span style={{ fontSize: '0.75rem', color: 'var(--text-secondary)' }}>{fmtTime(c.last_message_at)}</span>
                            </div>
                            <div style={{ fontSize: '0.8rem', color: 'var(--text-secondary)', marginTop: '0.15rem', display: 'flex', justifyContent: 'space-between', gap: '0.5rem' }}>
                                <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flex: 1 }}>
                                    {c.last_direction === 'outbound' ? '→ ' : ''}{c.last_message_body || '(média)'}
                                </span>
                                {c.unread_count > 0 && (
                                    <span className="badge badge-primary" style={{ fontSize: '0.7rem' }}>{c.unread_count}</span>
                                )}
                            </div>
                            <div style={{ fontSize: '0.7rem', color: 'var(--text-secondary)', marginTop: '0.15rem' }}>
                                via {c.line_label || c.line_phone}
                                {c.bot_enabled === 0 && <span style={{ marginLeft: '0.5rem', color: 'var(--warning)' }}>· bot off</span>}
                            </div>
                        </div>
                        );
                    })}
                </div>
            </div>

            {/* Fil de discussion */}
            <div className="card" style={{ overflow: 'hidden', display: 'flex', flexDirection: 'column' }}>
                {!selected && (
                    <div style={{ padding: '2rem', color: 'var(--text-secondary)', textAlign: 'center', margin: 'auto' }}>
                        Sélectionnez une conversation pour la lire.
                    </div>
                )}
                {selected && (
                    <>
                        <div style={{ padding: '0.75rem 1rem', borderBottom: '1px solid var(--border)', display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: '1rem', flexWrap: 'wrap' }}>
                            <div>
                                <strong>{selected.contact_name || selected.contact_number}</strong>
                                <div style={{ fontSize: '0.8rem', color: 'var(--text-secondary)' }}>
                                    {selected.contact_number}
                                    {totals && (
                                        <span style={{ marginLeft: '0.75rem' }}>
                                            · {totals.messages_count || 0} messages
                                            {totals.out_count > 0 && parseFloat(totals.total_billed_chf || 0) > 0 && (
                                                <span style={{ fontWeight: 600, color: '#10b981' }}> · {parseFloat(totals.total_billed_chf).toFixed(2)} {totals.billed_currency || 'CHF'}</span>
                                            )}
                                        </span>
                                    )}
                                </div>
                            </div>
                            <div style={{ display: 'flex', gap: '0.5rem', alignItems: 'center' }}>
                                <label style={{ display: 'flex', alignItems: 'center', gap: '0.4rem', fontSize: '0.85rem' }}>
                                    <input type="checkbox"
                                        checked={selected.bot_enabled === 1}
                                        onChange={e => toggleBot(e.target.checked)} />
                                    Bot IA actif
                                </label>
                            </div>
                        </div>

                        <div ref={scrollRef} style={{ flex: 1, overflowY: 'auto', padding: '1rem', background: 'var(--bg-secondary)' }}>
                            {loadingMessages && <div style={{ color: 'var(--text-secondary)' }}>Chargement…</div>}
                            {!loadingMessages && messages.map(m => {
                                const isIn = m.direction === 'inbound';
                                return (
                                    <div key={m.id} style={{ display: 'flex', justifyContent: isIn ? 'flex-start' : 'flex-end', marginBottom: '0.5rem' }}>
                                        <div style={{
                                            maxWidth: '70%', padding: '0.5rem 0.75rem', borderRadius: '12px',
                                            background: isIn ? 'var(--bg-primary)' : (m.sent_by === 'bot' ? '#dbeafe' : '#dcfce7'),
                                            border: '1px solid var(--border)',
                                        }}>
                                            <div style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word', fontSize: '0.9rem' }}>{m.body}</div>
                                            <div style={{ fontSize: '0.7rem', color: 'var(--text-secondary)', marginTop: '0.25rem', textAlign: 'right' }}>
                                                {!isIn && m.sent_by === 'bot' && <span style={{ marginRight: '0.5rem' }}>🤖 bot</span>}
                                                {!isIn && m.sent_by === 'user' && <span style={{ marginRight: '0.5rem' }}>👤 vous</span>}
                                                {fmtTime(m.created_at)}
                                                {!isIn && m.status && <span style={{ marginLeft: '0.5rem' }}>· {m.status}</span>}
                                                {!isIn && parseFloat(m.billed_price || 0) > 0 && (
                                                    <span style={{ marginLeft: '0.5rem', fontWeight: 600, color: '#10b981' }}
                                                          title={`Twilio ${m.price ? parseFloat(m.price).toFixed(4)+' '+(m.price_unit||'USD') : '~'} + IA ${m.ai_cost_usd ? parseFloat(m.ai_cost_usd).toFixed(5)+' $' : '0'}`}>
                                                        · {parseFloat(m.billed_price).toFixed(3)} {m.billed_currency || 'CHF'}
                                                    </span>
                                                )}
                                            </div>
                                        </div>
                                    </div>
                                );
                            })}
                        </div>

                        <div style={{ padding: '0.75rem 1rem', borderTop: '1px solid var(--border)', display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
                            <textarea
                                value={draft}
                                onChange={e => setDraft(e.target.value)}
                                placeholder="Votre réponse…"
                                rows={2}
                                onKeyDown={e => { if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) sendReply(); }}
                                style={{ width: '100%', padding: '0.5rem', borderRadius: '8px', border: '1px solid var(--border)', resize: 'vertical' }} />
                            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: '0.75rem', flexWrap: 'wrap' }}>
                                <label style={{ display: 'flex', alignItems: 'center', gap: '0.4rem', fontSize: '0.8rem', color: 'var(--text-secondary)' }}>
                                    <input type="checkbox" checked={takeOver} onChange={e => setTakeOver(e.target.checked)} />
                                    Désactiver le bot IA pour cette conversation après envoi
                                </label>
                                <button className="btn btn-primary btn-sm" onClick={sendReply} disabled={sending || !draft.trim()}>
                                    {sending ? 'Envoi…' : 'Envoyer (Ctrl+Entrée)'}
                                </button>
                            </div>
                        </div>
                    </>
                )}
            </div>
        </div>
    );
};

// ==================== WHATSAPP SENDERS - wizard d'activation ====================
const SENDER_STATUS_META = {
    PENDING:   { label: 'En préparation', cls: 'badge-gray',    desc: 'Sender enregistré localement, en attente d\'envoi à Vocal.' },
    CREATING:  { label: 'Création…',      cls: 'badge-info',    desc: 'Vocal est en train de créer le sender côté Meta. Patientez quelques minutes.' },
    VERIFYING: { label: 'Vérification',   cls: 'badge-warning', desc: 'Validation par Meta en cours. Pour un numéro externe, saisissez l\'OTP reçu par SMS/appel.' },
    ONLINE:    { label: 'Actif',          cls: 'badge-success', desc: 'Sender prêt à recevoir et envoyer des messages WhatsApp.' },
    ERRORED:   { label: 'Erreur',         cls: 'badge-danger',  desc: 'Vocal a refusé le sender. Voir le détail de l\'erreur.' },
    OFFLINE:   { label: 'Hors-ligne',     cls: 'badge-gray',    desc: 'Sender suspendu.' },
};
// ==================== WHATSAPP SENDER DETAIL (tabs) ====================
const WhatsappSenderDetailView = ({ sender, onBack, onChanged }) => {
    const { notify } = useApp();
    const [tab, setTab] = useState('overview');
    if (!sender) {
        return (
            <div className="page-content">
                <button className="btn btn-secondary btn-sm" onClick={onBack}>← Retour</button>
                <p style={{ marginTop: '1rem' }}>Sender introuvable.</p>
            </div>
        );
    }
    const meta = SENDER_STATUS_META[sender.status] || { label: sender.status, cls: 'badge-gray' };

    const TABS = [
        { id: 'overview',  label: "Vue d'ensemble" },
        { id: 'profile',   label: 'Profil business' },
        { id: 'webhooks',  label: 'Webhooks' },
        { id: 'templates', label: 'Templates' },
    ];

    return (
        <>
            <div className="page-header" style={{ alignItems: 'flex-start' }}>
                <div>
                    <button className="btn btn-secondary btn-sm" onClick={onBack} style={{ marginBottom: '0.75rem' }}>← Tous les comptes</button>
                    <h1 className="page-title" style={{ fontFamily: 'ui-monospace, monospace', display: 'flex', alignItems: 'center', gap: '0.75rem', flexWrap: 'wrap' }}>
                        {sender.phone_number}
                        <span className={`badge ${meta.cls}`} style={{ fontSize: '0.7rem' }}>{meta.label}</span>
                    </h1>
                    <p className="page-subtitle" style={{ fontFamily: '-apple-system, BlinkMacSystemFont, sans-serif' }}>
                        {sender.display_name || 'Sans nom'}
                    </p>
                </div>
            </div>

            {/* Tabs style segmented control */}
            <div style={{ padding: '0 1.5rem 1rem' }}>
                <div className="sender-tabs">
                    {TABS.map(t => (
                        <button key={t.id}
                            className={`sender-tab ${tab === t.id ? 'active' : ''}`}
                            onClick={() => setTab(t.id)}>
                            {t.label}
                        </button>
                    ))}
                </div>
            </div>

            <div className="page-content">
                {tab === 'overview'  && <SenderOverviewTab sender={sender} />}
                {tab === 'profile'   && <SenderProfileTab sender={sender} onChanged={onChanged} />}
                {tab === 'webhooks'  && <SenderWebhooksTab sender={sender} onChanged={onChanged} />}
                {tab === 'templates' && <SenderTemplatesTab sender={sender} />}
            </div>
        </>
    );
};

const SenderOverviewTab = ({ sender }) => {
    const { notify } = useApp();
    const phoneClean = (sender.phone_number || '').replace(/^\+/, '').replace(/\D/g, '');
    const waLink = phoneClean ? `https://wa.me/${phoneClean}` : null;
    const qrUrl = waLink
        ? `https://api.qrserver.com/v1/create-qr-code/?data=${encodeURIComponent(waLink)}&size=240x240&margin=8&color=128C7E`
        : null;

    const copy = (text, label) => {
        navigator.clipboard?.writeText(text);
        notify.success(`${label} copié`);
    };

    const InfoRow = ({ label, value, mono = false }) => (
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '0.55rem 0', borderBottom: '1px solid var(--border)', gap: '1rem' }}>
            <span style={{ fontSize: '0.78rem', color: 'var(--text-muted)', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.03em' }}>{label}</span>
            <span style={{ fontSize: '0.85rem', fontWeight: 600, fontFamily: mono ? 'ui-monospace, monospace' : 'inherit', textAlign: 'right', wordBreak: 'break-all' }}>
                {value || <em style={{ color: 'var(--text-muted)', fontWeight: 400 }}>—</em>}
            </span>
        </div>
    );

    return (
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(320px, 1fr))', gap: '1.25rem' }}>
            {/* Bloc Informations */}
            <div className="card" style={{ padding: '1.5rem' }}>
                <h3 style={{ marginTop: 0, marginBottom: '1rem', fontSize: '1rem' }}>Informations</h3>
                <InfoRow label="Numéro"     value={sender.phone_number} mono />
                <InfoRow label="Statut"     value={sender.status} />
                <InfoRow label="Nom"        value={sender.display_name} />
                <InfoRow label="Sender SID" value={sender.sender_sid} mono />
                <InfoRow label="WABA ID"    value={sender.waba_id} mono />
                <InfoRow label="Source"     value={sender.source === 'twilio' ? 'Créé via Vocal' : 'BYON (numéro existant)'} />
                <InfoRow label="Créé le"    value={sender.created_at} />
                <InfoRow label="Mis à jour" value={sender.updated_at} />
                {sender.failure_reason && (
                    <div style={{ marginTop: '0.75rem', padding: '0.6rem 0.8rem', background: '#fef2f2', border: '1px solid #fecaca', borderRadius: 8, color: '#991b1b', fontSize: '0.82rem' }}>
                        <strong>Erreur :</strong> {sender.failure_reason}
                    </div>
                )}
            </div>

            {/* Bloc Démarrer conversation */}
            <div className="card" style={{ padding: '1.5rem', background: 'linear-gradient(135deg, #f0fdf4 0%, #ecfdf5 100%)', border: '1px solid #bbf7d0' }}>
                <h3 style={{ marginTop: 0, marginBottom: '0.25rem', fontSize: '1rem', color: '#065f46' }}>Démarrer une conversation</h3>
                <p style={{ margin: '0 0 1.25rem', fontSize: '0.82rem', color: '#047857', lineHeight: 1.5 }}>
                    Partagez ce lien ou ce QR code à vos contacts pour qu'ils vous écrivent en 1 clic sur WhatsApp.
                </p>

                {waLink ? (
                    <>
                        {/* QR code */}
                        <div style={{ display: 'flex', justifyContent: 'center', marginBottom: '1.25rem' }}>
                            <div style={{ background: 'white', padding: '0.75rem', borderRadius: 12, boxShadow: '0 4px 12px rgba(0,0,0,0.08)' }}>
                                <img src={qrUrl} alt="QR WhatsApp" width="240" height="240"
                                    style={{ display: 'block', borderRadius: 6 }} />
                            </div>
                        </div>

                        {/* Lien wa.me */}
                        <label style={{ fontSize: '0.72rem', fontWeight: 700, color: '#065f46', textTransform: 'uppercase', letterSpacing: '0.04em' }}>Lien WhatsApp</label>
                        <div style={{ display: 'flex', gap: '0.4rem', marginTop: '0.3rem', marginBottom: '0.75rem' }}>
                            <input readOnly value={waLink}
                                style={{ flex: 1, padding: '0.5rem 0.7rem', fontFamily: 'ui-monospace, monospace', fontSize: '0.82rem', border: '1px solid #bbf7d0', borderRadius: 8, background: 'white' }} />
                            <button className="btn btn-sm" onClick={() => copy(waLink, 'Lien')}
                                style={{ background: '#10b981', color: 'white', border: 'none' }}>
                                Copier
                            </button>
                        </div>

                        {/* Boutons d'actions */}
                        <div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap' }}>
                            <a href={waLink} target="_blank" rel="noreferrer" className="btn btn-sm"
                                style={{ background: '#25D366', color: 'white', textDecoration: 'none', display: 'inline-flex', alignItems: 'center', gap: '0.4rem', flex: 1, justifyContent: 'center', padding: '0.55rem' }}>
                                Ouvrir WhatsApp
                            </a>
                            <a href={qrUrl + '&download=1'} download={`whatsapp-${sender.phone_number}.png`} className="btn btn-sm btn-secondary"
                                style={{ display: 'inline-flex', alignItems: 'center', gap: '0.4rem', flex: 1, justifyContent: 'center', padding: '0.55rem' }}>
                                Télécharger le QR
                            </a>
                        </div>
                    </>
                ) : (
                    <p style={{ color: 'var(--text-muted)', fontSize: '0.85rem' }}>Numéro de téléphone invalide.</p>
                )}
            </div>
        </div>
    );
};

// Composant Field défini HORS du parent pour ne pas être recréé à chaque keystroke
// (sinon React démonte l'input et perd le focus à chaque caractère tapé).
const ProfileField = React.memo(function ProfileField({ label, name, value, onChange, hint, type = 'text' }) {
    return (
        <div style={{ marginBottom: '0.75rem' }}>
            <label style={{ fontSize: '0.85rem', fontWeight: 600, display: 'block', marginBottom: '0.25rem' }}>{label}</label>
            <input type={type} value={value || ''} onChange={e => onChange(name, e.target.value)}
                style={{ width: '100%', padding: '0.5rem', borderRadius: 6, border: '1px solid var(--border)' }} />
            {hint && <div style={{ fontSize: '0.7rem', color: 'var(--text-muted)', marginTop: '0.15rem' }}>{hint}</div>}
        </div>
    );
});

// Catégories WhatsApp Business autorisées par Twilio/Meta.
// La VALUE envoyée à l'API DOIT rester en anglais (liste fermée).
// Le LABEL affiché à l'utilisateur est traduit en français.
const WA_VERTICALS = [
    { value: 'Automotive',                  label: 'Automobile' },
    { value: 'Beauty, Spa and Salon',       label: 'Beauté, spa et salon' },
    { value: 'Clothing and Apparel',        label: 'Vêtements et habillement' },
    { value: 'Education',                   label: 'Éducation' },
    { value: 'Entertainment',               label: 'Divertissement' },
    { value: 'Event Planning and Service',  label: 'Événementiel et services' },
    { value: 'Finance and Banking',         label: 'Finance et banque' },
    { value: 'Food and Grocery',            label: 'Alimentation et épicerie' },
    { value: 'Public Service',              label: 'Service public' },
    { value: 'Hotel and Lodging',           label: 'Hôtellerie et hébergement' },
    { value: 'Medical and Health',          label: 'Médical et santé' },
    { value: 'Non-profit',                  label: 'Association à but non lucratif' },
    { value: 'Professional Services',       label: 'Services professionnels' },
    { value: 'Shopping and Retail',         label: 'Shopping et commerce de détail' },
    { value: 'Travel and Transportation',   label: 'Voyage et transport' },
    { value: 'Restaurant',                  label: 'Restaurant' },
    { value: 'Other',                       label: 'Autre' },
];

const ProfileSelect = React.memo(function ProfileSelect({ label, name, value, onChange, options, hint, placeholder = '- Sélectionner -' }) {
    return (
        <div style={{ marginBottom: '0.75rem' }}>
            <label style={{ fontSize: '0.85rem', fontWeight: 600, display: 'block', marginBottom: '0.25rem' }}>{label}</label>
            <select value={value || ''} onChange={e => onChange(name, e.target.value)}
                style={{ width: '100%', padding: '0.5rem', borderRadius: 6, border: '1px solid var(--border)', background: 'white' }}>
                <option value="">{placeholder}</option>
                {options.map(opt => (
                    <option key={opt.value} value={opt.value}>{opt.label}</option>
                ))}
            </select>
            {hint && <div style={{ fontSize: '0.7rem', color: 'var(--text-muted)', marginTop: '0.15rem' }}>{hint}</div>}
        </div>
    );
});

const SenderProfileTab = ({ sender, onChanged }) => {
    const { notify } = useApp();
    const [draft, setDraft] = useState({
        display_name: sender.display_name || '',
        about: sender.about || '',
        description: sender.description || '',
        vertical: sender.vertical || '',
        address: sender.address || '',
        email: sender.email || '',
        website: sender.website || '',
        logo_url: sender.logo_url || '',
    });
    const [busy, setBusy] = useState(false);
    const [uploadingLogo, setUploadingLogo] = useState(false);
    const fileInputRef = useRef(null);

    const setField = useCallback((k, v) => setDraft(d => ({ ...d, [k]: v })), []);

    const save = async () => {
        setBusy(true);
        try {
            const r = await api.put(`/my/whatsapp-senders/${sender.id}`, draft);
            if (r?.error) { notify.error(r.error); return; }
            notify.success('Profil mis à jour');
            onChanged && onChanged();
        } catch (e) { notify.error('Erreur'); } finally { setBusy(false); }
    };

    const onLogoFile = async (e) => {
        const file = e.target.files?.[0];
        if (!file) return;
        // Vérifier dimensions minimales (640×640 recommandé par Meta).
        const dims = await new Promise((resolve) => {
            const img = new Image();
            img.onload = () => resolve({ w: img.naturalWidth, h: img.naturalHeight });
            img.onerror = () => resolve(null);
            img.src = URL.createObjectURL(file);
        });
        if (dims && (dims.w < 192 || dims.h < 192)) {
            notify.error(`Image trop petite (${dims.w}×${dims.h}). Minimum 192×192, recommandé 640×640.`);
            e.target.value = '';
            return;
        }
        setUploadingLogo(true);
        try {
            const fd = new FormData();
            fd.append('file', file);
            const r = await api.upload('/my/upload/image', fd);
            if (r?.error) { notify.error(r.error); return; }
            setField('logo_url', r.url);
            notify.success('Logo uploadé');
        } catch (err) {
            notify.error('Erreur upload: ' + (err?.message || ''));
        } finally {
            setUploadingLogo(false);
            e.target.value = '';
        }
    };

    return (
        <div className="card" style={{ padding: '1.5rem', maxWidth: 700 }}>
            <h3 style={{ marginTop: 0 }}>Profil affiché aux utilisateurs WhatsApp</h3>
            <ProfileField label="Nom affiché" name="display_name" value={draft.display_name} onChange={setField} />
            <ProfileField label="À propos" name="about" value={draft.about} onChange={setField} hint="Court (139 car. max)" />
            <ProfileField label="Description" name="description" value={draft.description} onChange={setField} hint="Plus détaillé (512 car. max)" />
            <ProfileSelect label="Catégorie" name="vertical" value={draft.vertical} onChange={setField}
                options={WA_VERTICALS}
                hint="Catégorie d'activité affichée aux utilisateurs WhatsApp (liste fixée par Meta)." />
            <ProfileField label="Adresse" name="address" value={draft.address} onChange={setField} />
            <ProfileField label="Email" name="email" value={draft.email} onChange={setField} type="email" />
            <ProfileField label="Site web" name="website" value={draft.website} onChange={setField} type="url" />

            {/* Logo : upload + preview + URL éditable */}
            <div style={{ marginBottom: '0.75rem' }}>
                <label style={{ fontSize: '0.85rem', fontWeight: 600, display: 'block', marginBottom: '0.25rem' }}>Logo</label>
                <div style={{ display: 'flex', gap: '0.75rem', alignItems: 'flex-start' }}>
                    {draft.logo_url ? (
                        <img src={draft.logo_url} alt="logo"
                            style={{ width: 64, height: 64, borderRadius: 12, objectFit: 'cover', border: '1px solid var(--border)' }}
                            onError={e => { e.target.style.opacity = '0.3'; }} />
                    ) : (
                        <div style={{ width: 64, height: 64, borderRadius: 12, border: '1px dashed var(--border)',
                            display: 'flex', alignItems: 'center', justifyContent: 'center',
                            fontSize: '0.7rem', color: 'var(--text-muted)' }}>
                            Aperçu
                        </div>
                    )}
                    <div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: '0.4rem' }}>
                        <input type="url" value={draft.logo_url || ''} placeholder="https://…"
                            onChange={e => setField('logo_url', e.target.value)}
                            style={{ width: '100%', padding: '0.5rem', borderRadius: 6, border: '1px solid var(--border)' }} />
                        <div style={{ display: 'flex', gap: '0.4rem' }}>
                            <button type="button" className="btn btn-secondary btn-sm"
                                onClick={() => fileInputRef.current?.click()} disabled={uploadingLogo}>
                                {uploadingLogo ? 'Upload…' : '📤 Uploader une image'}
                            </button>
                            {draft.logo_url && (
                                <button type="button" className="btn btn-secondary btn-sm"
                                    onClick={() => setField('logo_url', '')}>
                                    Retirer
                                </button>
                            )}
                        </div>
                        <input ref={fileInputRef} type="file" accept="image/jpeg,image/png,image/webp"
                            onChange={onLogoFile} style={{ display: 'none' }} />
                    </div>
                </div>
                <div style={{ fontSize: '0.7rem', color: 'var(--text-muted)', marginTop: '0.4rem', lineHeight: 1.5 }}>
                    <b>Dimensions :</b> minimum 192×192 px, recommandé <b>640×640 px</b>, carré.<br />
                    <b>Format :</b> JPG, PNG ou WEBP. <b>Poids max :</b> 5 MB. <b>Fond uni</b> conseillé (pas de transparence).
                </div>
            </div>

            <div style={{ display: 'flex', gap: '0.5rem', marginTop: '1rem' }}>
                <button className="btn btn-primary" onClick={save} disabled={busy}>{busy ? 'Enregistrement…' : 'Enregistrer & publier'}</button>
            </div>
        </div>
    );
};

const SenderWebhooksTab = ({ sender, onChanged }) => {
    const { notify } = useApp();
    const [busy, setBusy] = useState(false);
    const base = 'https://api.helvia.app';
    const expected = {
        incoming: `${base}/whatsapp/incoming`,
        fallback: `${base}/whatsapp/incoming-fallback`,
        status:   `${base}/whatsapp/status`,
    };
    const sync = async () => {
        setBusy(true);
        try {
            const r = await api.post(`/my/whatsapp-senders/${sender.id}/sync-webhooks`, {});
            if (r?.error) { notify.error(r.error + (r.detail?.message ? ' - ' + r.detail.message : '')); return; }
            notify.success('Webhooks synchronisés');
            onChanged && onChanged();
        } catch (e) { notify.error('Erreur'); } finally { setBusy(false); }
    };
    return (
        <div className="card" style={{ padding: '1.5rem', maxWidth: 800 }}>
            <h3 style={{ marginTop: 0 }}>URLs de webhooks Vocal</h3>
            <p style={{ color: 'var(--text-muted)' }}>
                Vocal reçoit ces URLs à chaque message WhatsApp entrant et à chaque changement de statut de message sortant.
            </p>
            <table className="data-table">
                <thead><tr><th>Rôle</th><th>URL Vocal</th></tr></thead>
                <tbody>
                    <tr><td><strong>Réception</strong></td><td style={{ fontFamily: 'ui-monospace, monospace', fontSize: '0.8rem' }}>{expected.incoming}</td></tr>
                    <tr><td><strong>Fallback</strong></td><td style={{ fontFamily: 'ui-monospace, monospace', fontSize: '0.8rem' }}>{expected.fallback}</td></tr>
                    <tr><td><strong>Statuts</strong></td><td style={{ fontFamily: 'ui-monospace, monospace', fontSize: '0.8rem' }}>{expected.status}</td></tr>
                </tbody>
            </table>
            <div style={{ marginTop: '1rem' }}>
                <strong>Webhook actuellement enregistré :</strong>
                <div style={{ fontFamily: 'ui-monospace, monospace', fontSize: '0.8rem', marginTop: '0.25rem', padding: '0.5rem', background: 'var(--bg-secondary)', borderRadius: 6 }}>
                    {sender.webhook_url || <em style={{ color: 'var(--text-muted)' }}>non configuré</em>}
                </div>
            </div>
            <button className="btn btn-primary" onClick={sync} disabled={busy} style={{ marginTop: '1rem' }}>
                {busy ? 'Synchronisation…' : '🔄 Synchroniser maintenant'}
            </button>
        </div>
    );
};

// ==================== SENDER TEMPLATES TAB ====================
const TEMPLATE_STATUS_META = {
    draft:    { label: 'Brouillon',     cls: 'badge-gray',    desc: 'Pas encore publié.' },
    created:  { label: 'Publié',        cls: 'badge-info',    desc: 'Template créé mais pas encore soumis à WhatsApp.' },
    pending:  { label: 'En attente WA', cls: 'badge-warning', desc: 'Soumis à WhatsApp pour approbation.' },
    approved: { label: 'Approuvé',      cls: 'badge-success', desc: 'Prêt à l\'envoi.' },
    rejected: { label: 'Rejeté',        cls: 'badge-danger',  desc: 'WhatsApp a refusé le template.' },
    paused:   { label: 'Suspendu',      cls: 'badge-warning', desc: 'Suspendu par WhatsApp (qualité).' },
    disabled: { label: 'Désactivé',     cls: 'badge-gray',    desc: 'Désactivé par WhatsApp.' },
    error:    { label: 'Erreur',        cls: 'badge-danger',  desc: 'Erreur lors de la publication.' },
};

const SenderTemplatesTab = ({ sender }) => {
    const { notify } = useApp();
    const [templates, setTemplates] = useState([]);
    const [loading, setLoading] = useState(true);
    const [showForm, setShowForm] = useState(false);
    const [testingTpl, setTestingTpl] = useState(null);
    const [testDraft, setTestDraft] = useState({ to: '', variables: '{}' });
    const [viewingTpl, setViewingTpl] = useState(null);

    const load = useCallback(async () => {
        setLoading(true);
        try {
            const r = await api.get(`/my/whatsapp-senders/${sender.id}/templates`);
            if (r?.error) { notify.error(r.error); return; }
            setTemplates(r.templates || []);
        } catch (e) { notify.error('Erreur chargement templates'); }
        finally { setLoading(false); }
    }, [sender.id, notify]);
    useEffect(() => { load(); }, [load]);

    const refresh = async (id) => {
        try {
            const r = await api.post(`/my/whatsapp-templates/${id}/refresh-status`, {});
            if (r?.error) { notify.error(r.error); return; }
            notify.success('Statut actualisé');
            load();
        } catch (e) { notify.error('Erreur'); }
    };
    const submit = async (id) => {
        try {
            const r = await api.post(`/my/whatsapp-templates/${id}/submit-approval`, {});
            if (r?.error) { notify.error(r.error + (r.detail?.message ? ' - ' + r.detail.message : '')); return; }
            notify.success('Template soumis à WhatsApp');
            load();
        } catch (e) { notify.error('Erreur soumission'); }
    };
    const remove = async (id) => {
        if (!confirm('Supprimer ce template ? Action irréversible.')) return;
        try {
            const r = await api.del(`/my/whatsapp-templates/${id}`);
            if (r?.error) { notify.error(r.error); return; }
            notify.success('Template supprimé');
            load();
        } catch (e) { notify.error('Erreur'); }
    };
    const sendTest = async () => {
        if (!testingTpl) return;
        let vars = {};
        try { vars = testDraft.variables ? JSON.parse(testDraft.variables) : {}; }
        catch (e) { notify.error('Variables JSON invalide'); return; }
        try {
            const r = await api.post(`/my/whatsapp-templates/${testingTpl.id}/send-test`, { to: testDraft.to, variables: vars });
            if (r?.error) { notify.error(r.error + (r.detail?.message ? ' - ' + r.detail.message : '')); return; }
            notify.success('Envoyé : ' + r.message_sid);
            setTestingTpl(null); setTestDraft({ to: '', variables: '{}' });
        } catch (e) { notify.error('Erreur envoi'); }
    };

    const statusBadge = (s) => {
        const m = TEMPLATE_STATUS_META[s] || { label: s, cls: 'badge-gray', desc: '' };
        return <span className={`badge ${m.cls}`} title={m.desc}>{m.label}</span>;
    };

    if (showForm) return <TemplateCreateForm sender={sender} onCancel={() => setShowForm(false)} onCreated={() => { setShowForm(false); load(); }} />;

    return (
        <>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
                <div>
                    <h3 style={{ margin: 0 }}>Templates de contenu WhatsApp</h3>
                    <p style={{ color: 'var(--text-muted)', fontSize: '0.85rem', margin: '0.25rem 0 0' }}>
                        Modèles approuvés par WhatsApp permettant d'envoyer des messages business-initiated hors fenêtre 24h
                        (confirmations, rappels, notifications transactionnelles).
                    </p>
                </div>
                <div style={{ display: 'flex', gap: '0.5rem' }}>
                    <button className="btn btn-secondary btn-sm" onClick={load}>↻ Actualiser</button>
                    <button className="btn btn-primary btn-sm" onClick={() => setShowForm(true)}>+ Nouveau modèle</button>
                </div>
            </div>

            {loading && <div className="card" style={{ padding: '2rem', textAlign: 'center', color: 'var(--text-muted)' }}>Chargement…</div>}

            {!loading && templates.length === 0 && (
                <div className="card" style={{ padding: '2rem', textAlign: 'center' }}>
                    <p style={{ color: 'var(--text-muted)' }}>Aucun template pour ce sender. Créez-en un pour démarrer des conversations.</p>
                    <button className="btn btn-primary" onClick={() => setShowForm(true)}>+ Créer mon premier modèle</button>
                </div>
            )}

            {!loading && templates.length > 0 && (
                <div className="card">
                    <div className="table-container">
                        <table className="data-table">
                            <thead><tr>
                                <th>Statut</th><th>Nom</th><th>Catégorie</th><th>Langue</th>
                                <th>Aperçu</th><th>ContentSid</th>
                                <th style={{ textAlign: 'right' }}>Actions</th>
                            </tr></thead>
                            <tbody>
                                {templates.map(t => (
                                    <tr key={t.id} style={{ cursor: 'pointer' }} onClick={() => setViewingTpl(t)}>
                                        <td>{statusBadge(t.approval_status)}{t.rejection_reason && <div style={{ fontSize: '0.7rem', color: 'var(--danger, #c00)', marginTop: 4 }}>{t.rejection_reason}</div>}</td>
                                        <td><strong style={{ textDecoration: 'underline', color: 'var(--primary, #2563eb)' }}>{t.friendly_name}</strong>{t.approval_name && <div style={{ fontSize: '0.7rem', color: 'var(--text-muted)' }}>{t.approval_name}</div>}</td>
                                        <td>{t.category || '-'}</td>
                                        <td>{t.language}</td>
                                        <td style={{ maxWidth: 320, fontSize: '0.8rem' }}>{t.body ? (t.body.length > 80 ? t.body.slice(0, 80) + '…' : t.body) : <em style={{ color: 'var(--text-muted)' }}>-</em>}</td>
                                        <td style={{ fontFamily: 'ui-monospace, monospace', fontSize: '0.7rem' }}>{t.content_sid || '-'}</td>
                                        <td style={{ textAlign: 'right' }} onClick={(e) => e.stopPropagation()}>
                                            <div style={{ display: 'inline-flex', gap: '0.3rem', flexWrap: 'wrap', justifyContent: 'flex-end' }}>
                                                <button className="btn btn-secondary btn-sm" onClick={() => setViewingTpl(t)} title="Voir le détail">👁</button>
                                                {t.content_sid && <button className="btn btn-secondary btn-sm" onClick={() => refresh(t.id)} title="Actualiser le statut WhatsApp">↻</button>}
                                                {t.content_sid && t.approval_status !== 'approved' && t.approval_status !== 'pending' && (
                                                    <button className="btn btn-primary btn-sm" onClick={() => submit(t.id)}>📨 Soumettre WA</button>
                                                )}
                                                {t.approval_status === 'approved' && (
                                                    <button className="btn btn-success btn-sm" onClick={() => setTestingTpl(t)}>✉️ Tester</button>
                                                )}
                                                <button className="btn btn-danger btn-sm" onClick={() => remove(t.id)}>🗑</button>
                                            </div>
                                        </td>
                                    </tr>
                                ))}
                            </tbody>
                        </table>
                    </div>
                </div>
            )}

            {/* Modal détail template */}
            {viewingTpl && (
                <div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.5)', zIndex: 1000, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '1rem' }} onClick={() => setViewingTpl(null)}>
                    <div className="card" style={{ width: 'min(720px, 100%)', maxHeight: '90vh', overflow: 'auto', padding: '1.5rem' }} onClick={e => e.stopPropagation()}>
                        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: '1rem', marginBottom: '1rem' }}>
                            <div>
                                <h3 style={{ margin: 0 }}>{viewingTpl.friendly_name}</h3>
                                {viewingTpl.approval_name && <div style={{ fontSize: '0.8rem', color: 'var(--text-muted)' }}>{viewingTpl.approval_name}</div>}
                                <div style={{ marginTop: '0.5rem' }}>{statusBadge(viewingTpl.approval_status)}</div>
                            </div>
                            <button className="btn btn-sm btn-secondary" onClick={() => setViewingTpl(null)}>Fermer</button>
                        </div>

                        {viewingTpl.rejection_reason && (
                            <div style={{ padding: '0.75rem 1rem', background: '#fef2f2', border: '1px solid #fecaca', borderRadius: 8, color: '#b91c1c', fontSize: '0.85rem', marginBottom: '1rem' }}>
                                <strong>Raison du rejet :</strong> {viewingTpl.rejection_reason}
                            </div>
                        )}

                        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '0.5rem 1.5rem', fontSize: '0.85rem', marginBottom: '1rem' }}>
                            <div><strong>Catégorie :</strong> {viewingTpl.category || '-'}</div>
                            <div><strong>Langue :</strong> {viewingTpl.language}</div>
                            <div style={{ gridColumn: '1 / 3' }}><strong>ContentSid :</strong> <code style={{ fontSize: '0.75rem' }}>{viewingTpl.content_sid || '-'}</code></div>
                            <div><strong>Créé :</strong> {viewingTpl.created_at || '-'}</div>
                            <div><strong>Mis à jour :</strong> {viewingTpl.updated_at || '-'}</div>
                            {viewingTpl.submitted_at && <div style={{ gridColumn: '1 / 3' }}><strong>Soumis :</strong> {viewingTpl.submitted_at}</div>}
                        </div>

                        <div style={{ marginBottom: '1rem' }}>
                            <div style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: '0.4rem' }}>Aperçu WhatsApp</div>
                            <div style={{ padding: '0.75rem 1rem', background: '#dcfce7', borderRadius: 12, whiteSpace: 'pre-wrap', wordBreak: 'break-word', fontSize: '0.9rem', maxWidth: '85%' }}>
                                {viewingTpl.body || <em style={{ color: 'var(--text-muted)' }}>(corps vide)</em>}
                                {viewingTpl.buttons_json && (() => {
                                    try {
                                        const btns = JSON.parse(viewingTpl.buttons_json);
                                        if (!Array.isArray(btns) || btns.length === 0) return null;
                                        return (
                                            <div style={{ marginTop: '0.6rem', paddingTop: '0.6rem', borderTop: '1px solid #86efac', display: 'flex', flexDirection: 'column', gap: '0.3rem' }}>
                                                {btns.map((b, i) => (
                                                    <div key={i} style={{ background: '#fff', padding: '0.4rem 0.6rem', borderRadius: 6, textAlign: 'center', color: '#2563eb', fontSize: '0.85rem' }}>
                                                        {b.type === 'URL' ? '🔗 ' : b.type === 'PHONE_NUMBER' ? '📞 ' : '↩️ '}{b.text}
                                                    </div>
                                                ))}
                                            </div>
                                        );
                                    } catch { return null; }
                                })()}
                            </div>
                        </div>

                        {viewingTpl.variables && (
                            <div style={{ marginBottom: '1rem' }}>
                                <div style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: '0.4rem' }}>Valeurs d'exemple</div>
                                <pre style={{ background: 'var(--bg-secondary, #f3f4f6)', padding: '0.75rem', borderRadius: 6, fontSize: '0.75rem', margin: 0, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>{(() => {
                                    try { return JSON.stringify(JSON.parse(viewingTpl.variables), null, 2); }
                                    catch { return viewingTpl.variables; }
                                })()}</pre>
                            </div>
                        )}

                        {viewingTpl.webhook_url && (
                            <div style={{ marginBottom: '1rem' }}>
                                <div style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: '0.4rem' }}>Webhook clic boutons</div>
                                <code style={{ fontSize: '0.75rem', wordBreak: 'break-all' }}>{viewingTpl.webhook_url}</code>
                            </div>
                        )}

                        {viewingTpl.types_json && (
                            <details style={{ marginBottom: '1rem' }}>
                                <summary style={{ cursor: 'pointer', fontSize: '0.85rem', fontWeight: 600 }}>Payload Twilio brut (types_json)</summary>
                                <pre style={{ background: 'var(--bg-secondary, #f3f4f6)', padding: '0.75rem', borderRadius: 6, fontSize: '0.7rem', marginTop: '0.5rem', whiteSpace: 'pre-wrap', wordBreak: 'break-word', maxHeight: '300px', overflow: 'auto' }}>{(() => {
                                    try { return JSON.stringify(JSON.parse(viewingTpl.types_json), null, 2); }
                                    catch { return viewingTpl.types_json; }
                                })()}</pre>
                            </details>
                        )}

                        <div style={{ display: 'flex', gap: '0.5rem', justifyContent: 'flex-end', borderTop: '1px solid var(--border)', paddingTop: '1rem' }}>
                            {viewingTpl.content_sid && <button className="btn btn-secondary" onClick={() => { refresh(viewingTpl.id); }}>↻ Actualiser statut</button>}
                            {viewingTpl.content_sid && viewingTpl.approval_status !== 'approved' && viewingTpl.approval_status !== 'pending' && (
                                <button className="btn btn-primary" onClick={() => { submit(viewingTpl.id); setViewingTpl(null); }}>📨 Soumettre à WhatsApp</button>
                            )}
                            {viewingTpl.approval_status === 'approved' && (
                                <button className="btn btn-success" onClick={() => { setTestingTpl(viewingTpl); setViewingTpl(null); }}>✉️ Tester</button>
                            )}
                        </div>
                    </div>
                </div>
            )}

            {/* Modal envoi test */}
            {testingTpl && (
                <div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.5)', zIndex: 1000, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '1rem' }} onClick={() => setTestingTpl(null)}>
                    <div className="card" style={{ padding: '1.5rem', maxWidth: 500, width: '100%' }} onClick={e => e.stopPropagation()}>
                        <h3 style={{ marginTop: 0 }}>Envoyer un test : {testingTpl.friendly_name}</h3>
                        <p style={{ fontSize: '0.85rem', color: 'var(--text-muted)' }}>{testingTpl.body}</p>
                        <label style={{ fontSize: '0.85rem', fontWeight: 600, display: 'block', marginTop: '0.75rem' }}>Numéro destinataire (E.164)</label>
                        <input value={testDraft.to} onChange={e => setTestDraft({ ...testDraft, to: e.target.value })}
                            placeholder="+41XXXXXXXXX" style={{ width: '100%', padding: '0.5rem', borderRadius: 6, border: '1px solid var(--border)' }} />
                        <label style={{ fontSize: '0.85rem', fontWeight: 600, display: 'block', marginTop: '0.75rem' }}>Variables (JSON)</label>
                        <textarea value={testDraft.variables} onChange={e => setTestDraft({ ...testDraft, variables: e.target.value })}
                            rows={3} placeholder='{"1":"Yannick","2":"10:30"}'
                            style={{ width: '100%', padding: '0.5rem', borderRadius: 6, border: '1px solid var(--border)', fontFamily: 'ui-monospace, monospace', fontSize: '0.8rem' }} />
                        <div style={{ display: 'flex', gap: '0.5rem', marginTop: '1rem', justifyContent: 'flex-end' }}>
                            <button className="btn btn-secondary" onClick={() => setTestingTpl(null)}>Annuler</button>
                            <button className="btn btn-primary" onClick={sendTest}>Envoyer</button>
                        </div>
                    </div>
                </div>
            )}
        </>
    );
};

const TemplateCreateForm = ({ sender, onCancel, onCreated }) => {
    const { notify } = useApp();
    const [draft, setDraft] = useState({
        friendly_name: '',
        language: 'fr',
        category: 'UTILITY',
        body: '',
    });
    // Map { "1": "Yannick", "2": "Genève → CDG", ... } - exemple soumis à WhatsApp
    // pour la review. Obligatoire pour l'approbation Meta.
    const [varSamples, setVarSamples] = useState({});
    const [submitToWA, setSubmitToWA] = useState(false);
    const [busy, setBusy] = useState(false);
    // Boutons interactifs (max 3 QUICK_REPLY ou max 2 URL/PHONE_NUMBER).
    const [buttons, setButtons] = useState([]); // [{ type, text, payload?, url?, phone? }]
    const [webhookUrl, setWebhookUrl]       = useState('');
    const [webhookSecret, setWebhookSecret] = useState('');

    const addButton = (type) => {
        setButtons(prev => {
            if (prev.length >= 3) return prev;
            if (type === 'QUICK_REPLY') return [...prev, { type, text: '', payload: '' }];
            if (type === 'URL')         return [...prev, { type, text: '', url: '' }];
            if (type === 'PHONE_NUMBER')return [...prev, { type, text: '', phone: '' }];
            return prev;
        });
    };
    const updateButton = (idx, patch) => setButtons(prev => prev.map((b, i) => i === idx ? { ...b, ...patch } : b));
    const removeButton = (idx) => setButtons(prev => prev.filter((_, i) => i !== idx));

    // Règles Twilio : QUICK_REPLY (max 3) ne peut PAS être mixé avec URL/PHONE.
    const onlyQR = buttons.length > 0 && buttons.every(b => b.type === 'QUICK_REPLY');
    const onlyCTA = buttons.length > 0 && buttons.every(b => b.type !== 'QUICK_REPLY');
    const buttonsValid = buttons.length === 0 || (
        (onlyQR && buttons.length <= 3) ||
        (onlyCTA && buttons.length <= 2)
    );

    const detectedVars = useMemo(() => {
        const out = new Set(); const re = /\{\{\s*(\d+)\s*\}\}/g; let m;
        while ((m = re.exec(draft.body)) !== null) out.add(m[1]);
        return [...out].sort((a, b) => parseInt(a) - parseInt(b));
    }, [draft.body]);

    // Synchronise varSamples avec les variables détectées : ajoute les manquantes,
    // garde les valeurs déjà saisies pour les variables encore présentes.
    useEffect(() => {
        setVarSamples(prev => {
            const next = {};
            for (const v of detectedVars) next[v] = prev[v] || '';
            return next;
        });
    }, [detectedVars.join('|')]);

    // Aperçu rendu avec les samples substitués.
    const preview = useMemo(() => {
        let out = draft.body;
        for (const v of detectedVars) {
            const sample = varSamples[v] || `{{${v}}}`;
            out = out.replaceAll(`{{${v}}}`, sample).replaceAll(`{{ ${v} }}`, sample);
        }
        return out;
    }, [draft.body, varSamples, detectedVars]);

    const create = async () => {
        if (!draft.friendly_name.trim()) { notify.error('Nom interne requis'); return; }
        if (!draft.body.trim()) { notify.error('Corps du message requis'); return; }
        // Meta refuse les templates dont le corps commence ou termine par une variable.
        const trimmedBody = draft.body.trim();
        if (/^\s*\{\{\s*\d+\s*\}\}/.test(trimmedBody)) {
            notify.error('WhatsApp refuse les templates qui commencent par une variable. Ajoute du texte avant {{1}} (ex: "Bonjour {{1}}…").');
            return;
        }
        if (/\{\{\s*\d+\s*\}\}\s*$/.test(trimmedBody)) {
            notify.error('WhatsApp refuse les templates qui finissent par une variable. Ajoute du texte ou un point après {{X}} (ex: "…{{1}}." ou "…{{1}} merci !").');
            return;
        }
        if (!buttonsValid) { notify.error('Boutons invalides : max 3 QUICK_REPLY OU max 2 URL/PHONE (pas de mix).'); return; }
        for (const b of buttons) {
            if (!b.text.trim()) { notify.error('Chaque bouton doit avoir un libellé'); return; }
            if (b.type === 'URL' && !(b.url || '').trim()) { notify.error('URL manquante sur un bouton'); return; }
            if (b.type === 'PHONE_NUMBER' && !(b.phone || '').trim()) { notify.error('Numéro manquant sur un bouton'); return; }
        }
        // Si l'utilisateur veut soumettre à WhatsApp, exiger des samples non vides.
        if (submitToWA && detectedVars.length > 0) {
            const missing = detectedVars.filter(v => !(varSamples[v] || '').trim());
            if (missing.length > 0) {
                notify.error(`Valeurs d'exemple manquantes pour : ${missing.map(v => `{{${v}}}`).join(', ')}. WhatsApp les exige pour la review.`);
                return;
            }
        }
        setBusy(true);
        try {
            // Construit la map variables (string keys → string values) à envoyer.
            const variables = {};
            for (const v of detectedVars) {
                variables[v] = (varSamples[v] || '').trim() || `Sample${v}`;
            }
            const r = await api.post(`/my/whatsapp-senders/${sender.id}/templates`, {
                ...draft,
                variables: detectedVars.length > 0 ? variables : undefined,
                buttons: buttons.length > 0 ? buttons : undefined,
                webhook_url: webhookUrl.trim() || undefined,
                webhook_secret: webhookSecret.trim() || undefined,
                publish: 1,
                submit: submitToWA ? 1 : 0,
            });
            if (r?.error) { notify.error(r.error + (r.detail?.message ? ' - ' + r.detail.message : '')); return; }
            notify.success(submitToWA ? 'Template créé et soumis à WhatsApp' : 'Template publié (non soumis)');
            onCreated();
        } catch (e) { notify.error('Erreur création'); }
        finally { setBusy(false); }
    };

    return (
        <div className="card" style={{ padding: '1.5rem', maxWidth: 800 }}>
            <button className="btn btn-secondary btn-sm" onClick={onCancel} style={{ marginBottom: '1rem' }}>← Retour à la liste</button>
            <h3 style={{ marginTop: 0 }}>Nouveau template WhatsApp</h3>

            <div style={{ marginBottom: '1rem', padding: '0.85rem 1rem', background: '#eff6ff', border: '1px solid #bfdbfe', borderRadius: 8 }}>
                <div style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: '0.4rem' }}>🚀 Démarrage rapide</div>
                <div style={{ fontSize: '0.8rem', color: 'var(--text-muted)', marginBottom: '0.5rem' }}>
                    Pré-remplit un template de test minimal et le soumet directement à WhatsApp pour approbation.
                </div>
                <button type="button" className="btn btn-sm btn-primary" disabled={busy} onClick={() => {
                    setDraft({
                        friendly_name: 'rappel_message_v1',
                        language: 'fr',
                        category: 'UTILITY',
                        body: 'Bonjour {{1}}, vous nous avez laissé un message le {{2}}. Comment puis-je vous aider ?',
                    });
                    setVarSamples({ '1': 'Yannick', '2': '28 mai à 14h30' });
                    setSubmitToWA(true);
                    setButtons([]);
                    setWebhookUrl('');
                    setWebhookSecret('');
                }}>📝 Utiliser le template "Message de test"</button>
            </div>

            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem' }}>
                <div>
                    <label style={{ fontSize: '0.85rem', fontWeight: 600, display: 'block', marginBottom: '0.25rem' }}>Nom interne</label>
                    <input value={draft.friendly_name} onChange={e => setDraft({ ...draft, friendly_name: e.target.value })}
                        placeholder="rey_confirmation_v1"
                        style={{ width: '100%', padding: '0.5rem', borderRadius: 6, border: '1px solid var(--border)' }} />
                    <div style={{ fontSize: '0.7rem', color: 'var(--text-muted)', marginTop: '0.15rem' }}>
                        Pour usage interne. WhatsApp dérivera un nom normalisé (lowercase, underscores).
                    </div>
                </div>
                <div>
                    <label style={{ fontSize: '0.85rem', fontWeight: 600, display: 'block', marginBottom: '0.25rem' }}>Langue</label>
                    <select value={draft.language} onChange={e => setDraft({ ...draft, language: e.target.value })}
                        style={{ width: '100%', padding: '0.5rem', borderRadius: 6, border: '1px solid var(--border)' }}>
                        <option value="fr">Français</option>
                        <option value="en">English</option>
                        <option value="de">Deutsch</option>
                        <option value="it">Italiano</option>
                        <option value="es">Español</option>
                        <option value="pt">Português</option>
                    </select>
                </div>
            </div>

            <div style={{ marginTop: '0.75rem' }}>
                <label style={{ fontSize: '0.85rem', fontWeight: 600, display: 'block', marginBottom: '0.25rem' }}>Catégorie WhatsApp</label>
                <select value={draft.category} onChange={e => setDraft({ ...draft, category: e.target.value })}
                    style={{ width: '100%', padding: '0.5rem', borderRadius: 6, border: '1px solid var(--border)' }}>
                    <option value="UTILITY">UTILITY - Confirmations, rappels, notifications transactionnelles</option>
                    <option value="MARKETING">MARKETING - Offres, promotions, annonces</option>
                    <option value="AUTHENTICATION">AUTHENTICATION - Codes OTP, vérifications</option>
                </select>
            </div>

            <div style={{ marginTop: '0.75rem' }}>
                <label style={{ fontSize: '0.85rem', fontWeight: 600, display: 'block', marginBottom: '0.25rem' }}>Corps du message</label>
                <textarea value={draft.body} onChange={e => setDraft({ ...draft, body: e.target.value })}
                    rows={5}
                    placeholder="Bonjour {{1}}, votre transfert vers {{3}} est confirmé pour {{2}}."
                    style={{ width: '100%', padding: '0.5rem', borderRadius: 6, border: '1px solid var(--border)' }} />
                <div style={{ fontSize: '0.75rem', color: 'var(--text-muted)', marginTop: '0.25rem' }}>
                    Utilisez <code>{`{{1}}, {{2}}, {{3}}`}</code> pour les variables dynamiques.
                </div>
                {(() => {
                    const t = (draft.body || '').trim();
                    const startsVar = /^\s*\{\{\s*\d+\s*\}\}/.test(t);
                    const endsVar   = /\{\{\s*\d+\s*\}\}\s*$/.test(t);
                    if (!startsVar && !endsVar) return null;
                    return (
                        <div style={{ fontSize: '0.75rem', color: '#b45309', marginTop: '0.35rem', padding: '0.4rem 0.6rem', background: '#fef3c7', borderRadius: 6 }}>
                            ⚠️ WhatsApp refuse les templates qui {startsVar ? 'commencent' : 'finissent'} par une variable.
                            {startsVar && ' Ajoute du texte avant '}{endsVar && ' Ajoute un point ou du texte après '}{startsVar || endsVar ? `{{${startsVar ? '1' : 'X'}}}` : ''}.
                        </div>
                    );
                })()}
            </div>

            {detectedVars.length > 0 && (
                <div style={{ marginTop: '1rem', padding: '0.85rem 1rem', border: '1px solid var(--border)', borderRadius: 8, background: '#fffbeb' }}>
                    <div style={{ fontSize: '0.85rem', fontWeight: 700, marginBottom: '0.5rem' }}>
                        Valeurs d'exemple pour la review WhatsApp
                    </div>
                    <div style={{ fontSize: '0.75rem', color: 'var(--text-muted)', marginBottom: '0.75rem' }}>
                        WhatsApp/Meta examine votre template avec ces valeurs pour comprendre le contexte.
                        Donnez des exemples <strong>réalistes</strong> (pas "Sample1") sinon le template sera rejeté.
                    </div>
                    <div style={{ display: 'grid', gap: '0.5rem' }}>
                        {detectedVars.map(v => (
                            <div key={v} style={{ display: 'grid', gridTemplateColumns: '80px 1fr', gap: '0.5rem', alignItems: 'center' }}>
                                <code style={{ fontSize: '0.85rem', fontWeight: 700 }}>{`{{${v}}}`}</code>
                                <input
                                    value={varSamples[v] || ''}
                                    onChange={e => setVarSamples({ ...varSamples, [v]: e.target.value })}
                                    placeholder={`Exemple pour {{${v}}} (ex: Yannick, Genève, 14h30…)`}
                                    style={{ width: '100%', padding: '0.45rem', borderRadius: 6, border: '1px solid var(--border)' }} />
                            </div>
                        ))}
                    </div>
                </div>
            )}

            <div style={{ marginTop: '1rem', padding: '0.75rem', background: 'var(--bg-secondary)', borderRadius: 6 }}>
                <strong style={{ fontSize: '0.85rem' }}>Aperçu (variables substituées) :</strong>
                <div style={{ marginTop: '0.5rem', padding: '0.75rem', background: '#dcfce7', borderRadius: 8, whiteSpace: 'pre-wrap', fontSize: '0.9rem' }}>
                    {preview || <em style={{ color: 'var(--text-muted)' }}>(vide)</em>}
                </div>
            </div>

            <div style={{ marginTop: '1rem', padding: '0.85rem 1rem', border: '1px solid var(--border)', borderRadius: 8 }}>
                <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '0.5rem' }}>
                    <div style={{ fontSize: '0.85rem', fontWeight: 700 }}>Boutons interactifs ({buttons.length}/3)</div>
                    <div style={{ display: 'flex', gap: '0.25rem' }}>
                        <button type="button" className="btn btn-sm" disabled={buttons.length >= 3 || onlyCTA} onClick={() => addButton('QUICK_REPLY')}>+ Réponse rapide</button>
                        <button type="button" className="btn btn-sm" disabled={buttons.length >= 2 || onlyQR}  onClick={() => addButton('URL')}>+ Lien</button>
                        <button type="button" className="btn btn-sm" disabled={buttons.length >= 2 || onlyQR}  onClick={() => addButton('PHONE_NUMBER')}>+ Téléphone</button>
                    </div>
                </div>
                <div style={{ fontSize: '0.7rem', color: 'var(--text-muted)', marginBottom: '0.5rem' }}>
                    WhatsApp autorise <strong>soit</strong> jusqu'à 3 boutons "Réponse rapide" (QUICK_REPLY),
                    <strong> soit</strong> jusqu'à 2 boutons d'action (Lien/Téléphone). Pas de mix.
                    Seuls les clics sur QUICK_REPLY déclenchent un webhook.
                </div>
                {buttons.length === 0 && (
                    <div style={{ fontSize: '0.75rem', color: 'var(--text-muted)', fontStyle: 'italic' }}>Aucun bouton - message texte simple.</div>
                )}
                {buttons.map((b, i) => (
                    <div key={i} style={{ display: 'grid', gridTemplateColumns: '110px 1fr 1fr auto', gap: '0.5rem', alignItems: 'center', marginBottom: '0.4rem' }}>
                        <span className="badge badge-info" style={{ textAlign: 'center' }}>
                            {b.type === 'QUICK_REPLY' ? 'Réponse' : b.type === 'URL' ? 'Lien' : 'Téléphone'}
                        </span>
                        <input value={b.text} onChange={e => updateButton(i, { text: e.target.value.slice(0, 20) })}
                            placeholder="Libellé (20 car. max)"
                            style={{ padding: '0.4rem', borderRadius: 6, border: '1px solid var(--border)' }} />
                        {b.type === 'QUICK_REPLY' && (
                            <input value={b.payload || ''} onChange={e => updateButton(i, { payload: e.target.value })}
                                placeholder="Payload (ex: CONFIRM)"
                                style={{ padding: '0.4rem', borderRadius: 6, border: '1px solid var(--border)' }} />
                        )}
                        {b.type === 'URL' && (
                            <input value={b.url || ''} onChange={e => updateButton(i, { url: e.target.value })}
                                placeholder="https://…"
                                style={{ padding: '0.4rem', borderRadius: 6, border: '1px solid var(--border)' }} />
                        )}
                        {b.type === 'PHONE_NUMBER' && (
                            <input value={b.phone || ''} onChange={e => updateButton(i, { phone: e.target.value })}
                                placeholder="+41…"
                                style={{ padding: '0.4rem', borderRadius: 6, border: '1px solid var(--border)' }} />
                        )}
                        <button type="button" className="btn btn-sm btn-danger" onClick={() => removeButton(i)}>✕</button>
                    </div>
                ))}

                {buttons.some(b => b.type === 'QUICK_REPLY') && (
                    <div style={{ marginTop: '0.75rem', paddingTop: '0.75rem', borderTop: '1px dashed var(--border)' }}>
                        <div style={{ fontSize: '0.8rem', fontWeight: 600, marginBottom: '0.35rem' }}>Webhook clic (POST JSON)</div>
                        <input value={webhookUrl} onChange={e => setWebhookUrl(e.target.value)}
                            placeholder="https://votre-app.com/webhook/vocal-button"
                            style={{ width: '100%', padding: '0.45rem', borderRadius: 6, border: '1px solid var(--border)', marginBottom: '0.4rem' }} />
                        <input value={webhookSecret} onChange={e => setWebhookSecret(e.target.value)}
                            placeholder="Secret HMAC (optionnel) - envoyé via header X-Vocal-Signature"
                            style={{ width: '100%', padding: '0.45rem', borderRadius: 6, border: '1px solid var(--border)' }} />
                        <div style={{ fontSize: '0.7rem', color: 'var(--text-muted)', marginTop: '0.3rem' }}>
                            Quand l'utilisateur clique sur un bouton "Réponse rapide", Vocal envoie un POST JSON contenant
                            <code> button.text, button.payload, from, to, template_name, conversation_id, message_sid, timestamp </code>.
                        </div>
                    </div>
                )}
            </div>

            <label style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', marginTop: '1rem', fontSize: '0.9rem' }}>
                <input type="checkbox" checked={submitToWA} onChange={e => setSubmitToWA(e.target.checked)} />
                Soumettre immédiatement à WhatsApp pour approbation (sinon créé en "Publié")
            </label>

            <div style={{ display: 'flex', gap: '0.5rem', marginTop: '1rem' }}>
                <button className="btn btn-secondary" onClick={onCancel}>Annuler</button>
                <button className="btn btn-primary" onClick={create} disabled={busy}>{busy ? 'Création…' : 'Créer le template'}</button>
            </div>
        </div>
    );
};

const WhatsappSendersView = () => {
    const { notify } = useApp();
    const [senders, setSenders] = useState([]);
    const [loading, setLoading] = useState(true);
    const [showWizard, setShowWizard] = useState(false);
    const [otpEditing, setOtpEditing] = useState(null); // sender row in OTP mode
    const [otpDraft, setOtpDraft] = useState({ code: '', method: 'sms' });
    const [editingProfile, setEditingProfile] = useState(null);
    const [selectedSenderId, setSelectedSenderId] = useState(null);

    const load = useCallback(async () => {
        setLoading(true);
        try {
            const r = await api.get('/my/whatsapp-senders');
            if (r?.error) { notify.error(r.error); return; }
            setSenders(r.senders || []);
        } catch (e) { notify.error('Erreur chargement senders'); }
        finally { setLoading(false); }
    }, [notify]);
    useEffect(() => { load(); }, [load]);

    const refresh = async (id) => {
        try {
            const r = await api.post(`/my/whatsapp-senders/${id}/refresh`, {});
            if (r?.error) notify.error(r.error);
            else { notify.success('Statut actualisé'); load(); }
        } catch (e) { notify.error('Erreur refresh'); }
    };
    const syncWebhooks = async (id) => {
        if (!confirm('Publier les URLs de webhooks Vocal (réception, fallback, status) pour ce sender ?')) return;
        try {
            const r = await api.post(`/my/whatsapp-senders/${id}/sync-webhooks`, {});
            if (r?.error) {
                notify.error(r.error + (r.detail?.message ? ' - ' + r.detail.message : ''));
                return;
            }
            notify.success('Webhooks synchronisés (réception + fallback + status)');
            load();
        } catch (e) { notify.error('Erreur sync webhooks'); }
    };
    const remove = async (id) => {
        if (!confirm('Supprimer ce sender WhatsApp ? (action irréversible)')) return;
        try {
            const r = await api.del(`/my/whatsapp-senders/${id}`);
            if (r?.error) notify.error(r.error);
            else { notify.success('Sender supprimé'); load(); }
        } catch (e) { notify.error('Erreur suppression'); }
    };
    const submitOtp = async () => {
        if (!otpEditing) return;
        try {
            const r = await api.post(`/my/whatsapp-senders/${otpEditing.id}/verify`, otpDraft);
            if (r?.error) { notify.error(r.error); return; }
            notify.success('OTP soumis');
            setOtpEditing(null); setOtpDraft({ code: '', method: 'sms' });
            load();
        } catch (e) { notify.error('Erreur OTP'); }
    };

    const statusBadge = (s) => {
        const meta = SENDER_STATUS_META[s] || { label: s, cls: 'badge-gray', desc: '' };
        return <span className={`badge ${meta.cls}`} title={meta.desc}>{meta.label}</span>;
    };

    if (selectedSenderId) {
        const sender = senders.find(s => s.id === selectedSenderId);
        return <WhatsappSenderDetailView
                    sender={sender}
                    onBack={() => setSelectedSenderId(null)}
                    onChanged={load} />;
    }

    return (
        <>
            <div className="page-header">
                <div>
                    <h1 className="page-title">WhatsApp Business - Comptes</h1>
                    <p className="page-subtitle">
                        Enregistrer vos numéros comme expéditeurs WhatsApp Business. La validation est faite par Meta (1 à 30 minutes).
                    </p>
                </div>
                <div style={{ display: 'flex', gap: '0.5rem' }}>
                    <button className="btn btn-secondary" onClick={load}><Icons.Refresh className="w-4 h-4" /> Actualiser</button>
                    <button className="btn btn-secondary" onClick={async () => {
                        const phone = prompt('Numéro E.164 du sender WhatsApp déjà créé (ex: +41225394800) :');
                        if (!phone) return;
                        try {
                            const r = await api.post('/my/whatsapp-senders/import-existing', { phone_number: phone, sync_webhooks: true });
                            if (r?.error) { notify.error(r.error + (r.available ? ' - disponibles: ' + r.available.join(', ') : '')); return; }
                            notify.success('Sender importé et webhooks synchronisés');
                            load();
                        } catch (e) { notify.error('Erreur import sender'); }
                    }}><Icons.Download className="w-4 h-4" /> Importer existant</button>
                    <button className="btn btn-primary" onClick={() => setShowWizard(true)}>+ Activer un numéro</button>
                </div>
            </div>

            <div className="page-content">
                {loading && <div style={{ padding: '2rem', textAlign: 'center', color: 'var(--text-muted)' }}>Chargement…</div>}

                {!loading && senders.length === 0 && (
                    <div className="card" style={{ padding: '3rem 2rem', textAlign: 'center' }}>
                        <Icons.Chat className="w-12 h-12" style={{ margin: '0 auto 1rem', opacity: 0.4 }} />
                        <h3 style={{ margin: '0 0 0.5rem' }}>Aucun sender WhatsApp configuré</h3>
                        <p style={{ color: 'var(--text-muted)', maxWidth: 480, margin: '0 auto 1.5rem', fontSize: '0.9rem' }}>
                            Pour activer WhatsApp Business sur un numéro, vous devez d'abord lier votre compte WhatsApp Business (WABA) chez Meta à votre compte Vocal,
                            puis enregistrer le numéro. Vocal automatise tout sauf l'étape Meta (à faire une fois côté Meta).
                        </p>
                        <button className="btn btn-primary" onClick={() => setShowWizard(true)}>+ Activer mon premier numéro</button>
                    </div>
                )}

                {!loading && senders.length > 0 && (
                    <div className="card">
                        <div className="table-container">
                            <table className="data-table">
                                <thead>
                                    <tr>
                                        <th>Statut</th>
                                        <th>Numéro</th>
                                        <th>Nom affiché</th>
                                        <th>WABA</th>
                                        <th>Sender SID</th>
                                        <th>Source</th>
                                        <th>Mis à jour</th>
                                        <th style={{ textAlign: 'right' }}>Actions</th>
                                    </tr>
                                </thead>
                                <tbody>
                                    {senders.map(s => (
                                        <React.Fragment key={s.id}>
                                            <tr onClick={() => setSelectedSenderId(s.id)} style={{ cursor: 'pointer' }}>
                                                <td>{statusBadge(s.status)}{s.failure_reason && <div style={{ fontSize: '0.7rem', color: 'var(--danger, #c00)', marginTop: 4 }}>{s.failure_reason}</div>}</td>
                                                <td style={{ fontFamily: 'ui-monospace, monospace', fontWeight: 600 }}>{s.phone_number}</td>
                                                <td>{s.display_name || <em style={{ color: 'var(--text-muted)' }}>-</em>}</td>
                                                <td style={{ fontFamily: 'ui-monospace, monospace', fontSize: '0.75rem' }}>{s.waba_id || '-'}</td>
                                                <td style={{ fontFamily: 'ui-monospace, monospace', fontSize: '0.7rem' }}>{s.sender_sid || '-'}</td>
                                                <td>{s.source === 'twilio' ? 'Vocal' : 'BYON'}</td>
                                                <td style={{ fontSize: '0.75rem' }}>{s.last_polled_at ? formatDateTime(s.last_polled_at) : formatDateTime(s.created_at)}</td>
                                                <td style={{ textAlign: 'right' }}>
                                                    <div onClick={e => e.stopPropagation()} style={{ display: 'inline-flex', gap: '0.3rem', flexWrap: 'wrap', justifyContent: 'flex-end' }}>
                                                        <button className="btn btn-secondary btn-sm" onClick={() => refresh(s.id)} title="Rafraîchir le statut" style={{ display: 'inline-flex', alignItems: 'center', padding: '0.3rem 0.5rem' }}><Icons.Refresh className="w-3.5 h-3.5" /></button>
                                                        {s.status === 'VERIFYING' && s.source === 'byon' && (
                                                            <button className="btn btn-primary btn-sm" onClick={() => setOtpEditing(s)}>Saisir OTP</button>
                                                        )}
                                                    </div>
                                                </td>
                                            </tr>
                                            {otpEditing?.id === s.id && (
                                                <tr>
                                                    <td colSpan={8} style={{ background: '#fffbeb', padding: '1rem' }}>
                                                        <div style={{ display: 'flex', gap: '0.75rem', alignItems: 'center', flexWrap: 'wrap' }}>
                                                            <div style={{ fontSize: '0.85rem' }}>
                                                                <strong>Code OTP reçu de Meta</strong> (par SMS ou appel sur <code>{s.phone_number}</code>)
                                                            </div>
                                                            <select value={otpDraft.method} onChange={e => setOtpDraft({ ...otpDraft, method: e.target.value })}
                                                                style={{ padding: '0.4rem 0.6rem' }}>
                                                                <option value="sms">SMS</option>
                                                                <option value="voice">Appel vocal</option>
                                                            </select>
                                                            <input type="text" inputMode="numeric" pattern="\\d{3,10}" value={otpDraft.code}
                                                                onChange={e => setOtpDraft({ ...otpDraft, code: e.target.value })}
                                                                placeholder="123456" maxLength={10}
                                                                style={{ padding: '0.4rem 0.6rem', fontFamily: 'ui-monospace, monospace', width: 140 }} />
                                                            <button className="btn btn-primary btn-sm" onClick={submitOtp}>Valider</button>
                                                            <button className="btn btn-secondary btn-sm" onClick={() => { setOtpEditing(null); setOtpDraft({ code: '', method: 'sms' }); }}>Annuler</button>
                                                        </div>
                                                    </td>
                                                </tr>
                                            )}
                                        </React.Fragment>
                                    ))}
                                </tbody>
                            </table>
                        </div>
                    </div>
                )}
            </div>

            {showWizard && <WhatsappSenderWizard onClose={() => setShowWizard(false)} onCreated={() => { setShowWizard(false); load(); }} />}
            {editingProfile && <WhatsappSenderProfileModal sender={editingProfile} onClose={() => setEditingProfile(null)} onSaved={() => { setEditingProfile(null); load(); }} />}
        </>
    );
};

// Wizard : créer un sender WhatsApp Business
const WhatsappSenderWizard = ({ onClose, onCreated }) => {
    const { notify } = useApp();
    const [lines, setLines] = useState([]);
    const [step, setStep] = useState(1);
    const [submitting, setSubmitting] = useState(false);
    const [form, setForm] = useState({
        source: 'twilio',
        line_id: '',
        phone_number: '',
        waba_id: '',
        display_name: '',
        about: '',
        description: '',
        vertical: 'Professional Services',
        website: '',
        email: '',
        address: '',
        logo_url: '',
    });

    useEffect(() => {
        api.get('/my/lines').then(r => setLines(r?.lines || [])).catch(() => {});
    }, []);

    const setField = (k, v) => setForm(f => ({ ...f, [k]: v }));
    const onLineChange = (id) => {
        const l = lines.find(x => String(x.id) === String(id));
        setForm(f => ({ ...f, line_id: id, phone_number: l?.phone_number || f.phone_number }));
    };

    const submit = async () => {
        if (!/^\+\d{6,16}$/.test(form.phone_number)) return notify.error('Numéro E.164 invalide');
        if (!form.waba_id.trim()) return notify.error('waba_id requis (récupéré via embedded signup Vocal)');
        if (!form.display_name.trim()) return notify.error('Nom affiché requis');
        setSubmitting(true);
        try {
            const payload = { ...form, line_id: form.line_id || null };
            const r = await api.post('/my/whatsapp-senders', payload);
            if (r?.error) { notify.error(r.error + (r.detail ? ` (${JSON.stringify(r.detail).slice(0, 200)})` : '')); return; }
            notify.success('Sender envoyé à Vocal. Validation Meta en cours.');
            onCreated();
        } catch (e) { notify.error('Erreur création: ' + e.message); }
        finally { setSubmitting(false); }
    };

    return (
        <div className="modal-overlay" onClick={onClose}
            style={{ position: 'fixed', inset: 0, background: 'rgba(15,23,42,0.6)', backdropFilter: 'blur(4px)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 9000, padding: '1.5rem' }}>
            <div className="modal-content" style={{ maxWidth: 720, width: '100%', background: '#fff', borderRadius: 14, boxShadow: '0 25px 60px rgba(0,0,0,0.3)', maxHeight: '90vh', display: 'flex', flexDirection: 'column' }} onClick={e => e.stopPropagation()}>
                <div className="modal-header" style={{ padding: '1.25rem 1.5rem', borderBottom: '1px solid #e5e7eb', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
                    <h2 style={{ margin: 0, fontSize: '1.15rem' }}>Activer WhatsApp Business - Étape {step}/3</h2>
                    <button className="btn btn-secondary btn-sm" onClick={onClose}>×</button>
                </div>
                <div className="modal-body" style={{ padding: '1.25rem 1.5rem', overflowY: 'auto', flex: 1 }}>
                    {step === 1 && (
                        <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
                            <div style={{ background: '#fef3c7', padding: '0.75rem 1rem', borderRadius: 8, fontSize: '0.85rem' }}>
                                ⚠️ <strong>Avant de continuer :</strong> vous devez avoir lié votre compte WhatsApp Business (WABA) à votre compte Vocal.
                                Récupérez ensuite votre <strong>WABA ID</strong> et continuez ici.
                            </div>
                            <label>
                                <div style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: 4 }}>Source du numéro</div>
                                <select value={form.source} onChange={e => setField('source', e.target.value)} style={{ width: '100%', padding: '0.5rem' }}>
                                    <option value="twilio">Numéro Vocal (recommandé)</option>
                                    <option value="byon">Mon propre numéro (BYON, OTP requis)</option>
                                </select>
                            </label>
                            {form.source === 'twilio' && (
                                <label>
                                    <div style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: 4 }}>Ligne Vocal à utiliser</div>
                                    <select value={form.line_id} onChange={e => onLineChange(e.target.value)} style={{ width: '100%', padding: '0.5rem' }}>
                                        <option value="">- Sélectionner une ligne -</option>
                                        {lines.map(l => <option key={l.id} value={l.id}>{l.label || '(sans nom)'} - {l.phone_number}</option>)}
                                    </select>
                                </label>
                            )}
                            <label>
                                <div style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: 4 }}>Numéro à activer (E.164)</div>
                                <input value={form.phone_number} onChange={e => setField('phone_number', e.target.value)}
                                    placeholder="+41783020487" style={{ width: '100%', padding: '0.5rem', fontFamily: 'ui-monospace, monospace' }} />
                            </label>
                            <label>
                                <div style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: 4 }}>WhatsApp Business Account ID (Meta)</div>
                                <input value={form.waba_id} onChange={e => setField('waba_id', e.target.value)}
                                    placeholder="123456789012345" style={{ width: '100%', padding: '0.5rem', fontFamily: 'ui-monospace, monospace' }} />
                                <div style={{ fontSize: '0.75rem', color: 'var(--text-muted)', marginTop: 4 }}>
                                    Visible dans votre console Vocal après avoir lié votre WABA.
                                </div>
                            </label>
                        </div>
                    )}

                    {step === 2 && (
                        <div style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
                            <label>
                                <div style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: 4 }}>Nom affiché (Display Name) *</div>
                                <input value={form.display_name} onChange={e => setField('display_name', e.target.value)}
                                    placeholder="Vocal · Support" maxLength={50} style={{ width: '100%', padding: '0.5rem' }} />
                                <div style={{ fontSize: '0.75rem', color: 'var(--text-muted)', marginTop: 4 }}>
                                    Soumis à approbation Meta (~24h). Doit refléter votre marque.
                                </div>
                            </label>
                            <label>
                                <div style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: 4 }}>Catégorie business</div>
                                <select value={form.vertical} onChange={e => setField('vertical', e.target.value)} style={{ width: '100%', padding: '0.5rem' }}>
                                    {['Automotive','Beauty, Spa and Salon','Clothing and Apparel','Education','Entertainment','Event Planning and Service','Finance and Banking','Food and Grocery','Public Service','Hotel and Lodging','Medical and Health','Non-profit','Professional Services','Shopping and Retail','Travel and Transportation','Restaurant','Other'].map(v => <option key={v}>{v}</option>)}
                                </select>
                            </label>
                            <label>
                                <div style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: 4 }}>Description courte (about)</div>
                                <input value={form.about} onChange={e => setField('about', e.target.value)}
                                    maxLength={139} placeholder="Service client Vocal" style={{ width: '100%', padding: '0.5rem' }} />
                            </label>
                            <label>
                                <div style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: 4 }}>Description complète</div>
                                <textarea value={form.description} onChange={e => setField('description', e.target.value)}
                                    rows={3} placeholder="Nous aidons les entreprises à automatiser leur accueil téléphonique..." style={{ width: '100%', padding: '0.5rem' }} />
                            </label>
                        </div>
                    )}

                    {step === 3 && (
                        <div style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
                            <label>
                                <div style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: 4 }}>Site web</div>
                                <input value={form.website} onChange={e => setField('website', e.target.value)}
                                    placeholder="https://helvia.app" style={{ width: '100%', padding: '0.5rem' }} />
                            </label>
                            <label>
                                <div style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: 4 }}>Email business</div>
                                <input type="email" value={form.email} onChange={e => setField('email', e.target.value)}
                                    placeholder="contact@helvia.app" style={{ width: '100%', padding: '0.5rem' }} />
                            </label>
                            <label>
                                <div style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: 4 }}>Adresse</div>
                                <input value={form.address} onChange={e => setField('address', e.target.value)}
                                    placeholder="Rue, NPA Ville, Pays" style={{ width: '100%', padding: '0.5rem' }} />
                            </label>
                            <label>
                                <div style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: 4 }}>URL du logo (carré, &lt; 5 Mo)</div>
                                <input value={form.logo_url} onChange={e => setField('logo_url', e.target.value)}
                                    placeholder="https://helvia.app/logo-wa.png" style={{ width: '100%', padding: '0.5rem' }} />
                            </label>
                            <div style={{ background: '#dbeafe', padding: '0.75rem 1rem', borderRadius: 8, fontSize: '0.85rem' }}>
                                Après soumission, le sender passera par les états <strong>CREATING → VERIFYING → ONLINE</strong>.
                                {form.source === 'byon' && <> Pour un numéro externe (BYON), vous recevrez un <strong>OTP par SMS/appel</strong> de Meta sur le numéro à activer, que vous devrez saisir ici.</>}
                            </div>
                        </div>
                    )}
                </div>
                <div className="modal-footer" style={{ display: 'flex', justifyContent: 'space-between', gap: '0.5rem', padding: '1rem 1.5rem', borderTop: '1px solid #e5e7eb', background: '#f9fafb', borderRadius: '0 0 14px 14px' }}>
                    <button className="btn btn-secondary" disabled={step === 1} onClick={() => setStep(s => Math.max(1, s - 1))}>← Précédent</button>
                    {step < 3 ? (
                        <button className="btn btn-primary" onClick={() => setStep(s => s + 1)}>Suivant →</button>
                    ) : (
                        <button className="btn btn-primary" disabled={submitting} onClick={submit}>{submitting ? 'Envoi…' : '🚀 Activer ce numéro'}</button>
                    )}
                </div>
            </div>
        </div>
    );
};

const WhatsappSenderProfileModal = ({ sender, onClose, onSaved }) => {
    const { notify } = useApp();
    const [f, setF] = useState({
        display_name: sender.display_name || '',
        about: sender.about || '',
        description: sender.description || '',
        vertical: sender.vertical || 'Professional Services',
        website: sender.website || '',
        email: sender.email || '',
        address: sender.address || '',
        logo_url: sender.logo_url || '',
    });
    const [saving, setSaving] = useState(false);
    const save = async () => {
        setSaving(true);
        try {
            const r = await api.put(`/my/whatsapp-senders/${sender.id}`, f);
            if (r?.error) notify.error(r.error);
            else { notify.success('Profil mis à jour'); onSaved(); }
        } catch (e) { notify.error('Erreur: ' + e.message); }
        finally { setSaving(false); }
    };
    const inp = (k, label, opts = {}) => (
        <label key={k}>
            <div style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: 4 }}>{label}</div>
            <input value={f[k] || ''} onChange={e => setF({ ...f, [k]: e.target.value })}
                placeholder={opts.placeholder || ''} maxLength={opts.maxLength}
                style={{ width: '100%', padding: '0.5rem' }} />
        </label>
    );
    return (
        <div className="modal-overlay" onClick={onClose}
            style={{ position: 'fixed', inset: 0, background: 'rgba(15,23,42,0.6)', backdropFilter: 'blur(4px)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 9000, padding: '1.5rem' }}>
            <div className="modal-content" style={{ maxWidth: 560, width: '100%', background: '#fff', borderRadius: 14, boxShadow: '0 25px 60px rgba(0,0,0,0.3)', maxHeight: '90vh', display: 'flex', flexDirection: 'column' }} onClick={e => e.stopPropagation()}>
                <div className="modal-header" style={{ padding: '1.25rem 1.5rem', borderBottom: '1px solid #e5e7eb', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
                    <h2 style={{ margin: 0, fontSize: '1.15rem' }}>Profil business - {sender.phone_number}</h2>
                    <button className="btn btn-secondary btn-sm" onClick={onClose}>×</button>
                </div>
                <div className="modal-body" style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem', padding: '1.25rem 1.5rem', overflowY: 'auto', flex: 1 }}>
                    {inp('display_name', 'Nom affiché', { maxLength: 50 })}
                    {inp('about', 'About (court)', { maxLength: 139 })}
                    <label>
                        <div style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: 4 }}>Description complète</div>
                        <textarea value={f.description || ''} onChange={e => setF({ ...f, description: e.target.value })}
                            rows={3} style={{ width: '100%', padding: '0.5rem' }} />
                    </label>
                    <label>
                        <div style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: 4 }}>Catégorie</div>
                        <select value={f.vertical || ''} onChange={e => setF({ ...f, vertical: e.target.value })} style={{ width: '100%', padding: '0.5rem' }}>
                            {['Automotive','Beauty, Spa and Salon','Clothing and Apparel','Education','Entertainment','Event Planning and Service','Finance and Banking','Food and Grocery','Public Service','Hotel and Lodging','Medical and Health','Non-profit','Professional Services','Shopping and Retail','Travel and Transportation','Restaurant','Other'].map(v => <option key={v}>{v}</option>)}
                        </select>
                    </label>
                    {inp('website', 'Site web')}
                    {inp('email',   'Email business')}
                    {inp('address', 'Adresse')}
                    {inp('logo_url','URL logo')}
                </div>
                <div className="modal-footer" style={{ display: 'flex', justifyContent: 'flex-end', gap: '0.5rem', padding: '1rem 1.5rem', borderTop: '1px solid #e5e7eb', background: '#f9fafb', borderRadius: '0 0 14px 14px' }}>
                    <button className="btn btn-secondary" onClick={onClose}>Annuler</button>
                    <button className="btn btn-primary" disabled={saving} onClick={save}>{saving ? 'Enregistrement…' : 'Enregistrer'}</button>
                </div>
            </div>
        </div>
    );
};

const MessagesView = ({ kind }) => {
    const { notify } = useApp();
    const isSms = kind === 'sms';
    const [data, setData] = useState({ items: [], total: 0, page: 1, total_pages: 1 });
    const [loading, setLoading] = useState(true);
    const [refreshing, setRefreshing] = useState(false);
    const [page, setPage] = useState(1);

    const load = useCallback(async (p = page, opts = {}) => {
        if (opts.background) setRefreshing(true); else setLoading(true);
        try {
            const d = await api.get(`/my/${isSms ? 'sms' : 'whatsapp'}?page=${p}&limit=30`);
            if (d?.error) notify.error(d.error);
            const items = (isSms ? d?.sms : d?.whatsapp) || [];
            setData({ items, total: d?.total || 0, page: d?.page || p, total_pages: d?.total_pages || 1 });
        } catch (e) { notify.error('Erreur chargement messages'); }
        finally { setLoading(false); setRefreshing(false); }
    }, [isSms, notify]);

    useEffect(() => { load(page); }, [page]);

    return (
        <>
            <div className="page-header">
                <div>
                    <h1 className="page-title">{isSms ? 'SMS' : 'WhatsApp'}</h1>
                    <p className="page-subtitle">{data.total} message(s)</p>
                </div>
                <div className="page-actions">
                    <button className="btn btn-secondary btn-sm" onClick={() => load(page, { background: true })} disabled={refreshing}>
                        <Icons.Refresh className="w-4 h-4" /> {refreshing ? 'Mise à jour...' : 'Actualiser'}
                    </button>
                </div>
            </div>
            <div className="page-content">
                <div className="card" style={{ overflow: 'hidden' }}>
                    {loading ? (
                        <SkeletonTable rows={8} cols={isSms ? 6 : 5} />
                    ) : data.items.length === 0 ? (
                        <div style={{ padding: '4rem 2rem', textAlign: 'center', color: 'var(--text-muted)' }}>
                            {isSms ? <Icons.Send className="w-10 h-10" style={{ margin: '0 auto 0.75rem', opacity: 0.4 }} /> : <Icons.Chat className="w-10 h-10" style={{ margin: '0 auto 0.75rem', opacity: 0.4 }} />}
                            <p style={{ fontWeight: 600, fontSize: '0.9rem', margin: 0 }}>Aucun message</p>
                        </div>
                    ) : (
                        <div className="table-container">
                            <table className="data-table">
                                <thead><tr><th>Date</th><th>Destinataire</th>{isSms && <th>Expéditeur</th>}<th>Message</th><th>Ligne</th><th>Statut</th></tr></thead>
                                <tbody style={{ opacity: refreshing ? 0.6 : 1, transition: 'opacity 0.15s' }}>
                                    {data.items.map(m => (
                                        <tr key={m.id}>
                                            <td style={{ whiteSpace: 'nowrap', fontSize: '0.8rem' }}>{formatDateTime(m.created_at)}</td>
                                            <td style={{ fontWeight: 500, fontFamily: 'ui-monospace, monospace', fontSize: '0.8rem' }}>{m.to_number || '-'}</td>
                                            {isSms && <td style={{ fontSize: '0.8rem' }}>{m.sender || '-'}</td>}
                                            <td style={{ fontSize: '0.8rem', maxWidth: '300px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{m.message || '-'}</td>
                                            <td style={{ fontSize: '0.8rem' }}>{m.line_label || m.line_phone || '-'}</td>
                                            <td><span className={`badge ${m.status === 'sent' ? 'badge-success' : (m.status === 'failed' || m.status === 'error' ? 'badge-danger' : 'badge-gray')}`}>{m.status === 'sent' ? 'Envoyé' : m.status}</span></td>
                                        </tr>
                                    ))}
                                </tbody>
                            </table>
                        </div>
                    )}
                    {data.total_pages > 1 && (
                        <div style={{ padding: '0.75rem 1.25rem', borderTop: '1px solid var(--border)', display: 'flex', justifyContent: 'space-between', alignItems: 'center', background: '#f8fafc' }}>
                            <span style={{ fontSize: '0.8rem', color: 'var(--text-muted)' }}>{data.items.length} sur {data.total}</span>
                            <div style={{ display: 'flex', gap: '0.5rem' }}>
                                <button className="btn btn-secondary btn-sm" onClick={() => setPage(p => Math.max(1, p - 1))} disabled={page <= 1}><Icons.ChevronLeft className="w-4 h-4" /></button>
                                <span style={{ fontSize: '0.8rem', fontWeight: 600, minWidth: '80px', textAlign: 'center' }}>{page} / {data.total_pages}</span>
                                <button className="btn btn-secondary btn-sm" onClick={() => setPage(p => Math.min(data.total_pages, p + 1))} disabled={page >= data.total_pages}><Icons.ChevronRight className="w-4 h-4" /></button>
                            </div>
                        </div>
                    )}
                </div>
            </div>
        </>
    );
};

// ==================== API KEYS ====================
const ApiKeysView = () => {
    const { notify } = useApp();
    const [keys, setKeys] = useState([]);
    const [lines, setLines] = useState([]);
    const [loading, setLoading] = useState(true);
    const [revealed, setRevealed] = useState({});
    const [copied, setCopied] = useState(null);
    const [showCreate, setShowCreate] = useState(false);
    const [newLabel, setNewLabel] = useState('');
    const [newLineIds, setNewLineIds] = useState([]);
    const [creating, setCreating] = useState(false);
    const [createdKey, setCreatedKey] = useState(null);

    const load = useCallback(async () => {
        setLoading(true);
        try {
            const [k, l] = await Promise.all([api.get('/my/api-keys'), api.get('/my/lines')]);
            setKeys(Array.isArray(k) ? k : []);
            setLines(l?.lines || []);
        } catch (e) { notify.error('Erreur chargement clés'); }
        finally { setLoading(false); }
    }, [notify]);

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

    const copy = (text, id) => { navigator.clipboard.writeText(text); setCopied(id); setTimeout(() => setCopied(null), 1800); };
    const mask = (k) => k ? `${k.slice(0, 6)}${'•'.repeat(20)}${k.slice(-4)}` : '';

    const create = async () => {
        if (!newLabel.trim()) return notify.error('Donnez un nom à la clé');
        setCreating(true);
        try {
            const res = await api.post('/my/api-keys', { label: newLabel, line_ids: newLineIds });
            if (res?.error) { notify.error(res.error); return; }
            if (res?.key_value) {
                setCreatedKey(res.key_value);
                setNewLabel('');
                setNewLineIds([]);
                notify.success('Clé créée');
                load();
            }
        } catch (e) { notify.error('Erreur: ' + e.message); }
        finally { setCreating(false); }
    };

    const toggle = async (k) => {
        try {
            await api.put(`/my/api-keys/${k.id}`, { is_active: k.is_active ? 0 : 1 });
            notify.success(k.is_active ? 'Clé désactivée' : 'Clé activée');
            load();
        } catch (e) { notify.error('Erreur'); }
    };

    const remove = async (k) => {
        if (!confirm(`Supprimer la clé "${k.label || k.key_value}" ?`)) return;
        try { await api.del(`/my/api-keys/${k.id}`); notify.success('Clé supprimée'); load(); }
        catch (e) { notify.error('Erreur'); }
    };

    return (
        <>
            <div className="page-header">
                <div>
                    <h1 className="page-title">Clés API</h1>
                    <p className="page-subtitle">Intégrez Vocal dans vos applications</p>
                </div>
                <div className="page-actions">
                    <button className="btn btn-secondary btn-sm" onClick={load}><Icons.Refresh className="w-4 h-4" /> Actualiser</button>
                    <button className="btn btn-primary btn-sm" onClick={() => { setShowCreate(true); setCreatedKey(null); }}>
                        <Icons.Plus className="w-4 h-4" /> Nouvelle clé
                    </button>
                </div>
            </div>
            <div className="page-content">
                <div className="card" style={{ padding: '1rem 1.25rem', marginBottom: '1.5rem', background: '#eef2ff', border: '1px solid #c7d2fe' }}>
                    <div style={{ fontSize: '0.85rem', color: 'var(--text-main)' }}>
                        <strong>Documentation :</strong> Consultez le guide d'intégration sur <a href="https://docs.helvia.app" target="_blank" rel="noreferrer" style={{ color: 'var(--primary-dark)', fontWeight: 600 }}>docs.helvia.app</a> pour utiliser ces clés API dans vos applications tierces.
                    </div>
                </div>

                {showCreate && (
                    <div className="card" style={{ padding: '1.25rem', marginBottom: '1.5rem', border: '2px solid var(--primary-light)' }}>
                        {createdKey ? (
                            <>
                                <h3 style={{ margin: '0 0 0.5rem', fontSize: '0.95rem', fontWeight: 700 }}>Clé API créée</h3>
                                <p style={{ margin: '0 0 1rem', fontSize: '0.8rem', color: 'var(--text-muted)' }}>
                                    Copiez cette clé maintenant • elle reste accessible plus tard mais conservez-la précieusement.
                                </p>
                                <div style={{ display: 'flex', gap: '0.5rem', alignItems: 'center', background: '#f8fafc', padding: '0.75rem', borderRadius: '0.5rem', border: '1px solid var(--border)' }}>
                                    <code style={{ flex: 1, fontFamily: 'ui-monospace, monospace', fontSize: '0.85rem', wordBreak: 'break-all' }}>{createdKey}</code>
                                    <button className="btn btn-primary btn-sm" onClick={() => copy(createdKey, 'new')}>
                                        {copied === 'new' ? <><Icons.Check className="w-4 h-4" /> Copié</> : <><Icons.Copy className="w-4 h-4" /> Copier</>}
                                    </button>
                                </div>
                                <div style={{ marginTop: '1rem', textAlign: 'right' }}>
                                    <button className="btn btn-secondary btn-sm" onClick={() => { setShowCreate(false); setCreatedKey(null); }}>Fermer</button>
                                </div>
                            </>
                        ) : (
                            <>
                                <h3 style={{ margin: '0 0 1rem', fontSize: '0.95rem', fontWeight: 700 }}>Nouvelle clé API</h3>
                                <div className="form-group">
                                    <label className="form-label">Nom de la clé</label>
                                    <input className="form-input" value={newLabel} onChange={(e) => setNewLabel(e.target.value)} placeholder="ex: Site web, App mobile, Backoffice..." autoFocus />
                                </div>
                                {lines.length > 0 && (
                                    <div className="form-group">
                                        <label className="form-label">Lignes autorisées (laisser vide = aucune restriction)</label>
                                        <div style={{ display: 'flex', flexDirection: 'column', gap: '0.4rem', maxHeight: 200, overflow: 'auto', border: '1px solid var(--border)', borderRadius: '0.5rem', padding: '0.5rem' }}>
                                            {lines.map(l => (
                                                <label key={l.id} style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', cursor: 'pointer', padding: '0.25rem' }}>
                                                    <input type="checkbox" checked={newLineIds.includes(l.id)} onChange={() => setNewLineIds(prev => prev.includes(l.id) ? prev.filter(x => x !== l.id) : [...prev, l.id])} />
                                                    <span style={{ fontSize: '0.85rem', fontFamily: 'ui-monospace, monospace' }}>{l.phone_number}</span>
                                                    {l.label && <span style={{ fontSize: '0.8rem', color: 'var(--text-muted)' }}>({l.label})</span>}
                                                </label>
                                            ))}
                                        </div>
                                    </div>
                                )}
                                <div style={{ display: 'flex', gap: '0.5rem', justifyContent: 'flex-end' }}>
                                    <button className="btn btn-secondary btn-sm" onClick={() => setShowCreate(false)}>Annuler</button>
                                    <button className="btn btn-primary btn-sm" onClick={create} disabled={creating || !newLabel.trim()}>
                                        {creating ? 'Création...' : 'Créer la clé'}
                                    </button>
                                </div>
                            </>
                        )}
                    </div>
                )}

                {loading ? (
                    <SkeletonCards count={3} columns={1} />
                ) : keys.length === 0 ? (
                    <div className="card" style={{ padding: '4rem 2rem', textAlign: 'center' }}>
                        <Icons.Key className="w-12 h-12" style={{ margin: '0 auto 1rem', opacity: 0.4, color: 'var(--text-muted)' }} />
                        <h3 style={{ margin: '0 0 0.5rem', fontWeight: 600 }}>Aucune clé API</h3>
                        <p style={{ margin: 0, color: 'var(--text-muted)', fontSize: '0.875rem' }}>Créez votre première clé pour intégrer Vocal dans votre application.</p>
                        <button className="btn btn-primary" onClick={() => { setShowCreate(true); setCreatedKey(null); }} style={{ marginTop: '1.25rem' }}>
                            <Icons.Plus className="w-4 h-4" /> Nouvelle clé
                        </button>
                    </div>
                ) : (
                    <div style={{ display: 'grid', gap: '0.75rem' }}>
                        {keys.map(k => {
                            const isShown = revealed[k.id];
                            return (
                                <div key={k.id} className="card" style={{ padding: '1rem 1.25rem' }}>
                                    <div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem', marginBottom: '0.75rem', flexWrap: 'wrap' }}>
                                        <div style={{ flex: 1, minWidth: 200 }}>
                                            <div style={{ fontWeight: 700, fontSize: '0.95rem' }}>{k.label || 'Clé API'}</div>
                                            <div style={{ fontSize: '0.7rem', color: 'var(--text-muted)', marginTop: '0.2rem' }}>
                                                Créée le {formatDate(k.created_at)} · {k.calls_count || 0} appels
                                            </div>
                                        </div>
                                        <span className={`badge ${k.is_active ? 'badge-success' : 'badge-gray'}`}>{k.is_active ? 'Active' : 'Désactivée'}</span>
                                        <a className="btn btn-secondary btn-sm" target="_blank" rel="noopener"
                                           href={`https://docs.helvia.app/?apikey=${encodeURIComponent(k.key_value)}#wa-template`}
                                           title="Ouvrir la doc avec cette clé pré-remplie">📖 Tester</a>
                                        <button className="btn btn-secondary btn-sm" onClick={() => toggle(k)}>{k.is_active ? 'Désactiver' : 'Réactiver'}</button>
                                        <button className="btn btn-danger btn-sm btn-icon" onClick={() => remove(k)}><Icons.Trash className="w-4 h-4" /></button>
                                    </div>
                                    <div style={{ display: 'flex', gap: '0.5rem', alignItems: 'center', background: '#f8fafc', padding: '0.6rem 0.75rem', borderRadius: '0.5rem', border: '1px solid var(--border)' }}>
                                        <code style={{ flex: 1, fontFamily: 'ui-monospace, monospace', fontSize: '0.8rem', wordBreak: 'break-all' }}>
                                            {isShown ? k.key_value : mask(k.key_value)}
                                        </code>
                                        <button className="btn btn-secondary btn-sm btn-icon" onClick={() => setRevealed(prev => ({ ...prev, [k.id]: !prev[k.id] }))} title={isShown ? 'Masquer' : 'Afficher'}>
                                            {isShown ? <Icons.EyeOff className="w-4 h-4" /> : <Icons.Eye className="w-4 h-4" />}
                                        </button>
                                        <button className="btn btn-secondary btn-sm btn-icon" onClick={() => copy(k.key_value, k.id)} title="Copier">
                                            {copied === k.id ? <Icons.Check className="w-4 h-4" /> : <Icons.Copy className="w-4 h-4" />}
                                        </button>
                                    </div>
                                    {k.line_ids?.length > 0 && (
                                        <div style={{ marginTop: '0.75rem', fontSize: '0.75rem', color: 'var(--text-muted)' }}>
                                            Lignes autorisées : {k.line_ids.map(lid => {
                                                const l = lines.find(x => x.id === lid);
                                                return l ? l.phone_number : `#${lid}`;
                                            }).join(', ')}
                                        </div>
                                    )}
                                </div>
                            );
                        })}
                    </div>
                )}
            </div>
        </>
    );
};

// ==================== SETTINGS ====================
const SettingsView = () => {
    const { user, notify, refreshProfile, logout } = useApp();
    const [form, setForm] = useState({
        name: '', company: '', phone: '',
        weekly_report_enabled: true, weekly_report_email: '',
        low_balance_threshold: '', critical_balance_threshold: '',
    });
    const [savedAt, setSavedAt] = useState(null);
    const [saving, setSaving] = useState(false);
    const [loaded, setLoaded] = useState(false);

    useEffect(() => {
        (async () => {
            try {
                const data = await api.get('/my/profile');
                if (data?.error) notify.error(data.error);
                const c = data?.client || {};
                setForm({
                    name: data?.user?.name || c.name || '',
                    company: c.company || '',
                    phone: c.phone || '',
                    weekly_report_enabled: c.weekly_report_enabled === undefined ? true : !!c.weekly_report_enabled,
                    weekly_report_email: c.weekly_report_email || '',
                    low_balance_threshold: c.low_balance_threshold ?? '',
                    critical_balance_threshold: c.critical_balance_threshold ?? '',
                });
                setSavedAt(c.weekly_report_last_sent_at || null);
            } catch (e) {}
            finally { setLoaded(true); }
        })();
    }, []);

    const save = async () => {
        setSaving(true);
        try {
            const res = await api.put('/my/profile', form);
            if (res?.error) notify.error(res.error);
            else { notify.success('Profil mis à jour'); refreshProfile(); }
        } catch (e) { notify.error('Erreur'); }
        finally { setSaving(false); }
    };

    return (
        <>
            <div className="page-header">
                <div>
                    <h1 className="page-title">Paramètres</h1>
                    <p className="page-subtitle">Gérez votre profil</p>
                </div>
            </div>
            <div className="page-content">
                <div className="card" style={{ padding: '1.5rem', maxWidth: 640, marginBottom: '1.5rem' }}>
                    <h3 style={{ margin: '0 0 1.25rem', fontSize: '1rem', fontWeight: 700 }}>Mes informations</h3>

                    {!loaded ? (
                        <>
                            <Skeleton height={36} style={{ marginBottom: '1rem' }} />
                            <Skeleton height={36} style={{ marginBottom: '1rem' }} />
                            <Skeleton height={36} />
                        </>
                    ) : (
                        <>
                            <div className="form-group">
                                <label className="form-label">Email</label>
                                <input className="form-input" value={user?.email || ''} disabled style={{ opacity: 0.7 }} />
                            </div>
                            <div className="form-group">
                                <label className="form-label">Nom</label>
                                <input className="form-input" value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} placeholder="Votre nom" />
                            </div>
                            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem' }}>
                                <div className="form-group">
                                    <label className="form-label">Téléphone</label>
                                    <input className="form-input" value={form.phone} onChange={(e) => setForm({ ...form, phone: e.target.value })} placeholder="+41..." />
                                </div>
                                <div className="form-group">
                                    <label className="form-label">Entreprise</label>
                                    <input className="form-input" value={form.company} onChange={(e) => setForm({ ...form, company: e.target.value })} placeholder="Nom de votre entreprise" />
                                </div>
                            </div>
                            <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
                                <button className="btn btn-primary" onClick={save} disabled={saving}>{saving ? 'Enregistrement...' : 'Enregistrer'}</button>
                            </div>
                        </>
                    )}
                </div>

                <div className="card" style={{ padding: '1.5rem', maxWidth: 640, marginBottom: '1.5rem' }}>
                    <h3 style={{ margin: '0 0 0.5rem', fontSize: '1rem', fontWeight: 700 }}>Notifications</h3>
                    <p style={{ margin: '0 0 1.25rem', color: 'var(--text-muted)', fontSize: '0.85rem' }}>
                        Recevez automatiquement un rapport d'activité de toutes vos lignes chaque lundi matin.
                    </p>
                    <label style={{ display: 'flex', alignItems: 'center', gap: '0.6rem', padding: '0.5rem 0', cursor: 'pointer' }}>
                        <input type="checkbox" checked={!!form.weekly_report_enabled} onChange={(e) => setForm({ ...form, weekly_report_enabled: e.target.checked })} />
                        <span style={{ fontSize: '0.875rem' }}>Rapport hebdomadaire d'activité par email</span>
                    </label>
                    {form.weekly_report_enabled && (
                        <div className="form-group" style={{ marginTop: '0.75rem' }}>
                            <label className="form-label" style={{ fontSize: '0.75rem' }}>Email du rapport (laisser vide = email du compte)</label>
                            <input type="email" className="form-input" value={form.weekly_report_email} onChange={(e) => setForm({ ...form, weekly_report_email: e.target.value })} placeholder={user?.email || 'vous@exemple.ch'} />
                        </div>
                    )}
                    {savedAt && (
                        <p style={{ margin: '0.5rem 0 0', fontSize: '0.75rem', color: 'var(--text-muted)' }}>
                            Dernier rapport envoyé : {new Date(savedAt + 'Z').toLocaleString('fr-CH', { dateStyle: 'long', timeStyle: 'short' })}
                        </p>
                    )}
                    <h4 style={{ margin: '1.5rem 0 0.5rem', fontSize: '0.85rem', fontWeight: 700, color: 'var(--text)' }}>Seuils d'alerte solde</h4>
                    <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '0.75rem' }}>
                        <div className="form-group">
                            <label className="form-label" style={{ fontSize: '0.75rem' }}>Solde faible (CHF)</label>
                            <input type="number" step="0.5" className="form-input" value={form.low_balance_threshold} onChange={(e) => setForm({ ...form, low_balance_threshold: e.target.value })} placeholder="5.00" />
                        </div>
                        <div className="form-group">
                            <label className="form-label" style={{ fontSize: '0.75rem' }}>Solde critique (CHF)</label>
                            <input type="number" step="0.5" className="form-input" value={form.critical_balance_threshold} onChange={(e) => setForm({ ...form, critical_balance_threshold: e.target.value })} placeholder="1.00" />
                        </div>
                    </div>
                    <div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: '0.5rem' }}>
                        <button className="btn btn-primary" onClick={save} disabled={saving}>{saving ? 'Enregistrement...' : 'Enregistrer'}</button>
                    </div>
                </div>

                <div className="card" style={{ padding: '1.5rem', maxWidth: 640 }}>
                    <h3 style={{ margin: '0 0 0.5rem', fontSize: '1rem', fontWeight: 700 }}>Sécurité</h3>
                    <p style={{ margin: '0 0 1rem', color: 'var(--text-muted)', fontSize: '0.85rem' }}>
                        Vous êtes connecté avec un lien magique envoyé à votre email. Aucun mot de passe à gérer.
                    </p>
                    <button className="btn btn-danger" onClick={logout}>Se déconnecter</button>
                </div>
            </div>
        </>
    );
};

// ==================== CALLBACK REQUESTS VIEW ("Rappels") ====================
const CallbackRequestsView = ({ initialStatus = 'pending' }) => {
    const { notify } = useApp();
    const [requests, setRequests] = useState([]);
    const [pending, setPending] = useState(0);
    const [total, setTotal] = useState(0);
    const [loading, setLoading] = useState(true);
    const [statusFilter, setStatusFilter] = useState(initialStatus);

    // Sync quand l'utilisateur change de sous-menu (sidebar) sans démonter la vue.
    useEffect(() => { setStatusFilter(initialStatus); }, [initialStatus]);
    const [sourceFilter, setSourceFilter] = useState(''); // '' | 'ai' | 'human' | 'ai_appointment' | ...
    const [editingId, setEditingId] = useState(null);
    const [noteDraft, setNoteDraft] = useState('');
    const [expandedId, setExpandedId] = useState(null);
    const [detailId, setDetailId] = useState(null);

    const load = useCallback(async () => {
        setLoading(true);
        try {
            const params = new URLSearchParams();
            if (statusFilter) params.set('status', statusFilter);
            if (sourceFilter) params.set('source', sourceFilter);
            const qs = params.toString() ? `?${params}` : '';
            const r = await api.get(`/my/callback-requests${qs}`);
            if (r?.error) { notify.error(r.error); return; }
            setRequests(r.requests || []);
            setPending(r.pending || 0);
            setTotal(r.total || 0);
        } catch (e) { notify.error('Erreur chargement demandes'); }
        finally { setLoading(false); }
    }, [statusFilter, sourceFilter, notify]);

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

    const setStatus = async (id, status) => {
        try {
            const r = await api.patch(`/my/callback-requests/${id}`, { status });
            if (r?.error) { notify.error(r.error); return; }
            notify.success(status === 'done' ? 'Marqué comme traité' : status === 'cancelled' ? 'Annulé' : 'Remis en attente');
            load();
        } catch (e) { notify.error('Erreur'); }
    };

    const saveNotes = async (id) => {
        try {
            const r = await api.patch(`/my/callback-requests/${id}`, { notes: noteDraft });
            if (r?.error) { notify.error(r.error); return; }
            notify.success('Note enregistrée');
            setEditingId(null); setNoteDraft('');
            load();
        } catch (e) { notify.error('Erreur'); }
    };

    const remove = async (id) => {
        if (!confirm('Supprimer définitivement cette demande de rappel ?')) return;
        try {
            const r = await api.del(`/my/callback-requests/${id}`);
            if (r?.error) { notify.error(r.error); return; }
            notify.success('Supprimé'); load();
        } catch (e) { notify.error('Erreur'); }
    };

    const statusBadge = (s) => {
        if (s === 'pending') return <span className="badge badge-warning">À traiter</span>;
        if (s === 'done') return <span className="badge badge-success">Traité</span>;
        if (s === 'cancelled') return <span className="badge badge-gray">Annulé</span>;
        return <span className="badge badge-gray">{s}</span>;
    };

    const TYPE_META = {
        ai_appointment:           { label: 'RDV',           Icon: Icons.Calendar,      cls: 'badge-info' },
        ai_reservation_restaurant:{ label: 'Resto',         Icon: Icons.Chat,          cls: 'badge-info' },
        ai_reservation_taxi:      { label: 'Taxi',          Icon: Icons.Car || Icons.Phone, cls: 'badge-info' },
        ai_message:               { label: 'Message IA',    Icon: Icons.MessageSquare, cls: 'badge-info' },
        ai_whatsapp:              { label: 'WhatsApp IA',   Icon: Icons.Chat,          cls: 'badge-success' },
        absence_menu:             { label: 'Rappel (menu)', Icon: Icons.Phone,         cls: 'badge-gray' },
        manual:                   { label: 'Manuel',        Icon: Icons.Edit,          cls: 'badge-gray' },
    };
    const typeBadge = (src) => {
        const meta = TYPE_META[src] || (src && src.startsWith('ai_')
            ? { label: src.replace(/^ai_/, ''), Icon: Icons.Robot, cls: 'badge-info' }
            : { label: src || 'Rappel', Icon: Icons.Phone, cls: 'badge-gray' });
        const I = meta.Icon;
        return (
            <span className={`badge ${meta.cls}`} title={src || ''} style={{ display: 'inline-flex', alignItems: 'center', gap: '0.3rem' }}>
                {I && <I className="w-3 h-3" />} {meta.label}
            </span>
        );
    };
    const parsePayload = (raw) => {
        if (!raw) return null;
        if (typeof raw === 'object') return raw;
        try { return JSON.parse(raw); } catch { return null; }
    };

    return (
        <>
            <div className="page-header">
                <div>
                    <h1 className="page-title" style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
                        Demandes
                        {pending > 0 && <span className="badge badge-warning">{pending} en attente</span>}
                    </h1>
                    <p className="page-subtitle">Rappels, RDV, réservations et messages captés par vos lignes (humain + IA)</p>
                </div>
            </div>

            <div className="page-content">
                {loading && <div style={{ padding: '2rem', textAlign: 'center', color: 'var(--text-muted)' }}>Chargement…</div>}

                {!loading && requests.length === 0 && (
                    <div className="card" style={{ padding: '3rem 2rem', textAlign: 'center', color: 'var(--text-muted)' }}>
                        <Icons.Refresh className="w-12 h-12" style={{ margin: '0 auto 1rem', opacity: 0.4 }} />
                        <h3 style={{ margin: '0 0 0.5rem', fontSize: '1rem' }}>Aucune demande de rappel</h3>
                        <p style={{ margin: 0, fontSize: '0.85rem' }}>
                            {statusFilter === 'pending' ? 'Aucun rappel en attente.' : 'Aucune demande dans ce statut.'}
                        </p>
                    </div>
                )}

                {!loading && requests.length > 0 && (
                    <div className="card">
                        <div className="table-container">
                            <table className="data-table">
                                <thead>
                                    <tr>
                                        <th>Statut</th>
                                        <th>Type</th>
                                        <th>Reçu le</th>
                                        <th>Appelant</th>
                                        <th>Ligne</th>
                                        <th>Détails / Notes</th>
                                        <th style={{ textAlign: 'right' }}>Actions</th>
                                    </tr>
                                </thead>
                                <tbody>
                                    {requests.map(r => {
                                        const payload = parsePayload(r.capture_payload) || parsePayload(r.captured_json);
                                        const isAi = (r.source || '').startsWith('ai_');
                                        const isOpen = expandedId === r.id;
                                        return (
                                        <React.Fragment key={r.id}>
                                        <tr style={{ cursor: 'pointer' }} onClick={(e) => {
                                            // Évite d'ouvrir le détail quand on clique sur un bouton ou un lien dans la ligne.
                                            if (e.target.closest('button, a, textarea, input')) return;
                                            setDetailId(r.id);
                                        }}>
                                            <td>{statusBadge(r.status)}</td>
                                            <td>{typeBadge(r.source)}</td>
                                            <td style={{ fontSize: '0.85rem' }}>{formatDateTime(r.created_at)}</td>
                                            <td style={{ fontFamily: 'ui-monospace, monospace', fontSize: '0.85rem', fontWeight: 600 }}>
                                                {r.caller_number || '-'}
                                            </td>
                                            <td style={{ fontSize: '0.8rem' }}>
                                                <div style={{ fontWeight: 600 }}>{r.line_label || '-'}</div>
                                                <div style={{ fontFamily: 'ui-monospace, monospace', color: 'var(--text-muted)', fontSize: '0.75rem' }}>{r.line_phone}</div>
                                            </td>
                                            <td style={{ fontSize: '0.8rem', maxWidth: 320 }}>
                                                {isAi && r.notes && (
                                                    <div style={{ marginBottom: '0.25rem', color: 'var(--text)' }}>{r.notes}</div>
                                                )}
                                                {isAi && payload && (
                                                    <button className="btn btn-secondary btn-sm" style={{ padding: '0.15rem 0.5rem', fontSize: '0.7rem', display: 'inline-flex', alignItems: 'center', gap: '0.3rem' }}
                                                        onClick={() => setExpandedId(isOpen ? null : r.id)}>
                                                        <Icons.Eye className="w-3 h-3" /> {isOpen ? 'Masquer' : 'Voir détails'}
                                                    </button>
                                                )}
                                                {!isAi && (editingId === r.id ? (
                                                    <div style={{ display: 'flex', gap: '0.25rem', alignItems: 'flex-start' }}>
                                                        <textarea rows={2} value={noteDraft} onChange={e => setNoteDraft(e.target.value)}
                                                            style={{ width: '100%', fontSize: '0.8rem', padding: '0.4rem' }} />
                                                        <div style={{ display: 'flex', flexDirection: 'column', gap: '0.25rem' }}>
                                                            <button className="btn btn-primary btn-sm" onClick={() => saveNotes(r.id)} title="Enregistrer"><Icons.Check className="w-3 h-3" /></button>
                                                            <button className="btn btn-secondary btn-sm" onClick={() => { setEditingId(null); setNoteDraft(''); }} title="Annuler"><Icons.X className="w-3 h-3" /></button>
                                                        </div>
                                                    </div>
                                                ) : (
                                                    <div onClick={() => { setEditingId(r.id); setNoteDraft(r.notes || ''); }}
                                                        style={{ cursor: 'pointer', color: r.notes ? 'var(--text)' : 'var(--text-muted)', fontStyle: r.notes ? 'normal' : 'italic' }}>
                                                        {r.notes || 'Cliquer pour ajouter une note…'}
                                                    </div>
                                                ))}
                                            </td>
                                            <td style={{ textAlign: 'right' }}>
                                                <div style={{ display: 'inline-flex', gap: '0.3rem', flexWrap: 'wrap', justifyContent: 'flex-end' }}>
                                                    {r.caller_number && (
                                                        <a href={`tel:${r.caller_number}`} className="btn btn-primary btn-sm" title="Rappeler">
                                                            <Icons.Phone className="w-3 h-3" /> Rappeler
                                                        </a>
                                                    )}
                                                    {r.status === 'pending' && (
                                                        <button className="btn btn-secondary btn-sm" onClick={() => setStatus(r.id, 'done')} title="Marquer comme traité"
                                                            style={{ display: 'inline-flex', alignItems: 'center', gap: '0.3rem' }}>
                                                            <Icons.Check className="w-3 h-3" /> Traité
                                                        </button>
                                                    )}
                                                    {r.status !== 'pending' && (
                                                        <button className="btn btn-secondary btn-sm" onClick={() => setStatus(r.id, 'pending')} title="Remettre en attente">
                                                            <Icons.Refresh className="w-3 h-3" />
                                                        </button>
                                                    )}
                                                    {r.status === 'pending' && (
                                                        <button className="btn btn-secondary btn-sm" onClick={() => setStatus(r.id, 'cancelled')} title="Annuler">
                                                            <Icons.X className="w-3 h-3" />
                                                        </button>
                                                    )}
                                                    <button className="btn btn-danger btn-sm" onClick={() => remove(r.id)} title="Supprimer">
                                                        <Icons.Trash className="w-3 h-3" />
                                                    </button>
                                                </div>
                                            </td>
                                        </tr>
                                        {isOpen && payload && (
                                            <tr>
                                                <td colSpan={7} style={{ background: 'var(--bg-muted, #fafafa)', padding: '0.75rem 1rem' }}>
                                                    <div style={{ fontSize: '0.8rem', display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))', gap: '0.5rem 1rem' }}>
                                                        {Object.entries(payload).map(([k, v]) => (
                                                            <div key={k}>
                                                                <div style={{ fontSize: '0.7rem', color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.04em' }}>{k.replace(/_/g, ' ')}</div>
                                                                <div style={{ fontWeight: 500, wordBreak: 'break-word' }}>{typeof v === 'object' ? JSON.stringify(v) : String(v || '—')}</div>
                                                            </div>
                                                        ))}
                                                    </div>
                                                    {r.call_sid && (
                                                        <div style={{ marginTop: '0.5rem', fontSize: '0.7rem', color: 'var(--text-muted)', fontFamily: 'ui-monospace, monospace' }}>
                                                            call_sid : {r.call_sid}
                                                        </div>
                                                    )}
                                                </td>
                                            </tr>
                                        )}
                                        </React.Fragment>
                                        );
                                    })}
                                </tbody>
                            </table>
                        </div>
                        <div style={{ padding: '0.75rem 1rem', borderTop: '1px solid var(--border)', fontSize: '0.8rem', color: 'var(--text-muted)' }}>
                            {total} demande{total > 1 ? 's' : ''}
                        </div>
                    </div>
                )}
            </div>

            {detailId != null && (
                <CallbackRequestDetailModal
                    requestId={detailId}
                    onClose={() => setDetailId(null)}
                    onChanged={() => load()}
                />
            )}
        </>
    );
};

const CallbackRequestDetailModal = ({ requestId, onClose, onChanged }) => {
    const { notify } = useApp();
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);
    const [acting, setActing] = useState(false);

    // WhatsApp reply state
    const [tplInfo, setTplInfo] = useState(null); // { templates, window_open, last_inbound_at }
    const [replyText, setReplyText] = useState('');
    const [sending, setSending] = useState(false);
    const [selectedTpl, setSelectedTpl] = useState(null);
    const [tplVars, setTplVars] = useState({}); // { "1": "...", "2": "..." }

    const reload = useCallback(async () => {
        try {
            const r = await api.get(`/my/callback-requests/${requestId}`);
            if (r?.error) { notify.error(r.error); return; }
            setData(r);
        } catch (e) { notify.error('Erreur chargement demande'); }
    }, [requestId, notify]);

    useEffect(() => {
        let cancelled = false;
        (async () => {
            setLoading(true);
            try {
                const r = await api.get(`/my/callback-requests/${requestId}`);
                if (cancelled) return;
                if (r?.error) { notify.error(r.error); onClose(); return; }
                setData(r);
                // Charge l'état template/24h si conv WhatsApp.
                if (r.request?.wa_conversation_id) {
                    try {
                        const t = await api.get(`/my/whatsapp/conversations/${r.request.wa_conversation_id}/templates`);
                        if (!cancelled) setTplInfo(t);
                    } catch {}
                }
            } catch (e) { if (!cancelled) notify.error('Erreur chargement demande'); }
            finally { if (!cancelled) setLoading(false); }
        })();
        return () => { cancelled = true; };
    }, [requestId]); // eslint-disable-line

    const sendReply = async () => {
        if (!data?.request?.wa_conversation_id) return;
        if (!replyText.trim()) return;
        setSending(true);
        try {
            const r = await api.post(`/my/whatsapp/conversations/${data.request.wa_conversation_id}/reply`, { body: replyText.trim() });
            if (r?.error) { notify.error(r.error + (r.detail?.message ? ' - ' + r.detail.message : '')); return; }
            notify.success('Message envoyé');
            setReplyText('');
            await reload();
        } catch (e) { notify.error('Erreur envoi'); }
        finally { setSending(false); }
    };

    const sendTemplate = async () => {
        if (!data?.request?.wa_conversation_id || !selectedTpl) return;
        setSending(true);
        try {
            const r = await api.post(`/my/whatsapp/conversations/${data.request.wa_conversation_id}/reply`, {
                template_id: selectedTpl.id, variables: tplVars,
            });
            if (r?.error) { notify.error(r.error + (r.detail?.message ? ' - ' + r.detail.message : '')); return; }
            notify.success('Template envoyé');
            setSelectedTpl(null); setTplVars({});
            await reload();
        } catch (e) { notify.error('Erreur envoi template'); }
        finally { setSending(false); }
    };

    // Pré-remplit les variables d'un template avec date de la demande, nom contact, etc.
    const pickTemplate = (tpl) => {
        const req = data?.request;
        const captured = parseCaptured(req);
        const requestDate = req?.created_at ? new Date(req.created_at.replace(' ', 'T') + 'Z').toLocaleDateString('fr-CH') : '';
        const name = captured?.nom_complet || captured?.nom || captured?.name || '';

        // Détecte les placeholders {{1}} {{2}} ...
        const body = tpl.body || '';
        const matches = [...body.matchAll(/\{\{(\d+)\}\}/g)].map(m => m[1]);
        const unique = [...new Set(matches)];
        const guesses = [name || req?.caller_number || '', requestDate, captured?.email || '', captured?.telephone || ''];
        const initial = {};
        unique.forEach((k, i) => { initial[k] = guesses[i] || ''; });
        setSelectedTpl(tpl);
        setTplVars(initial);
    };

    const parseCaptured = (req) => {
        const raw = req?.captured_json || req?.capture_payload;
        if (!raw) return null;
        if (typeof raw === 'object') return raw;
        try { return JSON.parse(raw); } catch { return null; }
    };

    const setStatus = async (status) => {
        setActing(true);
        try {
            const r = await api.patch(`/my/callback-requests/${requestId}`, { status });
            if (r?.error) { notify.error(r.error); return; }
            notify.success('Statut mis à jour'); onChanged?.();
            setData(d => d ? { ...d, request: { ...d.request, status } } : d);
        } catch (e) { notify.error('Erreur'); } finally { setActing(false); }
    };

    const req = data?.request;
    const captured = parseCaptured(req);
    const summary = data?.ai_summary?.summary;
    const messages = data?.wa_messages || [];
    const isWaConv = !!req?.wa_conversation_id;
    const windowOpen = tplInfo?.window_open;
    const lastInboundAt = tplInfo?.last_inbound_at;

    return (
        <div className="modal-overlay" onClick={onClose}>
            <div className="modal-content" style={{ maxWidth: 880, width: '95%' }} onClick={e => e.stopPropagation()}>
                <div className="modal-header">
                    <div style={{ minWidth: 0 }}>
                        <div className="modal-title" style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', flexWrap: 'wrap' }}>
                            <span>Demande #{requestId}</span>
                            {req?.public_id && (
                                <>
                                    <span style={{ color: 'var(--text-muted)', fontWeight: 400 }}>/</span>
                                    <code
                                        title="Cliquer pour copier"
                                        onClick={() => { navigator.clipboard?.writeText(req.public_id); notify.success('ID copié'); }}
                                        style={{ fontSize: '0.78rem', fontFamily: 'ui-monospace, monospace', background: 'var(--bg-page)', padding: '0.15rem 0.45rem', borderRadius: 4, color: 'var(--text-muted)', cursor: 'pointer', fontWeight: 500 }}>
                                        {req.public_id}
                                    </code>
                                </>
                            )}
                        </div>
                        {req && (
                            <div style={{ fontSize: '0.8rem', color: 'var(--text-muted)', marginTop: '0.15rem' }}>
                                {formatDateTime(req.created_at)} · {req.line_label || req.line_phone} · {req.caller_number || '-'}
                            </div>
                        )}
                    </div>
                    <button className="modal-close" onClick={onClose} title="Fermer"><Icons.X className="w-5 h-5" /></button>
                </div>
                <div className="modal-body" style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
                    {loading && <div style={{ padding: '2rem', textAlign: 'center', color: 'var(--text-muted)' }}>Chargement…</div>}

                    {!loading && req && (
                        <>
                            {/* Résumé IA */}
                            <div className="ai-card" style={{ marginBottom: 0, borderLeft: '4px solid var(--primary)' }}>
                                <h3 style={{ marginTop: 0 }}>
                                    <Icons.Sparkles className="w-4 h-4" /> Résumé IA
                                </h3>
                                {summary ? (
                                    <div style={{ whiteSpace: 'pre-wrap', fontSize: '0.9rem', lineHeight: 1.55 }}
                                        dangerouslySetInnerHTML={{ __html: summary
                                            .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
                                            .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>') }} />
                                ) : (
                                    <p className="muted" style={{ margin: 0, fontSize: '0.85rem' }}>
                                        {data?.ai_summary?.error ? `Erreur : ${data.ai_summary.error}` : 'Aucun résumé disponible.'}
                                    </p>
                                )}
                            </div>

                            {/* Champs collectés */}
                            {captured && Object.keys(captured).length > 0 && (
                                <div className="ai-card" style={{ marginBottom: 0 }}>
                                    <h3 style={{ marginTop: 0 }}><Icons.Document className="w-4 h-4" /> Champs collectés</h3>
                                    <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))', gap: '0.6rem 1rem', fontSize: '0.85rem' }}>
                                        {Object.entries(captured)
                                            .filter(([k]) => !k.startsWith('wa_done_email_sent'))
                                            .map(([k, v]) => (
                                            <div key={k}>
                                                <div style={{ fontSize: '0.7rem', color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.04em' }}>{k.replace(/_/g, ' ')}</div>
                                                <div style={{ fontWeight: 500, wordBreak: 'break-word' }}>
                                                    {Array.isArray(v) ? v.join(', ') : (typeof v === 'object' ? JSON.stringify(v) : String(v ?? '-'))}
                                                </div>
                                            </div>
                                        ))}
                                    </div>
                                </div>
                            )}

                            {/* Conversation WhatsApp */}
                            {messages.length > 0 && (
                                <div className="ai-card" style={{ marginBottom: 0 }}>
                                    <h3 style={{ marginTop: 0 }}><Icons.Chat className="w-4 h-4" /> Conversation WhatsApp ({messages.length} messages)</h3>
                                    <div style={{ maxHeight: 360, overflowY: 'auto', background: '#f4f5fb', padding: '0.75rem', borderRadius: 8, display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
                                        {messages.map(m => {
                                            const isIn = m.direction === 'inbound';
                                            const isBot = m.sent_by === 'bot';
                                            return (
                                                <div key={m.id} style={{
                                                    alignSelf: isIn ? 'flex-start' : 'flex-end',
                                                    maxWidth: '78%',
                                                    background: isIn ? 'white' : (isBot ? '#dcfce7' : '#dbeafe'),
                                                    color: '#1e1b4b',
                                                    padding: '0.5rem 0.75rem',
                                                    borderRadius: 12,
                                                    fontSize: '0.85rem',
                                                    border: '1px solid var(--border)',
                                                    boxShadow: 'var(--shadow-sm)',
                                                }}>
                                                    <div style={{ fontSize: '0.65rem', color: 'var(--text-muted)', marginBottom: '0.15rem', fontWeight: 600 }}>
                                                        {isIn ? 'Client' : (isBot ? 'Bot IA' : 'Agent')} · {formatDateTime(m.created_at)}
                                                    </div>
                                                    <div style={{ whiteSpace: 'pre-wrap' }}>{m.body || '(média)'}</div>
                                                </div>
                                            );
                                        })}
                                    </div>
                                </div>
                            )}

                            {/* Continuer la conversation WhatsApp */}
                            {isWaConv && (
                                <div className="ai-card" style={{ marginBottom: 0 }}>
                                    <h3 style={{ marginTop: 0 }}>
                                        <Icons.Send className="w-4 h-4" /> Continuer la conversation
                                    </h3>
                                    {windowOpen ? (
                                        <>
                                            <p className="ai-card-hint" style={{ display: 'flex', alignItems: 'center', gap: '0.4rem' }}>
                                                <span className="badge badge-success">Fenêtre 24h ouverte</span>
                                                Vous pouvez envoyer un message libre.
                                            </p>
                                            <textarea
                                                className="input" rows={3}
                                                placeholder="Votre message…"
                                                value={replyText}
                                                onChange={e => setReplyText(e.target.value)}
                                            />
                                            <div style={{ marginTop: '0.5rem', display: 'flex', gap: '0.5rem', justifyContent: 'flex-end' }}>
                                                <button className="btn btn-primary" onClick={sendReply} disabled={sending || !replyText.trim()}
                                                    style={{ display: 'inline-flex', alignItems: 'center', gap: '0.35rem' }}>
                                                    <Icons.Send className="w-4 h-4" /> {sending ? 'Envoi…' : 'Envoyer'}
                                                </button>
                                            </div>
                                        </>
                                    ) : (
                                        <>
                                            <p className="ai-card-hint" style={{ display: 'flex', alignItems: 'center', gap: '0.4rem', flexWrap: 'wrap' }}>
                                                <span className="badge badge-warning">Fenêtre 24h fermée</span>
                                                {lastInboundAt
                                                    ? `Dernier message du client : ${formatDateTime(lastInboundAt)}. Vous devez utiliser un template approuvé.`
                                                    : `Vous devez utiliser un template approuvé pour reprendre contact.`}
                                            </p>
                                            {selectedTpl ? (
                                                <div style={{ background: 'var(--bg-page)', padding: '0.85rem', borderRadius: 8 }}>
                                                    <div style={{ fontSize: '0.78rem', color: 'var(--text-muted)', marginBottom: '0.3rem' }}>
                                                        Template : <strong>{selectedTpl.friendly_name}</strong> · {selectedTpl.language}
                                                    </div>
                                                    <pre style={{ whiteSpace: 'pre-wrap', fontFamily: 'inherit', fontSize: '0.85rem', background: 'white', padding: '0.6rem', borderRadius: 6, border: '1px solid var(--border)', margin: '0 0 0.6rem' }}>
                                                        {selectedTpl.body || '(corps non disponible)'}
                                                    </pre>
                                                    {Object.keys(tplVars).length > 0 && (
                                                        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))', gap: '0.5rem', marginBottom: '0.6rem' }}>
                                                            {Object.keys(tplVars).sort().map(k => (
                                                                <label key={k}>
                                                                    <div style={{ fontSize: '0.72rem', color: 'var(--text-muted)', marginBottom: '0.2rem' }}>Variable {`{{${k}}}`}</div>
                                                                    <input className="input" value={tplVars[k]}
                                                                        onChange={e => setTplVars(v => ({ ...v, [k]: e.target.value }))}
                                                                        style={{ fontSize: '0.85rem', padding: '0.45rem 0.7rem' }} />
                                                                </label>
                                                            ))}
                                                        </div>
                                                    )}
                                                    <div style={{ display: 'flex', gap: '0.5rem', justifyContent: 'flex-end' }}>
                                                        <button className="btn btn-secondary btn-sm" onClick={() => { setSelectedTpl(null); setTplVars({}); }}>
                                                            Annuler
                                                        </button>
                                                        <button className="btn btn-primary btn-sm" onClick={sendTemplate} disabled={sending}
                                                            style={{ display: 'inline-flex', alignItems: 'center', gap: '0.35rem' }}>
                                                            <Icons.Send className="w-3 h-3" /> {sending ? 'Envoi…' : 'Envoyer le template'}
                                                        </button>
                                                    </div>
                                                </div>
                                            ) : (
                                                <div style={{ display: 'flex', flexDirection: 'column', gap: '0.4rem' }}>
                                                    {(tplInfo?.templates || []).length === 0 ? (
                                                        <p className="muted" style={{ fontSize: '0.85rem', margin: 0 }}>
                                                            Aucun template approuvé disponible. <a href="#whatsapp-senders" style={{ color: 'var(--primary)' }}>Gérer mes templates</a>
                                                        </p>
                                                    ) : (
                                                        (tplInfo.templates || []).map(t => (
                                                            <button key={t.id} className="btn btn-secondary"
                                                                onClick={() => pickTemplate(t)}
                                                                style={{ justifyContent: 'flex-start', textAlign: 'left', padding: '0.6rem 0.85rem' }}>
                                                                <div style={{ display: 'flex', flexDirection: 'column', gap: '0.15rem', width: '100%' }}>
                                                                    <div style={{ fontWeight: 600, fontSize: '0.88rem' }}>{t.friendly_name} <span style={{ color: 'var(--text-muted)', fontWeight: 400, fontSize: '0.75rem' }}>· {t.language}</span></div>
                                                                    {t.body && <div style={{ fontSize: '0.78rem', color: 'var(--text-muted)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{t.body}</div>}
                                                                </div>
                                                            </button>
                                                        ))
                                                    )}
                                                </div>
                                            )}
                                        </>
                                    )}
                                </div>
                            )}

                            {/* Transcription d'appel */}
                            {req.transcription_text && (
                                <div className="ai-card" style={{ marginBottom: 0 }}>
                                    <h3 style={{ marginTop: 0 }}><Icons.Mic className="w-4 h-4" /> Transcription d'appel</h3>
                                    <div style={{ whiteSpace: 'pre-wrap', fontSize: '0.85rem', maxHeight: 240, overflowY: 'auto', background: 'var(--bg-page)', padding: '0.75rem', borderRadius: 8 }}>
                                        {req.transcription_text}
                                    </div>
                                    {req.recording_url && (
                                        <audio controls src={req.recording_url} style={{ width: '100%', marginTop: '0.5rem' }} />
                                    )}
                                </div>
                            )}

                            {/* Notes */}
                            {req.notes && (
                                <div className="ai-card" style={{ marginBottom: 0 }}>
                                    <h3 style={{ marginTop: 0 }}><Icons.Edit className="w-4 h-4" /> Notes</h3>
                                    <div style={{ fontSize: '0.88rem', whiteSpace: 'pre-wrap' }}>{req.notes}</div>
                                </div>
                            )}
                        </>
                    )}
                </div>

                {!loading && req && (
                    <div className="modal-footer" style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap', justifyContent: 'space-between' }}>
                        <div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap' }}>
                            {req.caller_number && (
                                <a href={`tel:${req.caller_number}`} className="btn btn-primary"
                                    style={{ display: 'inline-flex', alignItems: 'center', gap: '0.35rem' }}>
                                    <Icons.Phone className="w-4 h-4" /> Rappeler {req.caller_number}
                                </a>
                            )}
                        </div>
                        <div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap' }}>
                            {req.status === 'pending' && (
                                <>
                                    <button className="btn btn-secondary" onClick={() => setStatus('done')} disabled={acting}
                                        style={{ display: 'inline-flex', alignItems: 'center', gap: '0.35rem' }}>
                                        <Icons.Check className="w-4 h-4" /> Marquer comme traité
                                    </button>
                                    <button className="btn btn-secondary" onClick={() => setStatus('cancelled')} disabled={acting}
                                        style={{ display: 'inline-flex', alignItems: 'center', gap: '0.35rem' }}>
                                        <Icons.X className="w-4 h-4" /> Annuler
                                    </button>
                                </>
                            )}
                            {req.status !== 'pending' && (
                                <button className="btn btn-secondary" onClick={() => setStatus('pending')} disabled={acting}
                                    style={{ display: 'inline-flex', alignItems: 'center', gap: '0.35rem' }}>
                                    <Icons.Refresh className="w-4 h-4" /> Remettre en attente
                                </button>
                            )}
                            <button className="btn btn-secondary" disabled={acting}
                                onClick={async () => {
                                    setActing(true);
                                    try {
                                        const r = await api.post(`/my/callback-requests/${requestId}/resend-email`, {});
                                        if (r?.error) notify.error(r.error); else notify.success('Email renvoyé');
                                    } catch (e) { notify.error('Erreur envoi'); } finally { setActing(false); }
                                }}
                                style={{ display: 'inline-flex', alignItems: 'center', gap: '0.35rem' }}>
                                <Icons.Send className="w-4 h-4" /> Renvoyer l'email
                            </button>
                            <button className="btn btn-secondary" onClick={onClose}>Fermer</button>
                        </div>
                    </div>
                )}
            </div>
        </div>
    );
};

// ==================== CONTACTS (CRM) VIEW ====================
const ContactsView = () => {
    const { notify } = useApp();
    const [contacts, setContacts] = useState([]);
    const [duplicates, setDuplicates] = useState([]);
    const [tab, setTab] = useState('list');
    const [loading, setLoading] = useState(true);
    const [q, setQ] = useState('');
    const [filter, setFilter] = useState('');
    const [show, setShow] = useState(false);
    const [draft, setDraft] = useState({ phone_number: '', name: '', company: '', email: '', notes: '', is_vip: false, is_blacklisted: false });
    const [detail, setDetail] = useState(null);

    const load = useCallback(async () => {
        setLoading(true);
        try {
            const params = new URLSearchParams();
            if (q) params.set('q', q);
            if (filter) params.set('filter', filter);
            params.set('limit', '100');
            const r = await api.get(`/my/contacts?${params}`);
            setContacts(r?.contacts || []);
            const d = await api.get('/my/contacts/duplicates?days=14&min_calls=3');
            setDuplicates(d?.duplicates || []);
        } catch (e) { notify.error('Erreur chargement'); } finally { setLoading(false); }
    }, [q, filter, notify]);
    useEffect(() => { load(); }, [load]);

    const create = async () => {
        if (!draft.phone_number.trim()) return notify.error('Numéro requis');
        try { await api.post('/my/contacts', draft); notify.success('Contact ajouté'); setShow(false); setDraft({ phone_number: '', name: '', company: '', email: '', notes: '', is_vip: false, is_blacklisted: false }); load(); }
        catch (e) { notify.error('Erreur: ' + e.message); }
    };

    const openDetail = async (c) => {
        try { const r = await api.get(`/my/contacts/${c.id}`); setDetail(r); }
        catch (e) { notify.error('Erreur'); }
    };

    if (detail) {
        return (
            <>
                <div className="page-header">
                    <div>
                        <button className="btn btn-secondary btn-sm" onClick={() => setDetail(null)}><Icons.ArrowLeft className="w-4 h-4" /> Retour</button>
                        <h1 className="page-title" style={{ marginTop: '0.5rem' }}>{detail.contact.name || detail.contact.phone_number}</h1>
                        <p className="page-subtitle">{detail.contact.phone_number}{detail.contact.company ? ` · ${detail.contact.company}` : ''}</p>
                    </div>
                </div>
                <div className="page-content" style={{ display: 'grid', gridTemplateColumns: 'minmax(300px, 1fr) 2fr', gap: '1rem' }}>
                    <ContactEditCard contact={detail.contact} onSaved={load} onBack={() => setDetail(null)} />
                    <div className="card">
                        <div style={{ padding: '1rem 1.25rem', borderBottom: '1px solid var(--border)', fontWeight: 700 }}>Historique</div>
                        {(detail.calls || []).length === 0 ? (
                            <p style={{ padding: '1rem', color: 'var(--text-muted)' }}>Aucun appel.</p>
                        ) : (
                            <div style={{ maxHeight: '70vh', overflow: 'auto' }}>
                                {detail.calls.map(c => (
                                    <div key={c.id} style={{ padding: '0.75rem 1rem', borderBottom: '1px solid var(--border)', fontSize: '0.85rem' }}>
                                        <div style={{ display: 'flex', justifyContent: 'space-between' }}>
                                            <strong>{c.direction === 'inbound' ? '↓ Entrant' : '↑ Sortant'} · {c.status}</strong>
                                            <span style={{ color: 'var(--text-muted)' }}>{parseUtcDate(c.created_at)?.toLocaleString('fr-CH') || '-'}</span>
                                        </div>
                                        {c.ai_summary && <div style={{ marginTop: '0.4rem', color: '#475569', fontStyle: 'italic' }}>{c.ai_summary}</div>}
                                    </div>
                                ))}
                            </div>
                        )}
                    </div>
                </div>
            </>
        );
    }

    return (
        <>
            <div className="page-header">
                <div><h1 className="page-title">Contacts</h1><p className="page-subtitle">CRM minimaliste basé sur vos appels & SMS</p></div>
                <div className="page-actions">
                    <button className="btn btn-secondary btn-sm" onClick={load}><Icons.Refresh className="w-4 h-4" /> Actualiser</button>
                    <button className="btn btn-primary btn-sm" onClick={() => setShow(true)}><Icons.Plus className="w-4 h-4" /> Nouveau contact</button>
                </div>
            </div>
            <div className="page-content">
                <div style={{ display: 'flex', gap: '0.5rem', marginBottom: '1rem', borderBottom: '1px solid var(--border)' }}>
                    {[{ id: 'list', label: `Tous (${contacts.length})` }, { id: 'dup', label: `Appelants fréquents (${duplicates.length})` }].map(t => (
                        <button key={t.id} onClick={() => setTab(t.id)}
                            style={{ background: 'none', border: 'none', padding: '0.6rem 1rem', cursor: 'pointer', fontWeight: 500, color: tab === t.id ? 'var(--primary)' : 'var(--text-muted)', borderBottom: tab === t.id ? '2px solid var(--primary)' : '2px solid transparent', marginBottom: '-1px' }}>
                            {t.label}
                        </button>
                    ))}
                </div>
                {tab === 'list' && (
                    <>
                        <div style={{ display: 'flex', gap: '0.5rem', marginBottom: '1rem' }}>
                            <input className="form-input" placeholder="Rechercher (nom, téléphone, email…)" value={q} onChange={e => setQ(e.target.value)} style={{ flex: 1 }} />
                            <select className="form-input" value={filter} onChange={e => setFilter(e.target.value)} style={{ width: 180 }}>
                                <option value="">Tous</option>
                                <option value="vip">VIP uniquement</option>
                                <option value="blacklisted">Blacklistés</option>
                                <option value="recent">Récents (30j)</option>
                            </select>
                        </div>
                        {show && (
                            <div className="card" style={{ padding: '1rem', marginBottom: '1rem', border: '2px solid var(--primary-light)' }}>
                                <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '0.75rem' }}>
                                    <div className="form-row"><label>Numéro *</label><input className="form-input" value={draft.phone_number} onChange={e => setDraft({ ...draft, phone_number: e.target.value })} placeholder="+41 79 …" /></div>
                                    <div className="form-row"><label>Nom</label><input className="form-input" value={draft.name} onChange={e => setDraft({ ...draft, name: e.target.value })} /></div>
                                    <div className="form-row"><label>Société</label><input className="form-input" value={draft.company} onChange={e => setDraft({ ...draft, company: e.target.value })} /></div>
                                    <div className="form-row"><label>Email</label><input className="form-input" type="email" value={draft.email} onChange={e => setDraft({ ...draft, email: e.target.value })} /></div>
                                    <div className="form-row" style={{ gridColumn: '1 / -1' }}><label>Notes</label><textarea className="form-input" rows={2} value={draft.notes} onChange={e => setDraft({ ...draft, notes: e.target.value })} /></div>
                                </div>
                                <div style={{ display: 'flex', gap: '1rem', marginTop: '0.5rem' }}>
                                    <label className="toggle-row"><input type="checkbox" checked={draft.is_vip} onChange={e => setDraft({ ...draft, is_vip: e.target.checked })} /><span>VIP</span></label>
                                    <label className="toggle-row"><input type="checkbox" checked={draft.is_blacklisted} onChange={e => setDraft({ ...draft, is_blacklisted: e.target.checked })} /><span>Blacklisté</span></label>
                                </div>
                                <div style={{ display: 'flex', gap: '0.5rem', justifyContent: 'flex-end', marginTop: '0.75rem' }}>
                                    <button className="btn btn-secondary" onClick={() => setShow(false)}>Annuler</button>
                                    <button className="btn btn-primary" onClick={create}>Créer</button>
                                </div>
                            </div>
                        )}
                        {loading ? <p>Chargement…</p> : contacts.length === 0 ? <p style={{ color: 'var(--text-muted)' }}>Aucun contact pour l'instant.</p> : (
                            <div className="card">
                                <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '0.875rem' }}>
                                    <thead><tr style={{ background: '#f8fafc', textAlign: 'left' }}>
                                        <th style={{ padding: '0.75rem 1rem' }}>Nom</th><th>Téléphone</th><th>Société</th><th style={{ textAlign: 'right' }}>Appels</th><th>Dernier contact</th><th></th>
                                    </tr></thead>
                                    <tbody>
                                        {contacts.map(c => (
                                            <tr key={c.id} style={{ borderTop: '1px solid var(--border)', cursor: 'pointer' }} onClick={() => openDetail(c)}>
                                                <td style={{ padding: '0.6rem 1rem', fontWeight: 600 }}>
                                                    {c.is_vip && <span style={{ marginRight: 6, color: '#f59e0b' }} title="VIP">★</span>}
                                                    {c.is_blacklisted && <span style={{ marginRight: 6, color: '#dc2626' }} title="Blacklisté">⊘</span>}
                                                    {c.name || '-'}
                                                </td>
                                                <td style={{ fontFamily: 'ui-monospace, monospace', color: 'var(--text-muted)' }}>{c.phone_number}</td>
                                                <td>{c.company || '-'}</td>
                                                <td style={{ textAlign: 'right', fontWeight: 600 }}>{c.total_calls || 0}</td>
                                                <td style={{ color: 'var(--text-muted)', fontSize: '0.8rem' }}>{c.last_contact_at ? parseUtcDate(c.last_contact_at)?.toLocaleDateString('fr-CH') : '-'}</td>
                                                <td></td>
                                            </tr>
                                        ))}
                                    </tbody>
                                </table>
                            </div>
                        )}
                    </>
                )}
                {tab === 'dup' && (
                    <div className="card" style={{ padding: '1rem' }}>
                        <p style={{ color: 'var(--text-muted)', margin: '0 0 1rem' }}>Numéros qui ont rappelé au moins 3 fois en 14 jours.</p>
                        {duplicates.length === 0 ? <p>Aucun appelant fréquent.</p> : (
                            <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '0.875rem' }}>
                                <thead><tr style={{ textAlign: 'left', color: 'var(--text-muted)' }}><th>Numéro</th><th>Nom</th><th style={{ textAlign: 'right' }}>Appels</th><th>Dernier</th><th></th></tr></thead>
                                <tbody>
                                    {duplicates.map(d => (
                                        <tr key={d.phone_number} style={{ borderTop: '1px solid var(--border)' }}>
                                            <td style={{ padding: '0.5rem 0', fontFamily: 'ui-monospace, monospace' }}>{d.phone_number}</td>
                                            <td>{d.name || '-'}</td>
                                            <td style={{ textAlign: 'right' }}><strong>{d.calls_count}</strong></td>
                                            <td style={{ color: 'var(--text-muted)' }}>{parseUtcDate(d.last_call)?.toLocaleDateString('fr-CH') || '-'}</td>
                                            <td><button className="btn btn-secondary btn-sm" onClick={async () => {
                                                await api.post('/my/contacts', { phone_number: d.phone_number, name: d.name || '' });
                                                notify.success('Ajouté aux contacts'); load();
                                            }}>+ Contact</button></td>
                                        </tr>
                                    ))}
                                </tbody>
                            </table>
                        )}
                    </div>
                )}
            </div>
        </>
    );
};

const ContactEditCard = ({ contact, onSaved, onBack }) => {
    const { notify } = useApp();
    const [form, setForm] = useState({ ...contact });
    useEffect(() => setForm({ ...contact }), [contact.id]);
    const save = async () => {
        try {
            await api.put(`/my/contacts/${contact.id}`, {
                name: form.name, company: form.company, email: form.email, notes: form.notes,
                is_vip: !!form.is_vip, is_blacklisted: !!form.is_blacklisted,
            });
            notify.success('Enregistré'); onSaved && onSaved();
        } catch (e) { notify.error('Erreur'); }
    };
    const remove = async () => {
        if (!confirm('Supprimer ce contact ?')) return;
        try { await api.del(`/my/contacts/${contact.id}`); notify.success('Supprimé'); onBack && onBack(); onSaved && onSaved(); }
        catch (e) { notify.error('Erreur'); }
    };
    return (
        <div className="card" style={{ padding: '1rem' }}>
            <h3 style={{ margin: '0 0 0.75rem' }}>Fiche contact</h3>
            <div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
                <div className="form-row"><label>Nom</label><input className="form-input" value={form.name || ''} onChange={e => setForm({ ...form, name: e.target.value })} /></div>
                <div className="form-row"><label>Société</label><input className="form-input" value={form.company || ''} onChange={e => setForm({ ...form, company: e.target.value })} /></div>
                <div className="form-row"><label>Email</label><input className="form-input" type="email" value={form.email || ''} onChange={e => setForm({ ...form, email: e.target.value })} /></div>
                <div className="form-row"><label>Notes</label><textarea className="form-input" rows={3} value={form.notes || ''} onChange={e => setForm({ ...form, notes: e.target.value })} /></div>
                <div style={{ display: 'flex', gap: '1rem' }}>
                    <label className="toggle-row"><input type="checkbox" checked={!!form.is_vip} onChange={e => setForm({ ...form, is_vip: e.target.checked })} /><span>VIP</span></label>
                    <label className="toggle-row"><input type="checkbox" checked={!!form.is_blacklisted} onChange={e => setForm({ ...form, is_blacklisted: e.target.checked })} /><span>Bloqué</span></label>
                </div>
                <div style={{ display: 'flex', gap: '0.5rem', marginTop: '0.5rem', flexWrap: 'wrap' }}>
                    <button className="btn btn-primary" onClick={save}>Enregistrer</button>
                    <button className="btn btn-secondary" onClick={remove}>Supprimer</button>
                    <PhoneHistoryButton phone={contact.phone_number} />
                </div>
            </div>
        </div>
    );
};

// ==================== PROXY NUMBERS VIEW ====================
const ProxyNumbersView = () => {
    const { notify } = useApp();
    const [items, setItems] = useState([]);
    const [lines, setLines] = useState([]);
    const [loading, setLoading] = useState(true);
    const [show, setShow] = useState(false);
    const [draft, setDraft] = useState({ line_id: '', target_number: '', label: '', expires_at: '', max_calls: '' });
    const load = async () => {
        setLoading(true);
        try {
            const [a, b] = await Promise.all([api.get('/my/proxy-numbers'), api.get('/my/lines')]);
            setItems(a?.proxy_numbers || []); setLines(b?.lines || []);
        } catch (e) { notify.error('Erreur'); } finally { setLoading(false); }
    };
    useEffect(() => { load(); }, []);
    const create = async () => {
        if (!draft.line_id || !draft.target_number) return notify.error('Ligne + cible requis');
        try {
            const r = await api.post('/my/proxy-numbers', {
                line_id: parseInt(draft.line_id),
                target_number: draft.target_number,
                label: draft.label || null,
                expires_at: draft.expires_at || null,
                max_calls: draft.max_calls ? parseInt(draft.max_calls) : null,
            });
            if (r?.error) return notify.error(r.error);
            notify.success('Créé'); setShow(false); setDraft({ line_id: '', target_number: '', label: '', expires_at: '', max_calls: '' }); load();
        } catch (e) { notify.error('Erreur: ' + e.message); }
    };
    const remove = async (p) => {
        if (!confirm(`Supprimer le numéro temporaire "${p.label || p.target_number}" ?`)) return;
        try { await api.del(`/my/proxy-numbers/${p.id}`); notify.success('Supprimé'); load(); }
        catch (e) { notify.error('Erreur'); }
    };
    const toggle = async (p) => {
        try { await api.put(`/my/proxy-numbers/${p.id}`, { is_active: !p.is_active ? 1 : 0 }); load(); }
        catch (e) { notify.error('Erreur'); }
    };
    return (
        <>
            <div className="page-header">
                <div><h1 className="page-title">Numéros temporaires</h1><p className="page-subtitle">Masquez votre vrai numéro derrière une de vos lignes Vocal (Anibis, Tutti, livraisons…)</p></div>
                <div className="page-actions">
                    <button className="btn btn-secondary btn-sm" onClick={load}><Icons.Refresh className="w-4 h-4" /> Actualiser</button>
                    <button className="btn btn-primary btn-sm" onClick={() => setShow(true)}><Icons.Plus className="w-4 h-4" /> Nouveau</button>
                </div>
            </div>
            <div className="page-content">
                {show && (
                    <div className="card" style={{ padding: '1rem', marginBottom: '1rem', border: '2px solid var(--primary-light)' }}>
                        <h3 style={{ margin: '0 0 0.75rem' }}>Nouveau numéro temporaire</h3>
                        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '0.75rem' }}>
                            <div className="form-row">
                                <label>Ligne Vocal qui sera affichée</label>
                                <select className="form-input" value={draft.line_id} onChange={e => setDraft({ ...draft, line_id: e.target.value })}>
                                    <option value="">- Choisir -</option>
                                    {lines.map(l => <option key={l.id} value={l.id}>{l.phone_number} {l.label ? `(${l.label})` : ''}</option>)}
                                </select>
                            </div>
                            <div className="form-row"><label>Vers (votre vrai numéro)</label><input className="form-input" value={draft.target_number} onChange={e => setDraft({ ...draft, target_number: e.target.value })} placeholder="+41 79 …" /></div>
                            <div className="form-row"><label>Étiquette</label><input className="form-input" value={draft.label} onChange={e => setDraft({ ...draft, label: e.target.value })} placeholder="Annonce Anibis Vespa 2018" /></div>
                            <div className="form-row"><label>Expire le (optionnel)</label><input type="datetime-local" className="form-input" value={draft.expires_at} onChange={e => setDraft({ ...draft, expires_at: e.target.value })} /></div>
                            <div className="form-row"><label>Limite d'appels (optionnel)</label><input type="number" className="form-input" value={draft.max_calls} onChange={e => setDraft({ ...draft, max_calls: e.target.value })} /></div>
                        </div>
                        <div style={{ display: 'flex', gap: '0.5rem', justifyContent: 'flex-end', marginTop: '0.75rem' }}>
                            <button className="btn btn-secondary" onClick={() => setShow(false)}>Annuler</button>
                            <button className="btn btn-primary" onClick={create}>Créer</button>
                        </div>
                    </div>
                )}
                {loading ? <p>Chargement…</p> : items.length === 0 ? <p style={{ color: 'var(--text-muted)' }}>Aucun numéro temporaire.</p> : (
                    <div className="card">
                        <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '0.875rem' }}>
                            <thead><tr style={{ background: '#f8fafc', textAlign: 'left' }}>
                                <th style={{ padding: '0.75rem 1rem' }}>Étiquette</th><th>Affichage</th><th>Vers</th><th>Appels</th><th>Expire</th><th>Actif</th><th></th>
                            </tr></thead>
                            <tbody>
                                {items.map(p => (
                                    <tr key={p.id} style={{ borderTop: '1px solid var(--border)' }}>
                                        <td style={{ padding: '0.6rem 1rem' }}>{p.label || '-'}</td>
                                        <td style={{ fontFamily: 'ui-monospace, monospace' }}>{p.proxy_phone_number}</td>
                                        <td style={{ fontFamily: 'ui-monospace, monospace', color: 'var(--text-muted)' }}>{p.target_number}</td>
                                        <td>{p.used_calls || 0}{p.max_calls ? ` / ${p.max_calls}` : ''}</td>
                                        <td style={{ color: 'var(--text-muted)' }}>{p.expires_at ? new Date(p.expires_at).toLocaleDateString('fr-CH') : '-'}</td>
                                        <td><input type="checkbox" checked={!!p.is_active} onChange={() => toggle(p)} /></td>
                                        <td><button className="btn btn-secondary btn-sm" onClick={() => remove(p)}><Icons.Trash className="w-4 h-4" /></button></td>
                                    </tr>
                                ))}
                            </tbody>
                        </table>
                    </div>
                )}
            </div>
        </>
    );
};

// ==================== SPAM / BLACKLIST VIEW ====================
const SpamView = () => {
    const { notify } = useApp();
    const [reports, setReports] = useState([]);
    const [draft, setDraft] = useState({ phone_number: '', reason: 'robocall' });
    const [loading, setLoading] = useState(true);
    const load = async () => {
        setLoading(true);
        try { const r = await api.get('/my/spam-reports'); setReports(r?.reports || []); }
        catch (e) { notify.error('Erreur'); } finally { setLoading(false); }
    };
    useEffect(() => { load(); }, []);
    const report = async () => {
        if (!draft.phone_number.trim()) return notify.error('Numéro requis');
        try {
            const r = await api.post('/my/spam-reports', draft);
            if (r?.error) return notify.error(r.error);
            notify.success(`Signalé. Blacklisté chez ${r.distinct_clients || 1} client(s) Vocal.`);
            setDraft({ phone_number: '', reason: 'robocall' });
            load();
        } catch (e) { notify.error('Erreur: ' + e.message); }
    };
    return (
        <>
            <div className="page-header">
                <div><h1 className="page-title">Spam & blacklist</h1><p className="page-subtitle">Signalez en 1 clic les démarcheurs et robocalls. Les numéros signalés par 3+ clients sont automatiquement bloqués pour tous.</p></div>
            </div>
            <div className="page-content" style={{ display: 'grid', gridTemplateColumns: 'minmax(280px, 1fr) 2fr', gap: '1rem' }}>
                <div className="card" style={{ padding: '1rem' }}>
                    <h3 style={{ margin: '0 0 0.75rem' }}>Signaler un numéro</h3>
                    <div className="form-row"><label>Numéro</label><input className="form-input" value={draft.phone_number} onChange={e => setDraft({ ...draft, phone_number: e.target.value })} placeholder="+41 … ou 0…" /></div>
                    <div className="form-row"><label>Motif</label>
                        <select className="form-input" value={draft.reason} onChange={e => setDraft({ ...draft, reason: e.target.value })}>
                            <option value="robocall">Robocall</option>
                            <option value="phishing">Hameçonnage / arnaque</option>
                            <option value="commercial">Démarchage commercial</option>
                            <option value="other">Autre</option>
                        </select>
                    </div>
                    <button className="btn btn-primary" onClick={report} style={{ width: '100%', marginTop: '0.5rem' }}>
                        <Icons.AlertCircle className="w-4 h-4" /> Signaler & bloquer
                    </button>
                </div>
                <div className="card">
                    <div style={{ padding: '1rem 1.25rem', borderBottom: '1px solid var(--border)', fontWeight: 700 }}>Mes signalements</div>
                    {loading ? <p style={{ padding: '1rem' }}>Chargement…</p> : reports.length === 0 ? <p style={{ padding: '1rem', color: 'var(--text-muted)' }}>Aucun signalement.</p> : (
                        <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '0.875rem' }}>
                            <thead><tr style={{ textAlign: 'left', color: 'var(--text-muted)' }}><th style={{ padding: '0.5rem 1rem' }}>Numéro</th><th>Motif</th><th>Date</th></tr></thead>
                            <tbody>
                                {reports.map(r => (
                                    <tr key={r.id} style={{ borderTop: '1px solid var(--border)' }}>
                                        <td style={{ padding: '0.5rem 1rem', fontFamily: 'ui-monospace, monospace' }}>{r.phone_number}</td>
                                        <td>{r.reason}</td>
                                        <td style={{ color: 'var(--text-muted)' }}>{parseUtcDate(r.created_at)?.toLocaleString('fr-CH') || '-'}</td>
                                    </tr>
                                ))}
                            </tbody>
                        </table>
                    )}
                </div>
            </div>
        </>
    );
};

// ==================== PORTING VIEW ====================
const PortingView = () => {
    const { notify } = useApp();
    const [reqs, setReqs] = useState([]);
    const [show, setShow] = useState(false);
    const [draft, setDraft] = useState({ phone_number: '', current_carrier: '', account_number: '', owner_name: '', owner_address: '', owner_email: '', desired_date: '', notes: '', documents_url: '' });
    const [loading, setLoading] = useState(true);
    const load = async () => {
        setLoading(true);
        try { const r = await api.get('/my/port-requests'); setReqs(r?.port_requests || []); }
        catch (e) { notify.error('Erreur'); } finally { setLoading(false); }
    };
    useEffect(() => { load(); }, []);
    const create = async () => {
        if (!draft.phone_number || !draft.owner_name) return notify.error('Numéro et titulaire requis');
        try {
            await api.post('/my/port-requests', draft);
            notify.success('Demande envoyée. Notre équipe vous contactera sous 48h.');
            setShow(false);
            setDraft({ phone_number: '', current_carrier: '', account_number: '', owner_name: '', owner_address: '', owner_email: '', desired_date: '', notes: '', documents_url: '' });
            load();
        } catch (e) { notify.error('Erreur: ' + e.message); }
    };
    const cancel = async (r) => {
        if (!confirm(`Annuler la demande pour ${r.phone_number} ?`)) return;
        try { await api.del(`/my/port-requests/${r.id}`); notify.success('Annulée'); load(); } catch (e) { notify.error('Erreur'); }
    };
    const statusBadge = (s) => {
        const map = { requested: ['#fef3c7', '#854d0e', 'En attente'], in_review: ['#dbeafe', '#1e40af', 'En revue'], submitted: ['#e0e7ff', '#4338ca', 'Envoyée à l\'opérateur'], completed: ['#dcfce7', '#15803d', 'Portée'], rejected: ['#fee2e2', '#b91c1c', 'Refusée'], cancelled: ['#f1f5f9', '#475569', 'Annulée'] };
        const [bg, fg, label] = map[s] || ['#f1f5f9', '#475569', s];
        return <span style={{ background: bg, color: fg, padding: '0.2rem 0.6rem', borderRadius: 999, fontSize: '0.75rem', fontWeight: 600 }}>{label}</span>;
    };
    return (
        <>
            <div className="page-header">
                <div><h1 className="page-title">Portabilité</h1><p className="page-subtitle">Transférez votre numéro existant (Swisscom, Sunrise, Salt…) vers Vocal sans perdre vos appels.</p></div>
                <div className="page-actions">
                    <button className="btn btn-secondary btn-sm" onClick={load}><Icons.Refresh className="w-4 h-4" /> Actualiser</button>
                    <button className="btn btn-primary btn-sm" onClick={() => setShow(true)}><Icons.Plus className="w-4 h-4" /> Nouvelle demande</button>
                </div>
            </div>
            <div className="page-content">
                <div className="card" style={{ padding: '1rem 1.25rem', marginBottom: '1rem', background: '#eef2ff', border: '1px solid #c7d2fe', fontSize: '0.875rem', color: '#3730a3' }}>
                    <strong>Comment ça marche :</strong> remplissez le formulaire avec les infos exactes du contrat actuel, joignez (URL) une copie d'identité + un mandat signé. Notre équipe traite la demande sous 48h ouvrées et vous tient informé. Le portage prend généralement 2 à 4 semaines selon l'opérateur de départ.
                </div>
                {show && (
                    <div className="card" style={{ padding: '1rem', marginBottom: '1rem', border: '2px solid var(--primary-light)' }}>
                        <h3 style={{ margin: '0 0 0.75rem' }}>Nouvelle demande de portabilité</h3>
                        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '0.75rem' }}>
                            <div className="form-row"><label>Numéro à porter *</label><input className="form-input" value={draft.phone_number} onChange={e => setDraft({ ...draft, phone_number: e.target.value })} placeholder="+41 22 …" /></div>
                            <div className="form-row"><label>Opérateur actuel</label>
                                <select className="form-input" value={draft.current_carrier} onChange={e => setDraft({ ...draft, current_carrier: e.target.value })}>
                                    <option value="">- Choisir -</option><option>Swisscom</option><option>Sunrise</option><option>Salt</option><option>Wingo</option><option>Yallo</option><option>Lebara</option><option>Coop Mobile</option><option>M-Budget</option><option>Autre</option>
                                </select>
                            </div>
                            <div className="form-row"><label>N° de contrat / compte chez l'opérateur</label><input className="form-input" value={draft.account_number} onChange={e => setDraft({ ...draft, account_number: e.target.value })} /></div>
                            <div className="form-row"><label>Titulaire (exact contrat) *</label><input className="form-input" value={draft.owner_name} onChange={e => setDraft({ ...draft, owner_name: e.target.value })} /></div>
                            <div className="form-row" style={{ gridColumn: '1 / -1' }}><label>Adresse facturation</label><input className="form-input" value={draft.owner_address} onChange={e => setDraft({ ...draft, owner_address: e.target.value })} /></div>
                            <div className="form-row"><label>Email de contact</label><input className="form-input" type="email" value={draft.owner_email} onChange={e => setDraft({ ...draft, owner_email: e.target.value })} /></div>
                            <div className="form-row"><label>Date de portage souhaitée</label><input className="form-input" type="date" value={draft.desired_date} onChange={e => setDraft({ ...draft, desired_date: e.target.value })} /></div>
                            <div className="form-row" style={{ gridColumn: '1 / -1' }}><label>Documents (URL d'un dossier partagé contenant identité + mandat signé)</label><input className="form-input" value={draft.documents_url} onChange={e => setDraft({ ...draft, documents_url: e.target.value })} placeholder="https://drive.google.com/…" /></div>
                            <div className="form-row" style={{ gridColumn: '1 / -1' }}><label>Notes</label><textarea className="form-input" rows={2} value={draft.notes} onChange={e => setDraft({ ...draft, notes: e.target.value })} /></div>
                        </div>
                        <div style={{ display: 'flex', gap: '0.5rem', justifyContent: 'flex-end', marginTop: '0.75rem' }}>
                            <button className="btn btn-secondary" onClick={() => setShow(false)}>Annuler</button>
                            <button className="btn btn-primary" onClick={create}>Envoyer</button>
                        </div>
                    </div>
                )}
                {loading ? <p>Chargement…</p> : reqs.length === 0 ? <p style={{ color: 'var(--text-muted)' }}>Aucune demande.</p> : (
                    <div className="card">
                        <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '0.875rem' }}>
                            <thead><tr style={{ background: '#f8fafc', textAlign: 'left' }}>
                                <th style={{ padding: '0.75rem 1rem' }}>Numéro</th><th>Opérateur</th><th>Titulaire</th><th>Statut</th><th>Date</th><th></th>
                            </tr></thead>
                            <tbody>
                                {reqs.map(r => (
                                    <tr key={r.id} style={{ borderTop: '1px solid var(--border)' }}>
                                        <td style={{ padding: '0.6rem 1rem', fontFamily: 'ui-monospace, monospace' }}>{r.phone_number}</td>
                                        <td>{r.current_carrier || '-'}</td>
                                        <td>{r.owner_name}</td>
                                        <td>{statusBadge(r.status)}</td>
                                        <td style={{ color: 'var(--text-muted)' }}>{parseUtcDate(r.created_at)?.toLocaleDateString('fr-CH') || '-'}</td>
                                        <td>{['requested', 'in_review'].includes(r.status) && <button className="btn btn-secondary btn-sm" onClick={() => cancel(r)}>Annuler</button>}</td>
                                    </tr>
                                ))}
                            </tbody>
                        </table>
                    </div>
                )}
            </div>
        </>
    );
};

// ==================== PHONE HISTORY (modal + provider + helpers) ====================
const PhoneHistoryContext = createContext({ open: () => {} });
const usePhoneHistory = () => useContext(PhoneHistoryContext);

const PhoneHistoryProvider = ({ children }) => {
    const [phone, setPhone] = useState(null);
    const open = useCallback((p) => { if (p) setPhone(p); }, []);
    return (
        <PhoneHistoryContext.Provider value={{ open }}>
            {children}
            {phone && <PhoneHistoryModal phone={phone} onClose={() => setPhone(null)} />}
        </PhoneHistoryContext.Provider>
    );
};

const PhoneLink = ({ phone, children, style }) => {
    const { open } = usePhoneHistory();
    if (!phone) return <span>{children || '-'}</span>;
    return (
        <button
            type="button"
            onClick={(e) => { e.stopPropagation(); open(phone); }}
            style={{ background: 'none', border: 'none', padding: 0, cursor: 'pointer', color: 'var(--primary)', font: 'inherit', textAlign: 'left', textDecoration: 'underline dotted', ...(style || {}) }}
            title={phone}
        >
            {children != null ? children : phone}
        </button>
    );
};

const PhoneHistoryButton = ({ phone, label }) => {
    const { open } = usePhoneHistory();
    const lang = useLang();
    if (!phone) return null;
    return (
        <button type="button" className="btn btn-secondary btn-sm" onClick={() => open(phone)}>
            <Icons.Eye className="w-4 h-4" /> {label || (lang === 'de' ? 'Verlauf' : lang === 'en' ? 'History' : lang === 'it' ? 'Storico' : "Voir l'historique")}
        </button>
    );
};

const PhoneHistoryModal = ({ phone, onClose }) => {
    const { notify } = useApp();
    const lang = useLang();
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);
    const [tab, setTab] = useState('calls');
    const [busy, setBusy] = useState(false);

    const load = useCallback(async () => {
        setLoading(true);
        try {
            const r = await api.get(`/my/phone-history/${encodeURIComponent(phone)}`);
            if (r?.error) notify.error(r.error);
            setData(r || {});
        } catch (e) { notify.error('Erreur chargement'); }
        finally { setLoading(false); }
    }, [phone, notify]);

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

    const blockNumber = async () => {
        if (!confirm(`Bloquer ${phone} ?`)) return;
        setBusy(true);
        try {
            const r = await api.post('/my/spam-reports', { phone_number: phone, reason: 'other' });
            if (r?.error) notify.error(r.error);
            else { notify.success('Numéro bloqué'); load(); }
        } catch (e) { notify.error('Erreur: ' + e.message); }
        finally { setBusy(false); }
    };

    const enriched = data?.enriched;
    const contact = data?.contact;
    const calls = data?.calls || [];
    const sms = data?.sms || [];
    const conversations = data?.conversations || [];
    const nps = data?.nps || [];
    const spamReports = data?.spam_reports || [];

    const tabs = [
        { id: 'calls', label: t('nav.calls', lang) + ` (${calls.length})` },
        { id: 'sms', label: 'SMS' + ` (${sms.length})` },
        { id: 'conv', label: t('nav.conversations', lang) + ` (${conversations.length})` },
        { id: 'nps', label: 'NPS' + ` (${nps.length})` },
        { id: 'spam', label: 'Spam' + ` (${spamReports.length})` },
    ];

    return (
        <div onClick={onClose} style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.45)', zIndex: 200, display: 'flex', alignItems: 'flex-start', justifyContent: 'center', padding: '2rem 1rem', overflowY: 'auto' }}>
            <div onClick={(e) => e.stopPropagation()} className="card" style={{ background: 'var(--bg-white)', width: '100%', maxWidth: 880, padding: 0, boxShadow: '0 24px 64px rgba(0,0,0,0.25)' }}>
                <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '1rem 1.25rem', borderBottom: '1px solid var(--border)' }}>
                    <div>
                        <div style={{ fontSize: '0.75rem', color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.05em', fontWeight: 600 }}>{t('nav.contacts', lang)}</div>
                        <h2 style={{ margin: 0, fontFamily: 'ui-monospace, monospace', fontSize: '1.15rem' }}>{phone}</h2>
                    </div>
                    <div style={{ display: 'flex', gap: '0.5rem', alignItems: 'center' }}>
                        <button className="btn btn-secondary btn-sm" onClick={blockNumber} disabled={busy}>
                            <Icons.Shield className="w-4 h-4" /> Bloquer
                        </button>
                        <button onClick={onClose} style={{ background: 'none', border: 'none', cursor: 'pointer', padding: '0.5rem' }} title={t('common.cancel', lang)}>
                            <Icons.X className="w-5 h-5" />
                        </button>
                    </div>
                </div>

                <div style={{ padding: '1rem 1.25rem' }}>
                    {loading ? (
                        <p style={{ color: 'var(--text-muted)' }}>{t('common.loading', lang)}</p>
                    ) : (
                        <>
                            {(enriched || contact) && (
                                <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: '0.75rem', marginBottom: '1rem' }}>
                                    {enriched && (
                                        <div className="card" style={{ padding: '0.75rem 1rem', background: '#f0f9ff', border: '1px solid #bae6fd' }}>
                                            <div style={{ fontSize: '0.7rem', textTransform: 'uppercase', color: '#0c4a6e', fontWeight: 700 }}>Enrichissement</div>
                                            <div style={{ fontWeight: 600, marginTop: '0.25rem' }}>{enriched.display_name || enriched.company_name || '-'}</div>
                                            {enriched.company_name && enriched.display_name && <div style={{ fontSize: '0.8rem', color: 'var(--text-muted)' }}>{enriched.company_name}</div>}
                                            <div style={{ fontSize: '0.7rem', color: 'var(--text-muted)', marginTop: '0.4rem' }}>
                                                {enriched.source ? `Source : ${enriched.source}` : ''}
                                                {enriched.confidence != null ? ` · ${Math.round(Number(enriched.confidence) * 100)}%` : ''}
                                            </div>
                                        </div>
                                    )}
                                    {contact && (
                                        <div className="card" style={{ padding: '0.75rem 1rem' }}>
                                            <div style={{ fontSize: '0.7rem', textTransform: 'uppercase', color: 'var(--text-muted)', fontWeight: 700 }}>Fiche CRM</div>
                                            <div style={{ fontWeight: 600, marginTop: '0.25rem', display: 'flex', gap: '0.4rem', alignItems: 'center' }}>
                                                {contact.is_vip && <span title="VIP" style={{ color: '#f59e0b' }}>★</span>}
                                                {contact.is_blacklisted && <span title="Blacklisté" style={{ color: '#dc2626' }}>⊘</span>}
                                                {contact.name || '-'}
                                            </div>
                                            {contact.company && <div style={{ fontSize: '0.8rem', color: 'var(--text-muted)' }}>{contact.company}</div>}
                                            {contact.email && <div style={{ fontSize: '0.8rem', color: 'var(--text-muted)' }}>{contact.email}</div>}
                                            {contact.tags && <div style={{ fontSize: '0.7rem', color: 'var(--text-muted)', marginTop: '0.3rem' }}>{contact.tags}</div>}
                                            {contact.notes && <div style={{ fontSize: '0.75rem', marginTop: '0.4rem', color: '#475569', fontStyle: 'italic' }}>{contact.notes}</div>}
                                        </div>
                                    )}
                                </div>
                            )}

                            <div style={{ display: 'flex', gap: '0.25rem', borderBottom: '1px solid var(--border)', marginBottom: '0.75rem', overflowX: 'auto' }}>
                                {tabs.map(tt => (
                                    <button key={tt.id} onClick={() => setTab(tt.id)}
                                        style={{ background: 'none', border: 'none', padding: '0.5rem 0.85rem', cursor: 'pointer', fontSize: '0.8rem', fontWeight: 500, color: tab === tt.id ? 'var(--primary)' : 'var(--text-muted)', borderBottom: tab === tt.id ? '2px solid var(--primary)' : '2px solid transparent', whiteSpace: 'nowrap' }}>
                                        {tt.label}
                                    </button>
                                ))}
                            </div>

                            <div style={{ maxHeight: '50vh', overflow: 'auto' }}>
                                {tab === 'calls' && (
                                    calls.length === 0 ? <p style={{ color: 'var(--text-muted)' }}>{t('common.empty', lang)}</p> : (
                                        <table style={{ width: '100%', fontSize: '0.85rem', borderCollapse: 'collapse' }}>
                                            <thead><tr style={{ textAlign: 'left', color: 'var(--text-muted)' }}><th style={{ padding: '0.4rem 0.5rem' }}>Date</th><th>Durée</th><th>Statut</th><th>Résumé</th></tr></thead>
                                            <tbody>{calls.map(c => (
                                                <tr key={c.id} style={{ borderTop: '1px solid var(--border)' }}>
                                                    <td style={{ padding: '0.4rem 0.5rem', whiteSpace: 'nowrap' }}>{formatDateTime(c.created_at)}</td>
                                                    <td style={{ whiteSpace: 'nowrap' }}>{formatDuration(c.duration)}</td>
                                                    <td>{c.status}</td>
                                                    <td style={{ color: '#475569', fontStyle: 'italic' }}>{c.ai_summary || c.transcript ? <span title={c.transcript || ''}>{(c.ai_summary || '').slice(0, 120)}</span> : '-'}</td>
                                                </tr>
                                            ))}</tbody>
                                        </table>
                                    )
                                )}
                                {tab === 'sms' && (
                                    sms.length === 0 ? <p style={{ color: 'var(--text-muted)' }}>{t('common.empty', lang)}</p> : (
                                        <table style={{ width: '100%', fontSize: '0.85rem', borderCollapse: 'collapse' }}>
                                            <thead><tr style={{ textAlign: 'left', color: 'var(--text-muted)' }}><th style={{ padding: '0.4rem 0.5rem' }}>Date</th><th>Sens</th><th>Message</th></tr></thead>
                                            <tbody>{sms.map(m => (
                                                <tr key={m.id} style={{ borderTop: '1px solid var(--border)' }}>
                                                    <td style={{ padding: '0.4rem 0.5rem', whiteSpace: 'nowrap' }}>{formatDateTime(m.created_at)}</td>
                                                    <td>{m.direction === 'outbound' ? '↑' : '↓'}</td>
                                                    <td>{m.body}</td>
                                                </tr>
                                            ))}</tbody>
                                        </table>
                                    )
                                )}
                                {tab === 'conv' && (
                                    conversations.length === 0 ? <p style={{ color: 'var(--text-muted)' }}>{t('common.empty', lang)}</p> : (
                                        <ul style={{ listStyle: 'none', padding: 0, margin: 0 }}>{conversations.map(c => (
                                            <li key={c.id} style={{ padding: '0.5rem 0', borderTop: '1px solid var(--border)', fontSize: '0.85rem' }}>
                                                <strong>{c.channel}</strong> · <span style={{ color: 'var(--text-muted)' }}>{formatRelative(c.last_message_at)}</span>
                                                <div>{c.last_message_preview}</div>
                                            </li>
                                        ))}</ul>
                                    )
                                )}
                                {tab === 'nps' && (
                                    nps.length === 0 ? <p style={{ color: 'var(--text-muted)' }}>{t('common.empty', lang)}</p> : (
                                        <table style={{ width: '100%', fontSize: '0.85rem', borderCollapse: 'collapse' }}>
                                            <thead><tr style={{ textAlign: 'left', color: 'var(--text-muted)' }}><th style={{ padding: '0.4rem 0.5rem' }}>Date</th><th>Score</th><th>Méthode</th><th>Comment</th></tr></thead>
                                            <tbody>{nps.map(n => (
                                                <tr key={n.id} style={{ borderTop: '1px solid var(--border)' }}>
                                                    <td style={{ padding: '0.4rem 0.5rem' }}>{formatDateTime(n.created_at)}</td>
                                                    <td><strong>{n.score ?? '-'}</strong></td>
                                                    <td>{n.method}</td>
                                                    <td style={{ color: '#475569' }}>{n.comment || '-'}</td>
                                                </tr>
                                            ))}</tbody>
                                        </table>
                                    )
                                )}
                                {tab === 'spam' && (
                                    spamReports.length === 0 ? <p style={{ color: 'var(--text-muted)' }}>{t('common.empty', lang)}</p> : (
                                        <ul style={{ listStyle: 'none', padding: 0, margin: 0 }}>{spamReports.map(r => (
                                            <li key={r.id} style={{ padding: '0.5rem 0', borderTop: '1px solid var(--border)', fontSize: '0.85rem' }}>
                                                <strong>{r.reason}</strong> · <span style={{ color: 'var(--text-muted)' }}>{formatDateTime(r.created_at)}</span>
                                            </li>
                                        ))}</ul>
                                    )
                                )}
                            </div>
                        </>
                    )}
                </div>
            </div>
        </div>
    );
};

// ==================== CONVERSATIONS VIEW ====================
const ConversationsView = () => {
    const { notify } = useApp();
    const lang = useLang();
    const [list, setList] = useState([]);
    const [loading, setLoading] = useState(true);
    const [refreshing, setRefreshing] = useState(false);
    const [channel, setChannel] = useState('');
    const [openId, setOpenId] = useState(null);

    const load = useCallback(async (background = false) => {
        if (background) setRefreshing(true); else setLoading(true);
        try {
            const params = new URLSearchParams();
            if (channel) params.set('channel', channel);
            const r = await api.get(`/my/conversations?${params}`);
            setList(r?.conversations || []);
        } catch (e) { notify.error('Erreur'); }
        finally { setLoading(false); setRefreshing(false); }
    }, [channel, notify]);

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

    return (
        <>
            <div className="page-header">
                <div>
                    <h1 className="page-title">{t('nav.conversations', lang)}</h1>
                    <p className="page-subtitle">WhatsApp · Webchat · SMS - historique unifié par contact.</p>
                </div>
                <div className="page-actions">
                    <button className="btn btn-secondary btn-sm" onClick={() => load(true)} disabled={refreshing}>
                        <Icons.Refresh className="w-4 h-4" /> {refreshing ? '…' : 'Actualiser'}
                    </button>
                </div>
            </div>
            <div className="page-content">
                <div className="card">
                    <div style={{ padding: '1rem 1.25rem', borderBottom: '1px solid var(--border)', display: 'flex', gap: '0.5rem', flexWrap: 'wrap', alignItems: 'center' }}>
                        <select className="form-input" value={channel} onChange={e => setChannel(e.target.value)} style={{ width: 200, marginBottom: 0 }}>
                            <option value="">Tous les canaux</option>
                            <option value="whatsapp">WhatsApp</option>
                            <option value="webchat">Webchat</option>
                            <option value="sms">SMS</option>
                        </select>
                    </div>
                    {loading ? (
                        <p style={{ padding: '1rem' }}>{t('common.loading', lang)}</p>
                    ) : list.length === 0 ? (
                        <p style={{ padding: '2rem', color: 'var(--text-muted)', textAlign: 'center' }}>{t('common.empty', lang)}</p>
                    ) : (
                        <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '0.875rem' }}>
                            <thead><tr style={{ background: '#f8fafc', textAlign: 'left' }}>
                                <th style={{ padding: '0.65rem 1rem' }}>Contact</th>
                                <th>Canal</th>
                                <th>Dernier message</th>
                                <th>Quand</th>
                                <th style={{ textAlign: 'right' }}>Non lus</th>
                            </tr></thead>
                            <tbody>{list.map(c => (
                                <tr key={c.id} style={{ borderTop: '1px solid var(--border)', cursor: 'pointer' }} onClick={() => setOpenId(c.id)}>
                                    <td style={{ padding: '0.6rem 1rem' }}>
                                        <div style={{ fontWeight: 600 }}>{c.contact_name || '-'}</div>
                                        <div style={{ fontFamily: 'ui-monospace, monospace', fontSize: '0.75rem', color: 'var(--text-muted)' }}>
                                            <PhoneLink phone={c.contact_phone}>{c.contact_phone}</PhoneLink>
                                        </div>
                                    </td>
                                    <td><span className="badge badge-info" style={{ textTransform: 'capitalize' }}>{c.channel}</span></td>
                                    <td style={{ color: '#475569', maxWidth: 380, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{c.last_message_preview || '-'}</td>
                                    <td style={{ color: 'var(--text-muted)', whiteSpace: 'nowrap' }}>{formatRelative(c.last_message_at)}</td>
                                    <td style={{ textAlign: 'right' }}>{c.unread_count > 0 ? <span className="badge badge-primary">{c.unread_count}</span> : <span style={{ color: 'var(--text-muted)' }}>-</span>}</td>
                                </tr>
                            ))}</tbody>
                        </table>
                    )}
                </div>
            </div>
            {openId && <ConversationDetailModal id={openId} onClose={() => { setOpenId(null); load(true); }} />}
        </>
    );
};

const ConversationDetailModal = ({ id, onClose }) => {
    const { notify } = useApp();
    const lang = useLang();
    const [conv, setConv] = useState(null);
    const [messages, setMessages] = useState([]);
    const [body, setBody] = useState('');
    const [sending, setSending] = useState(false);
    const scrollRef = useRef(null);

    const load = useCallback(async (background = false) => {
        try {
            const r = await api.get(`/my/conversations/${id}`);
            if (r?.error) { notify.error(r.error); return; }
            setConv(r.conversation || null);
            setMessages(r.messages || []);
        } catch (e) { if (!background) notify.error('Erreur'); }
    }, [id, notify]);

    useEffect(() => { load(); }, [load]);
    useEffect(() => {
        const i = setInterval(() => load(true), 4000);
        return () => clearInterval(i);
    }, [load]);
    useEffect(() => {
        if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
    }, [messages.length]);

    const send = async () => {
        if (!body.trim()) return;
        setSending(true);
        try {
            const r = await api.post('/my/conversations/send', { conversation_id: id, body: body.trim() });
            if (r?.error) notify.error(r.error);
            else { setBody(''); load(true); }
        } catch (e) { notify.error('Erreur: ' + e.message); }
        finally { setSending(false); }
    };

    return (
        <div onClick={onClose} style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.45)', zIndex: 200, display: 'flex', alignItems: 'flex-start', justifyContent: 'center', padding: '2rem 1rem', overflowY: 'auto' }}>
            <div onClick={(e) => e.stopPropagation()} className="card" style={{ background: 'var(--bg-white)', width: '100%', maxWidth: 720, padding: 0, display: 'flex', flexDirection: 'column', height: '80vh' }}>
                <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0.75rem 1.25rem', borderBottom: '1px solid var(--border)' }}>
                    <div>
                        <div style={{ fontWeight: 700 }}>{conv?.contact_name || conv?.contact_phone || '-'}</div>
                        <div style={{ fontSize: '0.75rem', color: 'var(--text-muted)' }}>
                            {conv?.channel} · <PhoneLink phone={conv?.contact_phone}>{conv?.contact_phone}</PhoneLink>
                        </div>
                    </div>
                    <button onClick={onClose} style={{ background: 'none', border: 'none', cursor: 'pointer', padding: '0.5rem' }}><Icons.X className="w-5 h-5" /></button>
                </div>
                <div ref={scrollRef} style={{ flex: 1, overflowY: 'auto', padding: '1rem', background: '#f8fafc', display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
                    {messages.length === 0 ? (
                        <p style={{ color: 'var(--text-muted)', textAlign: 'center' }}>{t('common.empty', lang)}</p>
                    ) : messages.map(m => {
                        const out = m.direction === 'outbound';
                        return (
                            <div key={m.id} style={{ alignSelf: out ? 'flex-end' : 'flex-start', maxWidth: '75%' }}>
                                <div style={{ background: out ? 'var(--primary)' : '#fff', color: out ? '#fff' : '#0f172a', padding: '0.55rem 0.85rem', borderRadius: 14, fontSize: '0.875rem', boxShadow: '0 1px 2px rgba(0,0,0,0.08)' }}>
                                    {m.body}
                                </div>
                                <div style={{ fontSize: '0.65rem', color: 'var(--text-muted)', marginTop: '0.2rem', textAlign: out ? 'right' : 'left' }}>
                                    {formatTimeOnly(m.created_at)}
                                </div>
                            </div>
                        );
                    })}
                </div>
                <div style={{ display: 'flex', gap: '0.5rem', padding: '0.75rem', borderTop: '1px solid var(--border)' }}>
                    <input className="form-input" value={body} onChange={e => setBody(e.target.value)}
                        onKeyDown={(e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(); } }}
                        placeholder="Écrire un message…" style={{ marginBottom: 0, flex: 1 }} />
                    <button className="btn btn-primary" onClick={send} disabled={sending || !body.trim()}>
                        <Icons.Send className="w-4 h-4" /> Envoyer
                    </button>
                </div>
            </div>
        </div>
    );
};

// ==================== ROUTING PANEL (line tab) ====================
const RoutingPanel = ({ lineId }) => {
    const { notify } = useApp();
    const lang = useLang();
    const [rules, setRules] = useState([]);
    const [loading, setLoading] = useState(true);
    const [draft, setDraft] = useState(null);

    const load = useCallback(async () => {
        setLoading(true);
        try {
            const r = await api.get(`/my/lines/${lineId}/routing`);
            setRules(r?.rules || []);
        } catch (e) { notify.error('Erreur'); }
        finally { setLoading(false); }
    }, [lineId, notify]);

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

    const blank = () => ({
        id: null,
        label: '',
        priority: 100,
        is_active: true,
        match_type: 'caller_pattern',
        match_value: '',
        match_keywords: '',
        action_type: 'forward',
        action_target: '',
        action_message: '',
    });

    const startNew = () => setDraft(blank());
    const startEdit = (r) => setDraft({
        id: r.id,
        label: r.label || '',
        priority: r.priority ?? 100,
        is_active: !!r.is_active,
        match_type: r.match_type || 'caller_pattern',
        match_value: r.match_value || '',
        match_keywords: Array.isArray(r.match_keywords) ? r.match_keywords.join(', ') : (r.match_keywords || ''),
        action_type: r.action_type || 'forward',
        action_target: r.action_target || '',
        action_message: r.action_message || '',
    });

    const save = async () => {
        if (!draft.label.trim()) return notify.error('Libellé requis');
        const payload = {
            label: draft.label.trim(),
            priority: Number(draft.priority) || 100,
            is_active: !!draft.is_active,
            match_type: draft.match_type,
            match_value: draft.match_value || null,
            match_keywords: draft.match_type === 'keyword'
                ? (draft.match_keywords || '').split(',').map(s => s.trim()).filter(Boolean)
                : null,
            action_type: draft.action_type,
            action_target: draft.action_target || null,
            action_message: draft.action_message || null,
        };
        try {
            const r = draft.id
                ? await api.put(`/my/lines/${lineId}/routing/${draft.id}`, payload)
                : await api.post(`/my/lines/${lineId}/routing`, payload);
            if (r?.error) return notify.error(r.error);
            notify.success('Règle enregistrée');
            setDraft(null);
            load();
        } catch (e) { notify.error('Erreur: ' + e.message); }
    };

    const remove = async (r) => {
        if (!confirm(`Supprimer la règle "${r.label}" ?`)) return;
        try {
            const x = await api.del(`/my/lines/${lineId}/routing/${r.id}`);
            if (x?.error) notify.error(x.error);
            else { notify.success('Supprimée'); load(); }
        } catch (e) { notify.error('Erreur'); }
    };

    return (
        <div className="card" style={{ padding: '1.25rem' }}>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
                <h3 style={{ margin: 0 }}>{t('nav.routing', lang)}</h3>
                <button className="btn btn-primary btn-sm" onClick={startNew}><Icons.Plus className="w-4 h-4" /> Ajouter une règle</button>
            </div>
            <p style={{ margin: '0 0 1rem', color: 'var(--text-muted)', fontSize: '0.85rem' }}>
                Définissez des règles pour rediriger, filtrer ou personnaliser les appels en fonction du numéro appelant, de l'heure, d'un mot-clé ou d'une intention détectée.
            </p>

            {draft && (
                <div className="card" style={{ padding: '1rem', marginBottom: '1rem', border: '2px solid var(--primary-light)' }}>
                    <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '0.75rem' }}>
                        <div className="form-row"><label>Libellé</label><input className="form-input" value={draft.label} onChange={e => setDraft({ ...draft, label: e.target.value })} /></div>
                        <div className="form-row"><label>Priorité</label><input type="number" className="form-input" value={draft.priority} onChange={e => setDraft({ ...draft, priority: e.target.value })} /></div>
                        <div className="form-row" style={{ gridColumn: '1 / -1' }}>
                            <label className="toggle-row"><input type="checkbox" checked={draft.is_active} onChange={e => setDraft({ ...draft, is_active: e.target.checked })} /><span>Active</span></label>
                        </div>
                        <div className="form-row"><label>Type de match</label>
                            <select className="form-input" value={draft.match_type} onChange={e => setDraft({ ...draft, match_type: e.target.value })}>
                                <option value="caller_pattern">Numéro appelant (regex)</option>
                                <option value="time">Plage horaire</option>
                                <option value="keyword">Mots-clés (transcription)</option>
                                <option value="intent">Intention</option>
                            </select>
                        </div>
                        {draft.match_type !== 'keyword' && (
                            <div className="form-row"><label>Valeur</label>
                                <input className="form-input" value={draft.match_value} onChange={e => setDraft({ ...draft, match_value: e.target.value })}
                                    placeholder={draft.match_type === 'caller_pattern' ? '^\\+4179.*' : draft.match_type === 'time' ? '08:00-17:00' : 'rendez-vous'} />
                            </div>
                        )}
                        {draft.match_type === 'keyword' && (
                            <div className="form-row" style={{ gridColumn: '1 / -1' }}>
                                <label>Mots-clés (séparés par virgules)</label>
                                <textarea className="form-input" rows={2} value={draft.match_keywords} onChange={e => setDraft({ ...draft, match_keywords: e.target.value })} placeholder="urgence, rendez-vous, devis" />
                            </div>
                        )}
                        <div className="form-row"><label>Action</label>
                            <select className="form-input" value={draft.action_type} onChange={e => setDraft({ ...draft, action_type: e.target.value })}>
                                <option value="forward">Rediriger</option>
                                <option value="voicemail">Messagerie</option>
                                <option value="sms">Envoyer un SMS</option>
                                <option value="hangup">Raccrocher</option>
                            </select>
                        </div>
                        <div className="form-row"><label>Cible</label>
                            <input className="form-input" value={draft.action_target} onChange={e => setDraft({ ...draft, action_target: e.target.value })}
                                placeholder={draft.action_type === 'forward' ? '+41 …' : ''} />
                        </div>
                        <div className="form-row" style={{ gridColumn: '1 / -1' }}>
                            <label>Message TTS (optionnel)</label>
                            <textarea className="form-input" rows={2} value={draft.action_message} onChange={e => setDraft({ ...draft, action_message: e.target.value })} />
                        </div>
                    </div>
                    <div style={{ display: 'flex', gap: '0.5rem', justifyContent: 'flex-end', marginTop: '0.75rem' }}>
                        <button className="btn btn-secondary" onClick={() => setDraft(null)}>{t('common.cancel', lang)}</button>
                        <button className="btn btn-primary" onClick={save}>{t('common.save', lang)}</button>
                    </div>
                </div>
            )}

            {loading ? <p>{t('common.loading', lang)}</p> : rules.length === 0 ? (
                <p style={{ color: 'var(--text-muted)' }}>{t('common.empty', lang)}</p>
            ) : (
                <div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
                    {rules.map(r => (
                        <div key={r.id} className="card" style={{ padding: '0.75rem 1rem', display: 'flex', alignItems: 'center', gap: '1rem', border: '1px solid var(--border)' }}>
                            <div style={{ flex: 1 }}>
                                <div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', flexWrap: 'wrap' }}>
                                    <strong>{r.label}</strong>
                                    {r.is_active ? <span className="badge badge-success">Active</span> : <span className="badge badge-gray">Inactive</span>}
                                    <span className="badge badge-gray">Prio {r.priority}</span>
                                </div>
                                <div style={{ fontSize: '0.8rem', color: 'var(--text-muted)', marginTop: '0.25rem' }}>
                                    Si <strong>{r.match_type}</strong>{r.match_value ? <> = <code>{r.match_value}</code></> : null}
                                    {' → '}
                                    <strong>{r.action_type}</strong>{r.action_target ? <> ({r.action_target})</> : null}
                                </div>
                            </div>
                            <button className="btn btn-secondary btn-sm" onClick={() => startEdit(r)}><Icons.Edit className="w-4 h-4" /></button>
                            <button className="btn btn-secondary btn-sm" onClick={() => remove(r)} style={{ color: '#dc2626' }}><Icons.Trash className="w-4 h-4" /></button>
                        </div>
                    ))}
                </div>
            )}
        </div>
    );
};

// ==================== NPS PANEL (line tab) ====================
const NpsPanel = ({ line, onSaved }) => {
    const { notify } = useApp();
    const lang = useLang();
    const [form, setForm] = useState({
        nps_enabled: !!line.nps_enabled,
        nps_method: line.nps_method || 'dtmf',
        nps_question: line.nps_question || 'Comment évaluez-vous cet appel sur une échelle de 1 à 5 ?',
        nps_min_duration: line.nps_min_duration || 30,
    });
    const [saving, setSaving] = useState(false);
    const [surveys, setSurveys] = useState([]);
    const [loadingS, setLoadingS] = useState(true);

    useEffect(() => {
        setForm({
            nps_enabled: !!line.nps_enabled,
            nps_method: line.nps_method || 'dtmf',
            nps_question: line.nps_question || 'Comment évaluez-vous cet appel sur une échelle de 1 à 5 ?',
            nps_min_duration: line.nps_min_duration || 30,
        });
    }, [line.id]);

    const load = useCallback(async () => {
        setLoadingS(true);
        try {
            const r = await api.get(`/my/nps/surveys?line_id=${line.id}`);
            setSurveys(r?.surveys || []);
        } catch (e) {} finally { setLoadingS(false); }
    }, [line.id]);

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

    const save = async () => {
        setSaving(true);
        try {
            const r = await api.put(`/my/lines/${line.id}/nps`, {
                nps_enabled: !!form.nps_enabled,
                nps_method: form.nps_method,
                nps_question: form.nps_question,
                nps_min_duration: Number(form.nps_min_duration) || 30,
            });
            if (r?.error) notify.error(r.error);
            else { notify.success('Configuration NPS enregistrée'); onSaved?.(); }
        } catch (e) { notify.error('Erreur: ' + e.message); }
        finally { setSaving(false); }
    };

    const stats = useMemo(() => npsAggregate(surveys), [surveys]);

    return (
        <>
            <div className="card" style={{ padding: '1.5rem', marginBottom: '1rem' }}>
                <h3 style={{ margin: '0 0 1rem' }}>{t('nav.nps', lang)} - Configuration</h3>
                <div className="form-group">
                    <label className="toggle-row"><input type="checkbox" checked={form.nps_enabled} onChange={e => setForm({ ...form, nps_enabled: e.target.checked })} /><span>Activer le sondage NPS après les appels</span></label>
                </div>
                <div className="form-group">
                    <label className="form-label">Méthode</label>
                    <select className="form-input" value={form.nps_method} onChange={e => setForm({ ...form, nps_method: e.target.value })}>
                        <option value="dtmf">Touches DTMF (1-5)</option>
                        <option value="voice">Réponse vocale</option>
                        <option value="sms">SMS post-appel</option>
                    </select>
                </div>
                <div className="form-group">
                    <label className="form-label">Question</label>
                    <textarea className="form-input" rows={2} value={form.nps_question} onChange={e => setForm({ ...form, nps_question: e.target.value })} />
                </div>
                <div className="form-group">
                    <label className="form-label">Durée minimale d'appel (secondes) avant déclenchement</label>
                    <input type="number" className="form-input" value={form.nps_min_duration} onChange={e => setForm({ ...form, nps_min_duration: e.target.value })} />
                </div>
                <button className="btn btn-primary" onClick={save} disabled={saving}>{saving ? '…' : t('common.save', lang)}</button>
            </div>

            <div className="card" style={{ padding: '1.25rem' }}>
                <h3 style={{ margin: '0 0 1rem' }}>Résultats récents</h3>
                {loadingS ? <p>{t('common.loading', lang)}</p> : (
                    <>
                        <div className="stats-grid" style={{ marginBottom: '1rem' }}>
                            <div className="stat-card"><div className="stat-icon blue"><Icons.Star className="w-6 h-6" /></div><div><div className="stat-label">Score moyen</div><div className="stat-value">{stats.avg.toFixed(2)}</div></div></div>
                            <div className="stat-card"><div className="stat-icon green"><Icons.Check className="w-6 h-6" /></div><div><div className="stat-label">Promoteurs (≥4)</div><div className="stat-value">{stats.promotersPct}%</div></div></div>
                            <div className="stat-card"><div className="stat-icon orange"><Icons.AlertCircle className="w-6 h-6" /></div><div><div className="stat-label">Détracteurs (≤2)</div><div className="stat-value">{stats.detractorsPct}%</div></div></div>
                            <div className="stat-card"><div className="stat-icon purple"><Icons.Sparkles className="w-6 h-6" /></div><div><div className="stat-label">NPS</div><div className="stat-value">{stats.nps}</div></div></div>
                        </div>
                        <NpsSurveysTable surveys={surveys} />
                    </>
                )}
            </div>
        </>
    );
};

function npsAggregate(surveys) {
    const valid = (surveys || []).filter(s => s.score != null && !Number.isNaN(Number(s.score)));
    const total = valid.length;
    if (total === 0) return { total: 0, avg: 0, promotersPct: 0, detractorsPct: 0, nps: 0 };
    const sum = valid.reduce((a, s) => a + Number(s.score), 0);
    const promoters = valid.filter(s => Number(s.score) >= 4).length;
    const detractors = valid.filter(s => Number(s.score) <= 2).length;
    const promotersPct = Math.round((promoters / total) * 100);
    const detractorsPct = Math.round((detractors / total) * 100);
    return {
        total,
        avg: sum / total,
        promotersPct,
        detractorsPct,
        nps: promotersPct - detractorsPct,
    };
}

const NpsSurveysTable = ({ surveys }) => {
    const lang = useLang();
    if (!surveys || surveys.length === 0) return <p style={{ color: 'var(--text-muted)' }}>{t('common.empty', lang)}</p>;
    return (
        <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '0.85rem' }}>
            <thead><tr style={{ background: '#f8fafc', textAlign: 'left' }}>
                <th style={{ padding: '0.5rem 0.75rem' }}>Date</th>
                <th>Numéro</th>
                <th>Score</th>
                <th>Méthode</th>
                <th>Commentaire</th>
            </tr></thead>
            <tbody>{surveys.slice(0, 50).map(s => (
                <tr key={s.id} style={{ borderTop: '1px solid var(--border)' }}>
                    <td style={{ padding: '0.45rem 0.75rem', whiteSpace: 'nowrap' }}>{formatDateTime(s.created_at)}</td>
                    <td style={{ fontFamily: 'ui-monospace, monospace' }}><PhoneLink phone={s.caller_number}>{s.caller_number || '-'}</PhoneLink></td>
                    <td><strong>{s.score ?? '-'}</strong></td>
                    <td>{s.method || '-'}</td>
                    <td style={{ color: '#475569' }}>{(s.comment || '').slice(0, 80)}{(s.comment || '').length > 80 ? '…' : ''}</td>
                </tr>
            ))}</tbody>
        </table>
    );
};

// ==================== NPS GLOBAL VIEW ====================
const NpsView = () => {
    const { notify } = useApp();
    const lang = useLang();
    const [surveys, setSurveys] = useState([]);
    const [loading, setLoading] = useState(true);

    const load = useCallback(async () => {
        setLoading(true);
        try {
            const r = await api.get('/my/nps/surveys');
            setSurveys(r?.surveys || []);
        } catch (e) { notify.error('Erreur'); }
        finally { setLoading(false); }
    }, [notify]);

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

    const stats = useMemo(() => npsAggregate(surveys), [surveys]);
    const timeline = useMemo(() => buildNpsTimeline(surveys), [surveys]);

    return (
        <>
            <div className="page-header">
                <div>
                    <h1 className="page-title">{t('nav.nps', lang)}</h1>
                    <p className="page-subtitle">Satisfaction client agrégée - toutes lignes confondues.</p>
                </div>
                <div className="page-actions">
                    <button className="btn btn-secondary btn-sm" onClick={load}><Icons.Refresh className="w-4 h-4" /> Actualiser</button>
                </div>
            </div>
            <div className="page-content">
                {loading ? <p>{t('common.loading', lang)}</p> : (
                    <>
                        <div className="stats-grid" style={{ marginBottom: '1rem' }}>
                            <div className="stat-card"><div className="stat-icon blue"><Icons.Star className="w-6 h-6" /></div><div><div className="stat-label">Score moyen</div><div className="stat-value">{stats.avg.toFixed(2)}</div></div></div>
                            <div className="stat-card"><div className="stat-icon gray"><Icons.Users className="w-6 h-6" /></div><div><div className="stat-label">Réponses</div><div className="stat-value">{stats.total}</div></div></div>
                            <div className="stat-card"><div className="stat-icon green"><Icons.Check className="w-6 h-6" /></div><div><div className="stat-label">Promoteurs</div><div className="stat-value">{stats.promotersPct}%</div></div></div>
                            <div className="stat-card"><div className="stat-icon orange"><Icons.AlertCircle className="w-6 h-6" /></div><div><div className="stat-label">Détracteurs</div><div className="stat-value">{stats.detractorsPct}%</div></div></div>
                            <div className="stat-card"><div className="stat-icon purple"><Icons.Sparkles className="w-6 h-6" /></div><div><div className="stat-label">NPS</div><div className="stat-value">{stats.nps}</div></div></div>
                        </div>
                        <div className="card" style={{ padding: '1rem 1.25rem', marginBottom: '1rem' }}>
                            <h3 style={{ margin: '0 0 0.75rem', fontSize: '0.95rem' }}>Évolution des scores</h3>
                            <NpsSparkline data={timeline} />
                        </div>
                        <div className="card" style={{ padding: '1rem 1.25rem' }}>
                            <h3 style={{ margin: '0 0 0.75rem', fontSize: '0.95rem' }}>Derniers retours</h3>
                            <NpsSurveysTable surveys={surveys} />
                        </div>
                    </>
                )}
            </div>
        </>
    );
};

function buildNpsTimeline(surveys) {
    const points = (surveys || [])
        .filter(s => s.score != null && s.created_at)
        .map(s => ({ t: parseUtcDate(s.created_at).getTime(), v: Number(s.score) }))
        .sort((a, b) => a.t - b.t);
    return points;
}

const NpsSparkline = ({ data }) => {
    if (!data || data.length < 2) {
        return <p style={{ color: 'var(--text-muted)', fontSize: '0.85rem', margin: 0 }}>Pas assez de données pour afficher une tendance.</p>;
    }
    const w = 720, h = 140, pad = 24;
    const minT = data[0].t, maxT = data[data.length - 1].t;
    const minV = 1, maxV = 5;
    const x = (t) => pad + ((t - minT) / Math.max(1, (maxT - minT))) * (w - 2 * pad);
    const y = (v) => h - pad - ((v - minV) / (maxV - minV)) * (h - 2 * pad);
    const path = data.map((p, i) => `${i === 0 ? 'M' : 'L'}${x(p.t).toFixed(1)},${y(p.v).toFixed(1)}`).join(' ');
    return (
        <svg viewBox={`0 0 ${w} ${h}`} width="100%" height="140" preserveAspectRatio="none" style={{ display: 'block' }}>
            <line x1={pad} y1={y(3)} x2={w - pad} y2={y(3)} stroke="#e2e8f0" strokeDasharray="3 3" />
            <path d={path} fill="none" stroke="var(--primary)" strokeWidth="2" />
            {data.map((p, i) => (
                <circle key={i} cx={x(p.t)} cy={y(p.v)} r="2.5" fill="var(--primary)" />
            ))}
            <text x={pad} y={h - 4} fontSize="10" fill="#64748b">{new Date(minT).toLocaleDateString('fr-CH')}</text>
            <text x={w - pad} y={h - 4} fontSize="10" fill="#64748b" textAnchor="end">{new Date(maxT).toLocaleDateString('fr-CH')}</text>
        </svg>
    );
};

// ==================== VOICE CLONES VIEW ====================
const VoiceClonesView = () => {
    const { notify } = useApp();
    const lang = useLang();
    const [items, setItems] = useState([]);
    const [loading, setLoading] = useState(true);
    const [show, setShow] = useState(false);
    const [draft, setDraft] = useState({ label: '', provider: 'elevenlabs', sample_url: '', language: 'fr', api_key: '' });
    const [saving, setSaving] = useState(false);

    const load = useCallback(async () => {
        setLoading(true);
        try {
            const r = await api.get('/my/voice-clones');
            setItems(r?.voice_clones || r?.clones || []);
        } catch (e) { notify.error('Erreur'); }
        finally { setLoading(false); }
    }, [notify]);

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

    const create = async () => {
        if (!draft.label.trim() || !draft.sample_url.trim()) return notify.error('Libellé et sample_url requis');
        setSaving(true);
        try {
            const r = await api.post('/my/voice-clones', draft);
            if (r?.error) return notify.error(r.error);
            notify.success('Voix créée');
            setShow(false);
            setDraft({ label: '', provider: 'elevenlabs', sample_url: '', language: 'fr', api_key: '' });
            load();
        } catch (e) { notify.error('Erreur: ' + e.message); }
        finally { setSaving(false); }
    };

    const remove = async (v) => {
        if (!confirm(`Supprimer la voix "${v.label}" ?`)) return;
        try {
            const r = await api.del(`/my/voice-clones/${v.id}`);
            if (r?.error) notify.error(r.error);
            else { notify.success('Supprimée'); load(); }
        } catch (e) { notify.error('Erreur'); }
    };

    const statusBadge = (s) => {
        const map = { ready: ['#dcfce7', '#15803d'], training: ['#fef3c7', '#854d0e'], failed: ['#fee2e2', '#b91c1c'], pending: ['#e0e7ff', '#4338ca'] };
        const [bg, fg] = map[s] || ['#f1f5f9', '#475569'];
        return <span style={{ background: bg, color: fg, padding: '0.2rem 0.6rem', borderRadius: 999, fontSize: '0.75rem', fontWeight: 600 }}>{s || '-'}</span>;
    };

    return (
        <>
            <div className="page-header">
                <div>
                    <h1 className="page-title">{t('nav.voice_clones', lang)}</h1>
                    <p className="page-subtitle">Créez vos propres voix synthétiques sur mesure pour vos bots vocaux, propulsées par la technologie Vocal.</p>
                </div>
                <div className="page-actions">
                    <button className="btn btn-secondary btn-sm" onClick={() => {
                        const tok = localStorage.getItem('vocal_my_token') || '';
                        window.open(`https://studio.helvia.app/?token=${encodeURIComponent(tok)}#voices`, '_blank', 'noopener');
                    }} title="Ouvrir dans la console dédiée Voice Studio (recorder, A/B compare, marketplace)">
                        <Icons.Sparkles className="w-4 h-4" /> Voice Studio
                    </button>
                    <button className="btn btn-secondary btn-sm" onClick={load}><Icons.Refresh className="w-4 h-4" /> Actualiser</button>
                    <button className="btn btn-primary btn-sm" onClick={() => setShow(true)}><Icons.Plus className="w-4 h-4" /> Ajouter une voix</button>
                </div>
            </div>
            <div className="page-content">
                <div className="card-flat" style={{ marginBottom: '1rem', background: 'linear-gradient(135deg, #fdfdff, #f5f3ff)', borderColor: 'var(--primary-light)' }}>
                    <div className="card-flat-content" style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
                        <Icons.Mic className="w-5 h-5" style={{ color: 'var(--primary)' }} />
                        <div style={{ flex: 1, fontSize: '0.85rem' }}>
                            <strong>Nouveau : Voice Studio</strong> — enregistrez vos échantillons directement dans le navigateur (recorder + waveform), comparez 2 voix en A/B, accédez au marketplace de voix premium suisses.
                        </div>
                        <button className="btn btn-primary btn-sm" onClick={() => {
                            const tok = localStorage.getItem('vocal_my_token') || '';
                            window.open(`https://studio.helvia.app/?token=${encodeURIComponent(tok)}#recorder`, '_blank', 'noopener');
                        }}>
                            Ouvrir le Recorder <Icons.ChevronRight className="w-4 h-4" />
                        </button>
                    </div>
                </div>
                {show && (
                    <div className="card" style={{ padding: '1rem', marginBottom: '1rem', border: '2px solid var(--primary-light)' }}>
                        <h3 style={{ margin: '0 0 0.75rem' }}>Nouvelle voix</h3>
                        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '0.75rem' }}>
                            <div className="form-row"><label>Libellé *</label><input className="form-input" value={draft.label} onChange={e => setDraft({ ...draft, label: e.target.value })} placeholder="Ex: Voix Marie" /></div>
                            <div className="form-row"><label>Gamme</label>
                                <select className="form-input" value={draft.provider} onChange={e => setDraft({ ...draft, provider: e.target.value })}>
                                    <option value="elevenlabs">Vocal Premium (clonage haute fidélité)</option>
                                    <option value="openai">Vocal Standard (clonage rapide)</option>
                                </select>
                            </div>
                            <div className="form-row" style={{ gridColumn: '1 / -1' }}><label>URL d'un échantillon audio (mp3/wav)</label><input className="form-input" value={draft.sample_url} onChange={e => setDraft({ ...draft, sample_url: e.target.value })} placeholder="https://r2.helvia.app/..." /></div>
                            <div className="form-row"><label>Langue</label>
                                <select className="form-input" value={draft.language} onChange={e => setDraft({ ...draft, language: e.target.value })}>
                                    <option value="fr">Français</option>
                                    <option value="de">Deutsch</option>
                                    <option value="en">English</option>
                                    <option value="it">Italiano</option>
                                </select>
                            </div>
                            <div className="form-row"><label>Clé d'activation (compte avancé)</label><input className="form-input" value={draft.api_key} onChange={e => setDraft({ ...draft, api_key: e.target.value })} placeholder="optionnel" /></div>
                        </div>
                        <div style={{ display: 'flex', gap: '0.5rem', justifyContent: 'flex-end', marginTop: '0.75rem' }}>
                            <button className="btn btn-secondary" onClick={() => setShow(false)}>{t('common.cancel', lang)}</button>
                            <button className="btn btn-primary" onClick={create} disabled={saving}>{saving ? '…' : t('common.save', lang)}</button>
                        </div>
                    </div>
                )}
                {loading ? <p>{t('common.loading', lang)}</p> : items.length === 0 ? (
                    <div className="card" style={{ padding: '3rem 2rem', textAlign: 'center', color: 'var(--text-muted)' }}>
                        <Icons.Sparkles className="w-10 h-10" style={{ margin: '0 auto 0.75rem', opacity: 0.4 }} />
                        <p style={{ margin: 0 }}>{t('common.empty', lang)}</p>
                    </div>
                ) : (
                    <div className="card">
                        <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '0.875rem' }}>
                            <thead><tr style={{ background: '#f8fafc', textAlign: 'left' }}>
                                <th style={{ padding: '0.65rem 1rem' }}>Libellé</th>
                                <th>Fournisseur</th>
                                <th>Langue</th>
                                <th>Statut</th>
                                <th>Créé le</th>
                                <th></th>
                            </tr></thead>
                            <tbody>{items.map(v => (
                                <tr key={v.id} style={{ borderTop: '1px solid var(--border)' }}>
                                    <td style={{ padding: '0.6rem 1rem', fontWeight: 600 }}>{v.label}</td>
                                    <td>{v.provider}</td>
                                    <td>{v.language || '-'}</td>
                                    <td>{statusBadge(v.status)}</td>
                                    <td style={{ color: 'var(--text-muted)' }}>{v.created_at ? parseUtcDate(v.created_at)?.toLocaleDateString('fr-CH') : '-'}</td>
                                    <td><button className="btn btn-secondary btn-sm" onClick={() => remove(v)} style={{ color: '#dc2626' }}><Icons.Trash className="w-4 h-4" /></button></td>
                                </tr>
                            ))}</tbody>
                        </table>
                    </div>
                )}
            </div>
        </>
    );
};

// ==================== STUDIO VOIX (TTS on-demand → MP3) ====================
const StudioView = () => {
    const { notify } = useApp();
    const lang = useLang();
    const [text, setText] = useState('');
    const [label, setLabel] = useState('');
    const [provider, setProvider] = useState('openai');
    const [model, setModel] = useState('tts-1');
    const [voice, setVoice] = useState('alloy');
    const [voiceCloneId, setVoiceCloneId] = useState('');
    const [language, setLanguage] = useState('fr');
    const [format, setFormat] = useState('mp3');
    const [speed, setSpeed] = useState(1.0);
    const [voicesData, setVoicesData] = useState({ openai: { voices: [], models: [] }, elevenlabs: { available: false }, custom: [] });
    const [items, setItems] = useState([]);
    const [loading, setLoading] = useState(true);
    const [generating, setGenerating] = useState(false);
    const [lastGenerated, setLastGenerated] = useState(null);

    const loadVoices = useCallback(async () => {
        try {
            const r = await api.get('/my/tts/voices');
            if (r) setVoicesData(r);
        } catch (e) {}
    }, []);

    const loadList = useCallback(async () => {
        setLoading(true);
        try {
            const r = await api.get('/my/tts');
            setItems(r?.recordings || []);
        } catch (e) { notify.error('Erreur chargement'); }
        finally { setLoading(false); }
    }, [notify]);

    useEffect(() => { loadVoices(); loadList(); }, [loadVoices, loadList]);

    const generate = async () => {
        const txt = text.trim();
        if (!txt) return notify.error('Texte requis');
        if (txt.length > 4000) return notify.error('Texte trop long (max 4000 caractères)');
        setGenerating(true);
        try {
            const payload = {
                text: txt,
                label: label.trim() || txt.slice(0, 60),
                provider,
                model,
                language,
                format,
                speed: parseFloat(speed) || 1.0,
            };
            if (voiceCloneId) payload.voice_clone_id = parseInt(voiceCloneId);
            else payload.voice = voice;
            const r = await api.post('/my/tts', payload);
            if (r?.error) return notify.error(r.error + (r.detail ? ` - ${r.detail}` : ''));
            notify.success('Audio généré');
            setLastGenerated(r);
            loadList();
        } catch (e) {
            notify.error('Erreur: ' + e.message);
        } finally { setGenerating(false); }
    };

    const remove = async (rec) => {
        if (!confirm(`Supprimer "${rec.label}" ?`)) return;
        try {
            const r = await api.del(`/my/tts/${rec.id}`);
            if (r?.error) notify.error(r.error);
            else { notify.success('Supprimé'); loadList(); if (lastGenerated?.id === rec.id) setLastGenerated(null); }
        } catch (e) { notify.error('Erreur'); }
    };

    const copyUrl = (url) => {
        navigator.clipboard?.writeText(url).then(() => notify.success('URL copiée'));
    };

    const useExamples = [
        { l: 'Message d\'accueil', t: "Bonjour et bienvenue chez Vocal. Pour rejoindre les ventes, dites 'ventes'. Pour le support, dites 'support'." },
        { l: 'Message de vacances', t: "Notre bureau est fermé du 23 décembre au 5 janvier. Laissez un message après le bip et nous vous rappellerons dès notre retour." },
        { l: 'Renvoi sur mobile', t: "Merci de patienter, nous vous transférons vers un conseiller." },
        { l: 'Hors horaires', t: "Bonjour, nos bureaux sont actuellement fermés. Nos horaires sont du lundi au vendredi, de 8h à 18h." },
    ];

    return (
        <>
            <div className="page-header">
                <div>
                    <h1 className="page-title">{t('nav.studio', lang)}</h1>
                    <p className="page-subtitle">Générez un MP3 à partir d'un texte. Réutilisable partout : accueil, vacances, IVR, voicemail, hold.</p>
                </div>
                <div className="page-actions">
                    <button className="btn btn-secondary btn-sm" onClick={() => {
                        const tok = localStorage.getItem('vocal_my_token') || '';
                        window.open(`https://studio.helvia.app/?token=${encodeURIComponent(tok)}#composer`, '_blank', 'noopener');
                    }} title="Ouvrir Voice Studio (Composer + presets + A/B + library complète)">
                        <Icons.Sparkles className="w-4 h-4" /> Voice Studio
                    </button>
                    <button className="btn btn-secondary btn-sm" onClick={loadList}><Icons.Refresh className="w-4 h-4" /> Actualiser</button>
                </div>
            </div>
            <div className="page-content">
                <div className="card-flat" style={{ marginBottom: '1rem', background: 'linear-gradient(135deg, #fdfdff, #f5f3ff)', borderColor: 'var(--primary-light)' }}>
                    <div className="card-flat-content" style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
                        <Icons.Sparkles className="w-5 h-5" style={{ color: 'var(--primary)' }} />
                        <div style={{ flex: 1, fontSize: '0.85rem' }}>
                            <strong>Besoin de plus de contrôle ?</strong> Voice Studio propose des presets (accueil, IVR, voicemail…), un A/B compare entre 2 voix, un dictionnaire de prononciation et un marketplace de voix premium suisses.
                        </div>
                        <button className="btn btn-primary btn-sm" onClick={() => {
                            const tok = localStorage.getItem('vocal_my_token') || '';
                            window.open(`https://studio.helvia.app/?token=${encodeURIComponent(tok)}#composer`, '_blank', 'noopener');
                        }}>
                            Ouvrir Voice Studio <Icons.ChevronRight className="w-4 h-4" />
                        </button>
                    </div>
                </div>
                <div className="card" style={{ padding: '1.25rem', marginBottom: '1rem' }}>
                    <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem' }}>
                        <div className="form-row" style={{ gridColumn: '1 / -1' }}>
                            <label>Texte à dire ({text.length}/4000)</label>
                            <textarea
                                className="form-input"
                                value={text}
                                onChange={e => setText(e.target.value)}
                                rows={5}
                                maxLength={4000}
                                placeholder="Bonjour, vous êtes bien chez…"
                                style={{ fontFamily: 'inherit', resize: 'vertical' }}
                            />
                            <div style={{ display: 'flex', gap: '0.4rem', flexWrap: 'wrap', marginTop: '0.4rem' }}>
                                {useExamples.map((ex, i) => (
                                    <button key={i} type="button" className="btn btn-secondary btn-sm"
                                        onClick={() => { setText(ex.t); if (!label) setLabel(ex.l); }}
                                        style={{ fontSize: '0.75rem' }}>{ex.l}</button>
                                ))}
                            </div>
                        </div>

                        <div className="form-row">
                            <label>Libellé (optionnel)</label>
                            <input className="form-input" value={label} onChange={e => setLabel(e.target.value)} placeholder="Ex: Accueil semaine" />
                        </div>
                        <div className="form-row">
                            <label>Langue</label>
                            <select className="form-input" value={language} onChange={e => setLanguage(e.target.value)}>
                                <option value="fr">Français</option>
                                <option value="de">Deutsch</option>
                                <option value="en">English</option>
                                <option value="it">Italiano</option>
                            </select>
                        </div>

                        <div className="form-row">
                            <label>Voix custom (clone)</label>
                            <select className="form-input" value={voiceCloneId}
                                onChange={e => {
                                    setVoiceCloneId(e.target.value);
                                    if (e.target.value) {
                                        const c = voicesData.custom.find(v => String(v.id) === e.target.value);
                                        if (c) setProvider(c.provider);
                                    }
                                }}>
                                <option value="">- Aucune (voix standard) -</option>
                                {voicesData.custom.map(v => {
                                    const tier = v.provider === 'elevenlabs' ? 'Premium' : 'Standard';
                                    return <option key={v.id} value={v.id}>{v.label} (Vocal {tier})</option>;
                                })}
                            </select>
                        </div>
                        <div className="form-row">
                            <label>Gamme</label>
                            <select className="form-input" value={provider} disabled={!!voiceCloneId}
                                onChange={e => {
                                    setProvider(e.target.value);
                                    if (e.target.value === 'elevenlabs') setModel('eleven_multilingual_v2');
                                    else { setModel('tts-1'); setVoice('alloy'); }
                                }}>
                                <option value="openai">Vocal Standard (intégré)</option>
                                <option value="elevenlabs" disabled={!voicesData.elevenlabs?.available}>
                                    Vocal Premium {voicesData.elevenlabs?.available ? '' : '(non activé)'}
                                </option>
                            </select>
                        </div>

                        {!voiceCloneId && provider === 'openai' && (
                            <>
                                <div className="form-row">
                                    <label>Voix</label>
                                    <select className="form-input" value={voice} onChange={e => setVoice(e.target.value)}>
                                        {(voicesData.openai?.voices || []).map(v => <option key={v} value={v}>{v}</option>)}
                                    </select>
                                </div>
                                <div className="form-row">
                                    <label>Qualité</label>
                                    <select className="form-input" value={model} onChange={e => setModel(e.target.value)}>
                                        {(voicesData.openai?.models || []).map(m => <option key={m} value={m}>{m}</option>)}
                                    </select>
                                </div>
                            </>
                        )}
                        {!voiceCloneId && provider === 'elevenlabs' && (
                            <div className="form-row" style={{ gridColumn: '1 / -1' }}>
                                <label>Identifiant de voix Vocal Premium</label>
                                <input className="form-input" value={voice} onChange={e => setVoice(e.target.value)} placeholder="Ex: identifiant fourni par votre conseiller" />
                                <small style={{ color: 'var(--text-muted)' }}>Disponible depuis votre catalogue Vocal Premium ou auprès de votre conseiller.</small>
                            </div>
                        )}

                        <div className="form-row">
                            <label>Format</label>
                            <select className="form-input" value={format} onChange={e => setFormat(e.target.value)}>
                                <option value="mp3">MP3</option>
                                <option value="wav">WAV</option>
                                <option value="ogg">OGG</option>
                            </select>
                        </div>
                        <div className="form-row">
                            <label>Vitesse: {speed}x</label>
                            <input type="range" min="0.5" max="2" step="0.05" value={speed} onChange={e => setSpeed(e.target.value)} />
                        </div>
                    </div>

                    <div style={{ display: 'flex', gap: '0.5rem', justifyContent: 'flex-end', marginTop: '1rem', borderTop: '1px solid var(--border)', paddingTop: '1rem' }}>
                        <button className="btn btn-primary" onClick={generate} disabled={generating || !text.trim()}>
                            {generating ? 'Génération…' : '🎙️ Générer le MP3'}
                        </button>
                    </div>

                    {lastGenerated && lastGenerated.file_url && (
                        <div style={{ marginTop: '1rem', padding: '1rem', background: '#f0fdf4', border: '1px solid #86efac', borderRadius: '0.5rem' }}>
                            <div style={{ fontWeight: 600, marginBottom: '0.5rem', color: '#15803d' }}>✓ {lastGenerated.label}</div>
                            <audio controls src={lastGenerated.file_url} style={{ width: '100%', marginBottom: '0.5rem' }} />
                            <div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap', alignItems: 'center', fontSize: '0.85rem' }}>
                                <code style={{ background: '#fff', padding: '0.3rem 0.5rem', borderRadius: 4, flex: 1, wordBreak: 'break-all' }}>{lastGenerated.file_url}</code>
                                <button className="btn btn-secondary btn-sm" onClick={() => copyUrl(lastGenerated.file_url)}>📋 Copier l'URL</button>
                                <a className="btn btn-secondary btn-sm" href={lastGenerated.file_url} download={`${lastGenerated.label}.${lastGenerated.format}`}>⬇ Télécharger</a>
                            </div>
                            <div style={{ marginTop: '0.5rem', fontSize: '0.75rem', color: 'var(--text-muted)' }}>
                                {lastGenerated.char_count} caractères · ≈ {(lastGenerated.cost_chf || 0).toFixed(3)} CHF · {(lastGenerated.file_size / 1024).toFixed(1)} KB
                            </div>
                        </div>
                    )}
                </div>

                <div className="card" style={{ padding: '1rem' }}>
                    <h3 style={{ margin: '0 0 0.75rem' }}>Mes enregistrements</h3>
                    {loading ? <p>{t('common.loading', lang)}</p> : items.length === 0 ? (
                        <div style={{ padding: '2rem', textAlign: 'center', color: 'var(--text-muted)' }}>
                            <p>{t('common.empty', lang)}</p>
                        </div>
                    ) : (
                        <div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
                            {items.map(rec => (
                                <div key={rec.id} style={{ border: '1px solid var(--border)', borderRadius: '0.5rem', padding: '0.75rem', background: '#fff' }}>
                                    <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '0.4rem', flexWrap: 'wrap', gap: '0.5rem' }}>
                                        <div>
                                            <div style={{ fontWeight: 600 }}>{rec.label}</div>
                                            <div style={{ fontSize: '0.75rem', color: 'var(--text-muted)' }}>
                                                {rec.provider} · {rec.voice || '-'} · {rec.language} · {rec.char_count} car. · {parseUtcDate(rec.created_at)?.toLocaleString('fr-CH') || '-'}
                                            </div>
                                        </div>
                                        <div style={{ display: 'flex', gap: '0.4rem' }}>
                                            <button className="btn btn-secondary btn-sm" onClick={() => copyUrl(rec.file_url)} title="Copier l'URL">📋</button>
                                            <a className="btn btn-secondary btn-sm" href={rec.file_url} download={`${rec.label}.${rec.format}`} title="Télécharger">⬇</a>
                                            <button className="btn btn-secondary btn-sm" style={{ color: '#dc2626' }} onClick={() => remove(rec)} title="Supprimer"><Icons.Trash className="w-4 h-4" /></button>
                                        </div>
                                    </div>
                                    <audio controls src={rec.file_url} style={{ width: '100%', height: 32 }} />
                                    <details style={{ marginTop: '0.4rem', fontSize: '0.8rem', color: 'var(--text-muted)' }}>
                                        <summary style={{ cursor: 'pointer' }}>Texte</summary>
                                        <p style={{ marginTop: '0.4rem', whiteSpace: 'pre-wrap' }}>{rec.text}</p>
                                    </details>
                                </div>
                            ))}
                        </div>
                    )}
                </div>
            </div>
        </>
    );
};

// ==================== MODULE GATE (affiche une CTA si module non activé) ====================
const ModulesContext = React.createContext({ modules: [], reload: () => {}, loading: false });

const ModulesProvider = ({ children }) => {
    const { user } = useApp();
    const [modules, setModules] = useState([]);
    const [loading, setLoading] = useState(false);
    const reload = useCallback(async () => {
        if (!user) { setModules([]); return; }
        setLoading(true);
        try {
            const r = await api.get('/my/modules');
            setModules(r?.modules || []);
        } catch {} finally { setLoading(false); }
    }, [user]);
    useEffect(() => { reload(); }, [reload]);
    const val = useMemo(() => ({ modules, reload, loading }), [modules, reload, loading]);
    return <ModulesContext.Provider value={val}>{children}</ModulesContext.Provider>;
};
const useModules = () => React.useContext(ModulesContext);
const useModuleEnabled = (code) => {
    const { modules } = useModules();
    return modules.find(m => m.code === code)?.enabled || false;
};

const ModuleGate = ({ code, children }) => {
    const { modules, loading, reload } = useModules();
    const { navigate, notify } = useApp();
    const [activating, setActivating] = useState(false);
    if (loading) return <div className="page-content"><p>Chargement…</p></div>;
    const m = modules.find(x => x.code === code);
    if (m?.enabled) return children;
    const activate = async () => {
        if (!m) return;
        if (m.price_chf > 0) {
            if (!confirm(`Activer "${m.name}" pour ${m.price_chf.toFixed(2)} CHF/${m.billing_period === 'year' ? 'an' : 'mois'} ?\n\nDébité aujourd'hui : ${((m.price_chf || 0) + (m.setup_fee_chf || 0)).toFixed(2)} CHF depuis votre wallet.`)) return;
        }
        setActivating(true);
        try {
            const r = await api.post(`/my/modules/${code}/activate`);
            if (r?.error) {
                if (r.required) notify.error(`Solde insuffisant. Requis: ${r.required.toFixed(2)} CHF`);
                else notify.error(r.error);
            } else {
                notify.success('Module activé');
                reload();
            }
        } finally { setActivating(false); }
    };
    return (
        <div className="page-content" style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '60vh' }}>
            <div className="card" style={{ padding: '2rem 2.5rem', maxWidth: 520, textAlign: 'center', border: '2px dashed var(--border)' }}>
                <Icons.Sparkles className="w-12 h-12" style={{ margin: '0 auto 1rem', color: '#6366f1' }} />
                <h2 style={{ margin: '0 0 0.5rem' }}>{m ? m.name : 'Module requis'}</h2>
                <p style={{ color: 'var(--text-muted)', marginBottom: '1.25rem' }}>
                    {m ? m.description : 'Cette fonctionnalité nécessite l\'activation d\'un module.'}
                </p>
                {m && (
                    <div style={{ background: '#f8fafc', padding: '0.9rem', borderRadius: 8, marginBottom: '1rem' }}>
                        {m.price_chf > 0 ? (
                            <>
                                <div style={{ fontSize: '1.5rem', fontWeight: 700 }}>
                                    {m.price_chf.toFixed(2)} CHF<span style={{ fontSize: '0.85rem', color: 'var(--text-muted)' }}> /{m.billing_period === 'year' ? 'an' : 'mois'}</span>
                                </div>
                                {m.trial_days > 0 && <div style={{ color: '#16a34a', fontWeight: 600 }}>🎁 {m.trial_days} jours gratuits</div>}
                            </>
                        ) : (
                            <div style={{ fontSize: '1.5rem', fontWeight: 700, color: '#16a34a' }}>GRATUIT</div>
                        )}
                    </div>
                )}
                <div style={{ display: 'flex', gap: '0.5rem', justifyContent: 'center' }}>
                    <button className="btn btn-secondary" onClick={() => navigate('modules')}>Voir tous les modules</button>
                    {m && <button className="btn btn-primary" onClick={activate} disabled={activating}>
                        {activating ? '…' : (m.price_chf > 0 ? `Activer pour ${m.price_chf.toFixed(2)} CHF/mois` : 'Activer (gratuit)')}
                    </button>}
                </div>
            </div>
        </div>
    );
};

// ==================== MODULES (catalogue d'options activables) ====================
const ModulesView = () => {
    const { notify, navigate } = useApp();
    const lang = useLang();
    const [items, setItems] = useState([]);
    const [loading, setLoading] = useState(true);
    const [pendingActivate, setPendingActivate] = useState(null);
    const [working, setWorking] = useState(false);

    const load = useCallback(async () => {
        setLoading(true);
        try {
            const r = await api.get('/my/modules');
            setItems(r?.modules || []);
        } catch (e) { notify.error('Erreur chargement modules'); }
        finally { setLoading(false); }
    }, [notify]);

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

    const activate = async (m) => {
        setWorking(true);
        try {
            const r = await api.post(`/my/modules/${m.code}/activate`);
            if (r?.error) {
                if (r.required) {
                    notify.error(`Solde insuffisant. Requis: ${r.required.toFixed(2)} CHF, vous avez ${r.balance.toFixed(2)} CHF.`);
                } else {
                    notify.error(r.error);
                }
            } else {
                if (r.charged_chf > 0) notify.success(`✓ Activé. Débité: ${r.charged_chf.toFixed(2)} CHF`);
                else notify.success('✓ Module activé');
                load();
            }
        } catch (e) { notify.error('Erreur: ' + e.message); }
        finally { setWorking(false); setPendingActivate(null); }
    };

    const deactivate = async (m) => {
        if (!confirm(`Désactiver "${m.name}" ? Vous ne serez plus facturé au prochain renouvellement.`)) return;
        setWorking(true);
        try {
            const r = await api.post(`/my/modules/${m.code}/deactivate`);
            if (r?.error) notify.error(r.error);
            else { notify.success('Module désactivé'); load(); }
        } catch (e) { notify.error('Erreur'); }
        finally { setWorking(false); }
    };

    const grouped = useMemo(() => {
        const map = {};
        for (const m of items) {
            const cat = m.category || 'general';
            if (!map[cat]) map[cat] = [];
            map[cat].push(m);
        }
        return map;
    }, [items]);

    const categoryLabels = {
        ai: '🤖 Intelligence artificielle',
        communication: '💬 Communication',
        crm: '👥 CRM & Contacts',
        monitoring: '📊 Monitoring & Live',
        general: '⚙️ Général',
    };

    const totalMonthly = items.filter(m => m.enabled && !m.in_trial).reduce((s, m) => s + (m.price_paid_chf || m.price_chf || 0), 0);

    return (
        <>
            <div className="page-header">
                <div>
                    <h1 className="page-title">{t('nav.modules', lang)}</h1>
                    <p className="page-subtitle">Activez les fonctionnalités dont vous avez besoin. Désactivables à tout moment.</p>
                </div>
                <div className="page-actions">
                    <div style={{ background: '#eef2ff', color: '#4338ca', padding: '0.5rem 0.9rem', borderRadius: 8, fontWeight: 600 }}>
                        Total mensuel: {totalMonthly.toFixed(2)} CHF
                    </div>
                </div>
            </div>
            <div className="page-content">
                {loading ? <p>{t('common.loading', lang)}</p> : items.length === 0 ? (
                    <div className="card" style={{ padding: '3rem 2rem', textAlign: 'center', color: 'var(--text-muted)' }}>
                        <p>Aucun module disponible.</p>
                    </div>
                ) : Object.entries(grouped).map(([cat, mods]) => (
                    <div key={cat} style={{ marginBottom: '1.5rem' }}>
                        <h2 style={{ fontSize: '1.05rem', margin: '0 0 0.6rem', color: 'var(--text-muted)', fontWeight: 600 }}>
                            {categoryLabels[cat] || cat}
                        </h2>
                        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(320px, 1fr))', gap: '0.75rem' }}>
                            {mods.map(m => (
                                <div key={m.code} className="card" style={{
                                    padding: '1rem',
                                    border: m.enabled ? '2px solid #16a34a' : '1px solid var(--border)',
                                    background: m.enabled ? '#f0fdf4' : '#fff',
                                    position: 'relative',
                                }}>
                                    {m.enabled && (
                                        <div style={{ position: 'absolute', top: 8, right: 8, background: '#16a34a', color: '#fff',
                                            padding: '0.15rem 0.5rem', borderRadius: 999, fontSize: '0.7rem', fontWeight: 700 }}>
                                            {m.in_trial ? `ESSAI (${m.trial_until ? new Date(m.trial_until).toLocaleDateString('fr-CH') : ''})` : 'ACTIF'}
                                        </div>
                                    )}
                                    <div style={{ fontWeight: 700, fontSize: '1.05rem', marginBottom: '0.25rem' }}>{m.name}</div>
                                    <div style={{ fontSize: '0.85rem', color: 'var(--text-muted)', minHeight: 38, marginBottom: '0.6rem' }}>
                                        {m.description}
                                    </div>
                                    <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: '0.5rem' }}>
                                        <div>
                                            {m.price_chf > 0 ? (
                                                <div>
                                                    <span style={{ fontSize: '1.4rem', fontWeight: 700, color: '#0f172a' }}>{m.price_chf.toFixed(2)} CHF</span>
                                                    <span style={{ color: 'var(--text-muted)', marginLeft: 4 }}>/{m.billing_period === 'year' ? 'an' : 'mois'}</span>
                                                    {m.setup_fee_chf > 0 && <div style={{ fontSize: '0.75rem', color: 'var(--text-muted)' }}>+ {m.setup_fee_chf.toFixed(2)} CHF activation</div>}
                                                    {m.trial_days > 0 && !m.enabled && <div style={{ fontSize: '0.75rem', color: '#16a34a', fontWeight: 600 }}>🎁 {m.trial_days} jours gratuits</div>}
                                                </div>
                                            ) : (
                                                <span style={{ fontWeight: 700, color: '#16a34a' }}>GRATUIT</span>
                                            )}
                                        </div>
                                        <div>
                                            {m.enabled ? (
                                                <button className="btn btn-secondary btn-sm" disabled={working} onClick={() => deactivate(m)}>
                                                    Désactiver
                                                </button>
                                            ) : (
                                                <button className="btn btn-primary btn-sm" disabled={working}
                                                    onClick={() => m.price_chf > 0 ? setPendingActivate(m) : activate(m)}>
                                                    {m.price_chf > 0 ? 'Activer' : 'Activer (gratuit)'}
                                                </button>
                                            )}
                                        </div>
                                    </div>
                                    {m.enabled && m.expires_at && (
                                        <div style={{ marginTop: '0.5rem', fontSize: '0.75rem', color: 'var(--text-muted)' }}>
                                            Renouvellement le {new Date(m.expires_at).toLocaleDateString('fr-CH')}
                                            {m.total_paid_chf > 0 && ` · Total payé: ${m.total_paid_chf.toFixed(2)} CHF`}
                                        </div>
                                    )}
                                </div>
                            ))}
                        </div>
                    </div>
                ))}
            </div>

            {pendingActivate && (
                <div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.5)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1000 }}
                     onClick={() => setPendingActivate(null)}>
                    <div className="card" style={{ padding: '1.5rem', maxWidth: 460, width: '90%', background: '#fff' }} onClick={e => e.stopPropagation()}>
                        <h3 style={{ margin: '0 0 0.75rem' }}>Activer "{pendingActivate.name}" ?</h3>
                        <p style={{ marginBottom: '1rem', color: 'var(--text-muted)' }}>{pendingActivate.description}</p>
                        <div style={{ background: '#f8fafc', padding: '0.75rem', borderRadius: 6, marginBottom: '1rem' }}>
                            <div>Prix mensuel : <strong>{pendingActivate.price_chf.toFixed(2)} CHF</strong></div>
                            {pendingActivate.setup_fee_chf > 0 && <div>Frais d'activation : <strong>{pendingActivate.setup_fee_chf.toFixed(2)} CHF</strong></div>}
                            {pendingActivate.trial_days > 0 ? (
                                <div style={{ color: '#16a34a', fontWeight: 600, marginTop: '0.4rem' }}>
                                    🎁 Essai gratuit de {pendingActivate.trial_days} jours - débit aujourd'hui : {(pendingActivate.setup_fee_chf || 0).toFixed(2)} CHF
                                </div>
                            ) : (
                                <div style={{ marginTop: '0.4rem' }}>
                                    Débit aujourd'hui : <strong>{((pendingActivate.price_chf || 0) + (pendingActivate.setup_fee_chf || 0)).toFixed(2)} CHF</strong> (depuis votre wallet)
                                </div>
                            )}
                        </div>
                        <p style={{ fontSize: '0.8rem', color: 'var(--text-muted)' }}>
                            Renouvellement automatique chaque mois. Désactivable à tout moment depuis cette page.
                        </p>
                        <div style={{ display: 'flex', gap: '0.5rem', justifyContent: 'flex-end', marginTop: '1rem' }}>
                            <button className="btn btn-secondary" onClick={() => setPendingActivate(null)}>Annuler</button>
                            <button className="btn btn-primary" disabled={working} onClick={() => activate(pendingActivate)}>
                                {working ? '…' : 'Confirmer l\'activation'}
                            </button>
                        </div>
                    </div>
                </div>
            )}
        </>
    );
};

// ==================== IMPERSONATION BANNER ====================
const ImpersonationBanner = () => {
    const { user, logout } = useApp();
    const isImpersonating = (() => {
        try { return sessionStorage.getItem('vocal_my_impersonating') === '1'; }
        catch (e) { return false; }
    })();
    if (!isImpersonating) return null;
    const exit = () => {
        try { sessionStorage.removeItem('vocal_my_impersonating'); } catch (e) {}
        logout();
    };
    return (
        <div style={{
            background: 'linear-gradient(135deg, #f59e0b, #ef4444)',
            color: '#fff', padding: '0.5rem 1rem', display: 'flex',
            alignItems: 'center', justifyContent: 'space-between', gap: '1rem',
            fontSize: '0.8125rem', fontWeight: 600, letterSpacing: '0.01em',
            boxShadow: '0 2px 6px rgba(0,0,0,0.08)', zIndex: 100,
        }}>
            <span>👁️ Mode admin - connecté en tant que <strong>{user?.email || user?.client?.email}</strong></span>
            <button onClick={exit} style={{
                background: 'rgba(255,255,255,0.2)', color: '#fff', border: '1px solid rgba(255,255,255,0.4)',
                borderRadius: '999px', padding: '0.25rem 0.875rem', fontWeight: 600, cursor: 'pointer',
                fontSize: '0.75rem',
            }}>Quitter l'impersonation</button>
        </div>
    );
};

// ==================== APP ROOT ====================
const App = () => {
    const { user, view, refreshProfile } = useApp();
    useEffect(() => { if (user) refreshProfile(); }, []);

    if (!user) return <><OfflineOverlay /><LoginScreen /></>;

    // Map view → minLevel. Si view au-dessus du niveau client, on rabat sur dashboard.
    const VIEW_MIN_LEVEL = {
        'whatsapp': 1, 'bots': 2, 'voice-clones': 3, 'studio': 3,
        'conversations': 2, 'nps': 3, 'proxy': 2,
        'modules': 2, 'porting': 2, 'api-keys': 1,
    };
    const clientLevel = user?.client?.level ?? 1;
    const requiredLevel = VIEW_MIN_LEVEL[view];
    const effectiveView = (requiredLevel && clientLevel < requiredLevel) ? 'dashboard' : view;

    return (
        <div className="app-layout">
            <OfflineOverlay />
            <Sidebar />
            <div className="main-content">
                <ImpersonationBanner />
                <MobileHeader />
                {effectiveView === 'dashboard' && <DashboardView />}
                {effectiveView === 'lines' && <LinesView />}
                {effectiveView === 'line-detail' && <LineDetailView />}
                {effectiveView === 'calls' && <CallsView />}
                {effectiveView === 'sms' && <MessagesView kind="sms" />}
                {effectiveView === 'callback-requests'           && <CallbackRequestsView initialStatus="pending" />}
                {effectiveView === 'callback-requests-pending'   && <CallbackRequestsView initialStatus="pending" />}
                {effectiveView === 'callback-requests-done'      && <CallbackRequestsView initialStatus="done" />}
                {effectiveView === 'callback-requests-cancelled' && <CallbackRequestsView initialStatus="cancelled" />}
                {effectiveView === 'callback-requests-all'       && <CallbackRequestsView initialStatus="" />}
                {effectiveView === 'whatsapp'          && <WhatsappRouter subview="inbox" />}
                {effectiveView === 'whatsapp-inbox'    && <WhatsappRouter subview="inbox" />}
                {effectiveView === 'whatsapp-messages' && <WhatsappRouter subview="messages" />}
                {effectiveView === 'whatsapp-senders'  && <WhatsappRouter subview="senders" />}
                {effectiveView === 'ai-agents' && <AiAgentsView />}
                {effectiveView === 'widgets' && <WidgetsView />}
                {effectiveView === 'bots' && <BotsView />}
                {effectiveView === 'contacts' && <ContactsView />}
                {effectiveView === 'conversations' && <ModuleGate code="whatsapp"><ConversationsView /></ModuleGate>}
                {effectiveView === 'nps' && <ModuleGate code="nps"><NpsView /></ModuleGate>}
                {effectiveView === 'voice-clones' && <ModuleGate code="voice_clones"><VoiceClonesView /></ModuleGate>}
                {effectiveView === 'studio' && <ModuleGate code="tts_studio"><StudioView /></ModuleGate>}
                {effectiveView === 'modules' && <ModulesView />}
                {effectiveView === 'proxy' && <ProxyNumbersView />}
                {effectiveView === 'spam' && <SpamView />}
                {effectiveView === 'billing' && <BillingView />}
                {effectiveView === 'subscriptions' && <SubscriptionsView />}
                {effectiveView === 'porting' && <PortingView />}
                {effectiveView === 'api-keys' && <ApiKeysView />}
                {effectiveView === 'sip' && <SipConfigView />}
                {effectiveView === 'settings' && <SettingsView />}
            </div>
        </div>
    );
};

// ============================================================================
// AGENTS IA - vue unifiée par ligne (voix + WhatsApp partagent la même config).
// Liste à gauche, détail à droite avec édition ai_name / ai_greeting /
// voice_ai_prompt / ai_knowledge + bouton "Apprendre des messages humains".
// ============================================================================
// ==================== SIP / WILDIX CONFIG VIEW ====================
// Liste les lignes du client avec leur config SIP éventuelle, et résume
// ce que le technicien doit configurer côté PBX (IPs Twilio, codecs, etc.).
const TWILIO_SIP_GATEWAYS = [
    { region: 'Europe Ireland (ie1)',     range: '54.171.127.192/30',  active: true,  ips: ['54.171.127.192', '54.171.127.193', '54.171.127.194', '54.171.127.195'] },
    { region: 'Europe Frankfurt (de1)',   range: '35.156.191.128/30',  active: true,  ips: ['35.156.191.128', '35.156.191.129', '35.156.191.130', '35.156.191.131'] },
    { region: 'North America Virginia (us1)', range: '54.172.60.0/30', active: false, ips: ['54.172.60.0', '54.172.60.1', '54.172.60.2', '54.172.60.3'] },
    { region: 'North America Oregon (us2)',   range: '54.244.51.0/30', active: false, ips: ['54.244.51.0', '54.244.51.1', '54.244.51.2', '54.244.51.3'] },
    { region: 'Asia-Pacific Tokyo (jp1)',     range: '54.65.63.192/30',     active: false, ips: ['54.65.63.192', '54.65.63.193', '54.65.63.194', '54.65.63.195'] },
    { region: 'Asia-Pacific Singapore (sg1)', range: '54.169.127.128/30',   active: false, ips: ['54.169.127.128', '54.169.127.129', '54.169.127.130', '54.169.127.131'] },
    { region: 'Asia-Pacific Sydney (au1)',    range: '54.252.254.64/30',    active: false, ips: ['54.252.254.64', '54.252.254.65', '54.252.254.66', '54.252.254.67'] },
    { region: 'South America São Paulo (br1)',range: '177.71.206.192/30',   active: false, ips: ['177.71.206.192', '177.71.206.193', '177.71.206.194', '177.71.206.195'] },
];

const SipConfigView = () => {
    const { notify } = useApp();
    const [lines, setLines] = useState([]);
    const [sipMap, setSipMap] = useState({}); // line_id -> sip config
    const [loading, setLoading] = useState(true);

    const load = useCallback(async () => {
        setLoading(true);
        try {
            const r = await api.get('/my/lines');
            const list = r?.lines || [];
            setLines(list);
            // Charge la config SIP de chaque ligne en parallèle.
            const entries = await Promise.all(list.map(async l => {
                try {
                    const s = await api.get(`/my/lines/${l.id}/sip`);
                    return [l.id, s?.sip || null];
                } catch { return [l.id, null]; }
            }));
            setSipMap(Object.fromEntries(entries));
        } catch (e) { notify.error('Erreur chargement'); }
        finally { setLoading(false); }
    }, [notify]);
    useEffect(() => { load(); }, [load]);

    const linesWithSip = lines.filter(l => sipMap[l.id]);

    const copy = (text, label = 'Copié') => {
        navigator.clipboard?.writeText(text); notify.success(label);
    };

    return (
        <>
            <div className="page-header">
                <div>
                    <h1 className="page-title">SIP / Wildix</h1>
                    <p className="page-subtitle">
                        Tout ce que votre technicien Wildix doit configurer côté PBX pour recevoir les appels Twilio.
                    </p>
                </div>
                <button className="btn btn-secondary" onClick={load}><Icons.Refresh className="w-4 h-4" /> Actualiser</button>
            </div>

            <div className="page-content" style={{ display: 'flex', flexDirection: 'column', gap: '1.25rem' }}>
                {/* 1. Routing actif */}
                <div className="card" style={{ padding: '1.25rem 1.4rem' }}>
                    <h2 style={{ marginTop: 0, fontSize: '1.05rem', display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
                        <Icons.Globe className="w-5 h-5" /> Routing actif
                    </h2>
                    <p style={{ color: 'var(--text-muted)', fontSize: '0.88rem', marginBottom: '1rem' }}>
                        Région européenne (Ireland + Frankfurt) — les appels sortent depuis ces IPs. La doc complète :{' '}
                        <a href="https://www.twilio.com/docs/sip-trunking/ip-addresses" target="_blank" rel="noreferrer" style={{ color: 'var(--primary)' }}>
                            twilio.com/docs/sip-trunking/ip-addresses
                        </a>
                    </p>
                    <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))', gap: '0.75rem' }}>
                        {TWILIO_SIP_GATEWAYS.map(g => (
                            <div key={g.range} style={{
                                padding: '0.85rem 1rem',
                                border: `1px solid ${g.active ? '#16a34a' : 'var(--border)'}`,
                                background: g.active ? '#f0fdf4' : '#fff',
                                borderRadius: 10,
                            }}>
                                <div style={{ display: 'flex', alignItems: 'center', gap: '0.4rem', marginBottom: '0.3rem' }}>
                                    <span style={{
                                        width: 8, height: 8, borderRadius: '50%',
                                        background: g.active ? '#16a34a' : '#cbd5e1',
                                    }} />
                                    <span style={{ fontWeight: 600, fontSize: '0.88rem' }}>{g.region}</span>
                                    {g.active && <span className="badge badge-success" style={{ fontSize: '0.62rem', padding: '0.1rem 0.4rem' }}>Active</span>}
                                </div>
                                <div style={{ fontFamily: 'ui-monospace, monospace', fontSize: '0.8rem', color: 'var(--text-muted)' }}>
                                    {g.range}
                                </div>
                                <details style={{ marginTop: '0.4rem' }}>
                                    <summary style={{ cursor: 'pointer', fontSize: '0.72rem', color: 'var(--text-muted)' }}>IPs individuelles</summary>
                                    <div style={{ fontFamily: 'ui-monospace, monospace', fontSize: '0.75rem', marginTop: '0.3rem', lineHeight: 1.6 }}>
                                        {g.ips.map(ip => <div key={ip}>{ip}</div>)}
                                        <div style={{ marginTop: '0.3rem' }}>Ports : <strong>5060</strong> (UDP/TCP), <strong>5061</strong> (TLS)</div>
                                    </div>
                                </details>
                            </div>
                        ))}
                    </div>
                </div>

                {/* 2. Checklist Wildix */}
                <div className="card" style={{ padding: '1.25rem 1.4rem' }}>
                    <h2 style={{ marginTop: 0, fontSize: '1.05rem', display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
                        <Icons.Check className="w-5 h-5" /> À transmettre au technicien Wildix
                    </h2>
                    <ol style={{ paddingLeft: '1.25rem', lineHeight: 1.7, fontSize: '0.9rem', color: 'var(--text)' }}>
                        <li><strong>Whitelist IP Twilio</strong> dans le firewall et le SIP trunking Wildix
                            (ports <code>5060</code> UDP/TCP et <code>5061</code> TLS).
                            Ajoutez au minimum les ranges Europe ci-dessus.</li>
                        <li><strong>Codecs autorisés</strong> : <code>PCMU</code> (G.711 µ-law) et <code>PCMA</code> (G.711 a-law).</li>
                        <li><strong>Authentification</strong> :
                            <ul style={{ marginTop: '0.3rem' }}>
                                <li><em>Recommandé</em> — IP whitelist seule (pas de login/password à gérer).</li>
                                <li><em>Alternative</em> — créer un compte SIP dédié (login/password), à renseigner dans le wizard de chaque ligne (Options avancées).</li>
                            </ul>
                        </li>
                        <li><strong>Format des URI SIP</strong> attendues : <code>sip:&lt;extension&gt;@&lt;pbx&gt;;transport=tls</code>
                            <span style={{ color: 'var(--text-muted)' }}> — l'extension peut être un utilisateur, une queue ou un groupe d'appels.</span></li>
                        <li><strong>NAT / SBC</strong> : si Wildix est derrière NAT, activer <code>rtp-symmetric</code> et autoriser le range RTP Twilio <code>168.86.128.0/18</code> (ports 10000–60000 UDP).</li>
                        <li><strong>Test</strong> : depuis la ligne configurée, faites un appel sortant — la trace SIP doit montrer un <code>INVITE</code> venant d'une IP Twilio listée plus haut.</li>
                    </ol>
                    <p style={{ marginTop: '1rem', padding: '0.75rem 1rem', background: '#fef3c7', borderLeft: '3px solid #d97706', borderRadius: 6, fontSize: '0.85rem', color: '#92400e' }}>
                        Astuce : pour démarrer plus vite, vous pouvez aussi <strong>renvoyer le numéro existant</strong> du client vers le numéro Twilio acheté chez Vocal. Une fois le service validé, on porte le numéro chez Twilio pour avoir un flux Twilio → IA → Wildix natif.
                    </p>
                </div>

                {/* 3. Lignes configurées en SIP */}
                <div className="card">
                    <div style={{ padding: '1rem 1.25rem', borderBottom: '1px solid var(--border)' }}>
                        <h2 style={{ margin: 0, fontSize: '1.05rem', display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
                            <Icons.Phone className="w-5 h-5" /> Vos lignes en mode SIP ({linesWithSip.length})
                        </h2>
                    </div>
                    {loading ? (
                        <div style={{ padding: '2rem', textAlign: 'center', color: 'var(--text-muted)' }}>Chargement…</div>
                    ) : linesWithSip.length === 0 ? (
                        <div style={{ padding: '2rem', textAlign: 'center', color: 'var(--text-muted)' }}>
                            Aucune ligne en mode SIP. Activez la redirection SIP dans le wizard d'une ligne (étape "Transfert").
                        </div>
                    ) : (
                        <div className="table-container">
                            <table className="data-table">
                                <thead>
                                    <tr>
                                        <th>Ligne</th>
                                        <th>PBX</th>
                                        <th>Extension</th>
                                        <th>Transport</th>
                                        <th>Auth</th>
                                        <th>URI SIP générée</th>
                                    </tr>
                                </thead>
                                <tbody>
                                    {linesWithSip.map(l => {
                                        const s = sipMap[l.id];
                                        const uri = `sip:${s.extension}@${s.pbx_url};transport=${s.transport || 'tls'}`;
                                        return (
                                            <tr key={l.id}>
                                                <td>
                                                    <div style={{ fontWeight: 600, fontSize: '0.9rem' }}>{l.label || '—'}</div>
                                                    <div style={{ fontFamily: 'ui-monospace, monospace', color: 'var(--text-muted)', fontSize: '0.78rem' }}>{l.phone_number}</div>
                                                </td>
                                                <td style={{ fontFamily: 'ui-monospace, monospace', fontSize: '0.85rem' }}>{s.pbx_url}</td>
                                                <td style={{ fontFamily: 'ui-monospace, monospace', fontSize: '0.9rem', fontWeight: 600 }}>{s.extension}</td>
                                                <td>
                                                    <span className="badge badge-info" style={{ fontSize: '0.7rem' }}>{(s.transport || 'tls').toUpperCase()}</span>
                                                </td>
                                                <td style={{ fontSize: '0.8rem' }}>
                                                    {s.username ? <span style={{ color: 'var(--text)' }}>{s.username} / ••••</span> : <span className="muted">IP whitelist</span>}
                                                </td>
                                                <td>
                                                    <code onClick={() => copy(uri, 'URI copiée')} title="Copier"
                                                        style={{ cursor: 'pointer', fontSize: '0.75rem', background: 'var(--bg-page)', padding: '0.2rem 0.45rem', borderRadius: 4 }}>
                                                        {uri}
                                                    </code>
                                                </td>
                                            </tr>
                                        );
                                    })}
                                </tbody>
                            </table>
                        </div>
                    )}
                </div>
            </div>
        </>
    );
};

const initialsOf = (name, phone) => {
    const src = (name || '').trim();
    if (src) {
        const parts = src.split(/\s+/).slice(0, 2);
        return parts.map(p => p[0]).join('').toUpperCase();
    }
    return (phone || '?').replace(/\D/g, '').slice(-2) || '?';
};

// ============================================================================
// WIDGETS (bulle chat embarquable branchee sur un agent IA)
// ============================================================================

const WIDGET_API_BASE = (typeof window !== 'undefined' && window.VOCAL_API_URL)
    ? String(window.VOCAL_API_URL).replace(/\/$/, '')
    : 'https://api.helvia.app';

const WidgetsView = () => {
    const { notify } = useApp();
    const [widgets, setWidgets] = useState([]);
    const [agents, setAgents] = useState([]);
    const [loading, setLoading] = useState(true);
    const [editing, setEditing] = useState(null); // null | 'new' | widget object
    const [snippetFor, setSnippetFor] = useState(null);
    const [historyFor, setHistoryFor] = useState(null);

    const load = useCallback(async () => {
        setLoading(true);
        try {
            const [wr, ar] = await Promise.all([
                api.get('/my/widgets'),
                api.get('/my/ai-agents'),
            ]);
            if (wr?.error) notify.error(wr.error);
            if (ar?.error) notify.error(ar.error);
            setWidgets(wr?.widgets || []);
            setAgents(ar?.agents || []);
        } catch (e) { notify.error('Erreur chargement widgets'); }
        finally { setLoading(false); }
    }, [notify]);

    useEffect(() => { load(); }, []); // eslint-disable-line

    const removeWidget = async (w) => {
        if (!confirm(`Supprimer le widget "${w.name}" ?`)) return;
        try {
            const r = await api.delete(`/my/widgets/${w.id}`);
            if (r?.error) return notify.error(r.error);
            notify.success('Widget supprime');
            load();
        } catch (e) { notify.error('Erreur suppression'); }
    };

    const toggleActive = async (w) => {
        try {
            const r = await api.put(`/my/widgets/${w.id}`, { active: w.active ? 0 : 1 });
            if (r?.error) return notify.error(r.error);
            load();
        } catch (e) { notify.error('Erreur'); }
    };

    if (editing) {
        return (
            <WidgetEditor
                widget={editing === 'new' ? null : editing}
                agents={agents}
                onBack={() => setEditing(null)}
                onSaved={() => { setEditing(null); load(); }}
            />
        );
    }

    if (snippetFor)  return <WidgetSnippet widget={snippetFor} onBack={() => setSnippetFor(null)} />;
    if (historyFor)  return <WidgetHistory widget={historyFor} onBack={() => setHistoryFor(null)} />;

    return (
        <>
            <div className="page-header">
                <div>
                    <h1 className="page-title">Widgets</h1>
                    <p className="page-subtitle">
                        Une bulle de chat sur votre site web, branchee sur l'un de vos agents IA. Vos visiteurs peuvent discuter en direct avec lui.
                    </p>
                </div>
                <div style={{ display: 'flex', gap: '0.5rem' }}>
                    <button className="btn btn-secondary" onClick={load}>
                        <Icons.Refresh className="w-4 h-4" /> Actualiser
                    </button>
                    <button className="btn btn-primary" onClick={() => setEditing('new')} disabled={agents.length === 0}>
                        <Icons.Plus className="w-4 h-4" /> Nouveau widget
                    </button>
                </div>
            </div>

            <div className="page-content">
                {loading && <div style={{ padding: '2rem', textAlign: 'center', color: 'var(--text-muted)' }}>Chargement…</div>}

                {!loading && agents.length === 0 && (
                    <div className="card" style={{ padding: '3rem 2rem', textAlign: 'center' }}>
                        <Icons.Sparkles className="w-12 h-12" style={{ margin: '0 auto 1rem', opacity: 0.4 }} />
                        <h3 style={{ margin: '0 0 0.5rem' }}>Aucun agent IA disponible</h3>
                        <p style={{ color: 'var(--text-muted)', maxWidth: 480, margin: '0 auto', fontSize: '0.9rem' }}>
                            Activez l'IA sur une ligne pour pouvoir creer un widget chat branche dessus.
                        </p>
                    </div>
                )}

                {!loading && agents.length > 0 && widgets.length === 0 && (
                    <div className="card" style={{ padding: '3rem 2rem', textAlign: 'center' }}>
                        <Icons.Globe className="w-12 h-12" style={{ margin: '0 auto 1rem', opacity: 0.4 }} />
                        <h3 style={{ margin: '0 0 0.5rem' }}>Pas encore de widget</h3>
                        <p style={{ color: 'var(--text-muted)', maxWidth: 480, margin: '0 auto 1rem', fontSize: '0.9rem' }}>
                            Creez votre premier widget chat et integrez-le sur votre site en collant un simple snippet HTML.
                        </p>
                        <button className="btn btn-primary" onClick={() => setEditing('new')}>
                            <Icons.Plus className="w-4 h-4" /> Creer mon premier widget
                        </button>
                    </div>
                )}

                {!loading && widgets.length > 0 && (
                    <div className="card">
                        <div className="table-container">
                            <table className="data-table">
                                <thead>
                                    <tr>
                                        <th>Nom</th>
                                        <th>Agent IA</th>
                                        <th>Domaines</th>
                                        <th>Convs.</th>
                                        <th>Msgs</th>
                                        <th>Statut</th>
                                        <th style={{ textAlign: 'right' }}>Actions</th>
                                    </tr>
                                </thead>
                                <tbody>
                                    {widgets.map(w => (
                                        <tr key={w.id}>
                                            <td>
                                                <div style={{ fontWeight: 600 }}>{w.name}</div>
                                                <div style={{ fontFamily: 'ui-monospace, monospace', fontSize: '0.72rem', color: 'var(--text-muted)' }}>{w.public_key}</div>
                                            </td>
                                            <td style={{ fontSize: '0.85rem' }}>
                                                <div style={{ fontWeight: 600 }}>{w.ai_name || '—'}</div>
                                                <div style={{ color: 'var(--text-muted)', fontSize: '0.78rem' }}>{w.label || w.phone_number || ''}</div>
                                            </td>
                                            <td style={{ fontSize: '0.8rem', maxWidth: 200 }}>
                                                {w.allowed_domains
                                                    ? <span style={{ color: 'var(--text)' }}>{w.allowed_domains}</span>
                                                    : <span style={{ color: 'var(--text-muted)', fontStyle: 'italic' }}>Tous (ouvert)</span>}
                                            </td>
                                            <td style={{ fontVariantNumeric: 'tabular-nums', fontWeight: 600 }}>{w.conversations_count || 0}</td>
                                            <td style={{ fontVariantNumeric: 'tabular-nums', fontWeight: 600 }}>{w.messages_count || 0}</td>
                                            <td>
                                                <span className={`badge ${w.active ? 'badge-success' : 'badge-warning'}`}>
                                                    {w.active ? 'Actif' : 'Inactif'}
                                                </span>
                                            </td>
                                            <td style={{ textAlign: 'right', whiteSpace: 'nowrap' }}>
                                                <button className="btn btn-sm btn-secondary" title="Snippet" onClick={() => setSnippetFor(w)}>
                                                    <Icons.Code className="w-4 h-4" />
                                                </button>{' '}
                                                <button className="btn btn-sm btn-secondary" title="Conversations" onClick={() => setHistoryFor(w)}>
                                                    <Icons.MessageSquare className="w-4 h-4" />
                                                </button>{' '}
                                                <button className="btn btn-sm btn-secondary" title="Modifier" onClick={() => setEditing(w)}>
                                                    <Icons.Settings className="w-4 h-4" />
                                                </button>{' '}
                                                <button className="btn btn-sm btn-secondary" title={w.active ? 'Desactiver' : 'Activer'} onClick={() => toggleActive(w)}>
                                                    {w.active ? <Icons.AlertCircle className="w-4 h-4" /> : <Icons.Refresh className="w-4 h-4" />}
                                                </button>{' '}
                                                <button className="btn btn-sm btn-secondary" title="Supprimer" onClick={() => removeWidget(w)} style={{ color: 'var(--error, #dc2626)' }}>
                                                    <Icons.X className="w-4 h-4" />
                                                </button>
                                            </td>
                                        </tr>
                                    ))}
                                </tbody>
                            </table>
                        </div>
                    </div>
                )}
            </div>
        </>
    );
};

const WidgetEditor = ({ widget, agents, onBack, onSaved }) => {
    const { notify } = useApp();
    const isNew = !widget;
    const [form, setForm] = useState(() => widget ? {
        name: widget.name || '',
        line_id: widget.line_id || (agents[0]?.line_id || ''),
        title: widget.title || '',
        subtitle: widget.subtitle || '',
        greeting: widget.greeting || '',
        primary_color: widget.primary_color || '#0066ff',
        position: widget.position || 'bottom-right',
        avatar_url: widget.avatar_url || '',
        launcher_text: widget.launcher_text || '',
        allowed_domains: widget.allowed_domains || '',
    } : {
        name: '',
        line_id: agents[0]?.line_id || '',
        title: '', subtitle: '', greeting: '',
        primary_color: '#0066ff', position: 'bottom-right',
        avatar_url: '', launcher_text: '', allowed_domains: '',
    });
    const [saving, setSaving] = useState(false);

    const selectedAgent = agents.find(a => a.line_id === parseInt(form.line_id)) || agents[0];

    const save = async () => {
        if (!form.name.trim()) return notify.error('Nom requis');
        if (!form.line_id) return notify.error('Selectionnez un agent IA');
        setSaving(true);
        try {
            const payload = { ...form, line_id: parseInt(form.line_id) };
            const r = isNew
                ? await api.post('/my/widgets', payload)
                : await api.put(`/my/widgets/${widget.id}`, payload);
            if (r?.error) return notify.error(r.error);
            notify.success(isNew ? 'Widget cree' : 'Widget mis a jour');
            onSaved();
        } catch (e) { notify.error('Erreur'); }
        finally { setSaving(false); }
    };

    // Preview en live : URL dynamique vers l'embed (sans clef si nouveau)
    const previewKey = !isNew ? widget?.public_key : null;

    return (
        <>
            <div className="page-header">
                <div>
                    <button className="btn btn-sm btn-secondary" onClick={onBack} style={{ marginBottom: '0.5rem' }}>
                        <Icons.ArrowLeft className="w-4 h-4" /> Retour
                    </button>
                    <h1 className="page-title">{isNew ? 'Nouveau widget' : `Widget : ${widget.name}`}</h1>
                </div>
            </div>
            <div className="page-content" style={{ display: 'grid', gridTemplateColumns: '1fr 400px', gap: '1.5rem' }}>
                <div className="card" style={{ padding: '1.5rem' }}>
                    <div className="form-group">
                        <label className="form-label">Nom interne *</label>
                        <input className="form-input" value={form.name} onChange={e => setForm({ ...form, name: e.target.value })} placeholder="Ex: Chat support site vitrine" />
                    </div>
                    <div className="form-group">
                        <label className="form-label">Agent IA *</label>
                        <select className="form-input" value={form.line_id} onChange={e => setForm({ ...form, line_id: e.target.value })}>
                            {agents.map(a => (
                                <option key={a.line_id} value={a.line_id}>
                                    {a.ai_name || '(sans nom)'} — {a.label || a.phone_number}
                                </option>
                            ))}
                        </select>
                        <div style={{ fontSize: '0.78rem', color: 'var(--text-muted)', marginTop: 4 }}>
                            Le widget utilise les instructions, la base de connaissances et le ton de cet agent.
                        </div>
                    </div>
                    <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem' }}>
                        <div className="form-group">
                            <label className="form-label">Titre affiche</label>
                            <input className="form-input" value={form.title} onChange={e => setForm({ ...form, title: e.target.value })} placeholder={selectedAgent?.ai_name || 'Assistant'} />
                        </div>
                        <div className="form-group">
                            <label className="form-label">Sous-titre</label>
                            <input className="form-input" value={form.subtitle} onChange={e => setForm({ ...form, subtitle: e.target.value })} placeholder="Repond en quelques secondes" />
                        </div>
                    </div>
                    <div className="form-group">
                        <label className="form-label">Message d'accueil</label>
                        <textarea className="form-input" rows={2} value={form.greeting} onChange={e => setForm({ ...form, greeting: e.target.value })} placeholder={selectedAgent?.ai_greeting || 'Bonjour, comment puis-je vous aider ?'} />
                    </div>
                    <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '1rem' }}>
                        <div className="form-group">
                            <label className="form-label">Couleur</label>
                            <input type="color" className="form-input" value={form.primary_color} onChange={e => setForm({ ...form, primary_color: e.target.value })} style={{ height: 40, padding: 4 }} />
                        </div>
                        <div className="form-group">
                            <label className="form-label">Position</label>
                            <select className="form-input" value={form.position} onChange={e => setForm({ ...form, position: e.target.value })}>
                                <option value="bottom-right">Bas droite</option>
                                <option value="bottom-left">Bas gauche</option>
                            </select>
                        </div>
                        <div className="form-group">
                            <label className="form-label">URL avatar</label>
                            <input className="form-input" value={form.avatar_url} onChange={e => setForm({ ...form, avatar_url: e.target.value })} placeholder="https://…" />
                        </div>
                    </div>
                    <div className="form-group">
                        <label className="form-label">Domaines autorises (CSV)</label>
                        <input className="form-input" value={form.allowed_domains} onChange={e => setForm({ ...form, allowed_domains: e.target.value })} placeholder="monsite.ch, www.monsite.ch (vide = ouvert)" />
                        <div style={{ fontSize: '0.78rem', color: 'var(--text-muted)', marginTop: 4 }}>
                            Recommande : limitez aux domaines ou le widget peut etre integre. Sous-domaines inclus automatiquement.
                        </div>
                    </div>
                    <div style={{ display: 'flex', gap: '0.5rem', marginTop: '1rem' }}>
                        <button className="btn btn-primary" onClick={save} disabled={saving}>
                            {saving ? 'Enregistrement…' : (isNew ? 'Creer le widget' : 'Enregistrer')}
                        </button>
                        <button className="btn btn-secondary" onClick={onBack}>Annuler</button>
                    </div>
                </div>

                <div>
                    <div className="card" style={{ padding: '1rem', position: 'sticky', top: 0 }}>
                        <h3 style={{ margin: '0 0 0.5rem', fontSize: '0.95rem' }}>Apercu</h3>
                        {previewKey ? (
                            <iframe
                                title="Preview widget"
                                src={`${WIDGET_API_BASE}/widget/v1/embed?k=${encodeURIComponent(previewKey)}`}
                                style={{ width: '100%', height: 560, border: '1px solid var(--border)', borderRadius: 8, background: '#f7f8fa' }}
                            />
                        ) : (
                            <div style={{ padding: '2rem 1rem', textAlign: 'center', color: 'var(--text-muted)', fontSize: '0.85rem', border: '1px dashed var(--border)', borderRadius: 8 }}>
                                L'apercu s'affichera apres la creation du widget.
                            </div>
                        )}
                        <div style={{ fontSize: '0.75rem', color: 'var(--text-muted)', marginTop: '0.5rem' }}>
                            Rafraichissez l'apercu apres avoir enregistre pour voir les modifications.
                        </div>
                    </div>
                </div>
            </div>
        </>
    );
};

const WidgetSnippet = ({ widget, onBack }) => {
    const { notify } = useApp();
    const snippet = `<script src="${WIDGET_API_BASE}/widget/v1.js" data-key="${widget.public_key}" async></script>`;
    const copy = async () => {
        try { await navigator.clipboard.writeText(snippet); notify.success('Copie !'); }
        catch { notify.error('Impossible de copier'); }
    };
    return (
        <>
            <div className="page-header">
                <div>
                    <button className="btn btn-sm btn-secondary" onClick={onBack} style={{ marginBottom: '0.5rem' }}>
                        <Icons.ArrowLeft className="w-4 h-4" /> Retour
                    </button>
                    <h1 className="page-title">Snippet d'integration</h1>
                    <p className="page-subtitle">Collez ce code juste avant la balise <code>&lt;/body&gt;</code> de votre site.</p>
                </div>
            </div>
            <div className="page-content">
                <div className="card" style={{ padding: '1.5rem' }}>
                    <h3 style={{ margin: '0 0 0.5rem' }}>Widget : {widget.name}</h3>
                    <p style={{ color: 'var(--text-muted)', fontSize: '0.85rem', marginTop: 0 }}>
                        Cle publique : <code style={{ fontFamily: 'ui-monospace, monospace' }}>{widget.public_key}</code>
                    </p>
                    <pre style={{
                        background: '#0f172a', color: '#e2e8f0', padding: '1rem',
                        borderRadius: 8, overflowX: 'auto', fontSize: '0.85rem',
                        fontFamily: 'ui-monospace, monospace', userSelect: 'all',
                    }}>{snippet}</pre>
                    <button className="btn btn-primary" onClick={copy} style={{ marginTop: '0.75rem' }}>
                        <Icons.Code className="w-4 h-4" /> Copier le snippet
                    </button>
                    <hr style={{ margin: '1.5rem 0', border: 'none', borderTop: '1px solid var(--border)' }} />
                    <h4 style={{ margin: '0 0 0.5rem' }}>Tester maintenant</h4>
                    <p style={{ color: 'var(--text-muted)', fontSize: '0.85rem', marginTop: 0 }}>
                        Ouvrez ce lien pour discuter avec le widget en plein ecran :
                    </p>
                    <a href={`${WIDGET_API_BASE}/widget/v1/embed?k=${encodeURIComponent(widget.public_key)}`}
                       target="_blank" rel="noopener" className="btn btn-secondary">
                        <Icons.Globe className="w-4 h-4" /> Ouvrir l'apercu
                    </a>
                </div>
                {widget.allowed_domains
                    ? null
                    : <div className="card" style={{ padding: '1rem', marginTop: '1rem', background: '#fffbea', border: '1px solid #fde68a' }}>
                        <strong>Conseil securite :</strong> aucun domaine autorise n'est defini : n'importe quel site peut integrer ce widget. Pensez a restreindre dans les parametres.
                      </div>}
            </div>
        </>
    );
};

const WidgetHistory = ({ widget, onBack }) => {
    const { notify } = useApp();
    const [convs, setConvs] = useState([]);
    const [selected, setSelected] = useState(null);
    const [messages, setMessages] = useState([]);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        (async () => {
            setLoading(true);
            try {
                const r = await api.get(`/my/widgets/${widget.id}/conversations`);
                if (r?.error) return notify.error(r.error);
                setConvs(r.conversations || []);
            } catch (e) { notify.error('Erreur'); }
            finally { setLoading(false); }
        })();
    }, [widget.id, notify]);

    useEffect(() => {
        if (!selected) return setMessages([]);
        (async () => {
            try {
                const r = await api.get(`/my/widgets/${widget.id}/conversations/${selected.id}/messages`);
                if (r?.error) return notify.error(r.error);
                setMessages(r.messages || []);
            } catch (e) { notify.error('Erreur'); }
        })();
    }, [selected, widget.id, notify]);

    return (
        <>
            <div className="page-header">
                <div>
                    <button className="btn btn-sm btn-secondary" onClick={onBack} style={{ marginBottom: '0.5rem' }}>
                        <Icons.ArrowLeft className="w-4 h-4" /> Retour
                    </button>
                    <h1 className="page-title">Conversations — {widget.name}</h1>
                </div>
            </div>
            <div className="page-content" style={{ display: 'grid', gridTemplateColumns: '300px 1fr', gap: '1rem' }}>
                <div className="card" style={{ padding: 0, maxHeight: '70vh', overflowY: 'auto' }}>
                    {loading && <div style={{ padding: '1rem', color: 'var(--text-muted)' }}>Chargement…</div>}
                    {!loading && convs.length === 0 && <div style={{ padding: '1rem', color: 'var(--text-muted)', fontSize: '0.85rem' }}>Aucune conversation.</div>}
                    {convs.map(c => (
                        <button key={c.id} onClick={() => setSelected(c)}
                            style={{
                                display: 'block', width: '100%', textAlign: 'left',
                                padding: '0.7rem 0.9rem', border: 0, background: selected?.id === c.id ? 'var(--bg-hover, #f1f5f9)' : 'transparent',
                                borderBottom: '1px solid var(--border)', cursor: 'pointer',
                            }}>
                            <div style={{ fontWeight: 600, fontSize: '0.85rem' }}>Visiteur {String(c.visitor_id).slice(0, 8)}…</div>
                            <div style={{ fontSize: '0.72rem', color: 'var(--text-muted)' }}>{c.messages_count} msgs · {new Date(c.last_message_at).toLocaleString('fr-CH')}</div>
                        </button>
                    ))}
                </div>
                <div className="card" style={{ padding: '1rem', minHeight: 300 }}>
                    {!selected && <div style={{ color: 'var(--text-muted)' }}>Selectionnez une conversation.</div>}
                    {selected && (
                        <div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
                            {messages.map(m => (
                                <div key={m.id} style={{
                                    alignSelf: m.role === 'user' ? 'flex-end' : 'flex-start',
                                    background: m.role === 'user' ? 'var(--primary, #0066ff)' : '#f1f5f9',
                                    color: m.role === 'user' ? '#fff' : '#1a1a1a',
                                    padding: '0.5rem 0.75rem', borderRadius: 12, maxWidth: '70%',
                                    fontSize: '0.88rem', whiteSpace: 'pre-wrap',
                                }}>
                                    {m.content}
                                    <div style={{ fontSize: '0.65rem', opacity: 0.7, marginTop: 2 }}>{new Date(m.created_at).toLocaleString('fr-CH')}</div>
                                </div>
                            ))}
                        </div>
                    )}
                </div>
            </div>
        </>
    );
};

const AiAgentsView = () => {
    const { notify } = useApp();
    const [agents, setAgents] = useState([]);
    const [loading, setLoading] = useState(true);
    const [selectedId, setSelectedId] = useState(null);

    const load = useCallback(async (silent = false) => {
        if (!silent) setLoading(true);
        try {
            const r = await api.get('/my/ai-agents');
            if (r?.error) { notify.error(r.error); return; }
            setAgents(r.agents || []);
        } catch (e) { notify.error('Erreur chargement agents'); }
        finally { if (!silent) setLoading(false); }
    }, [notify]);

    useEffect(() => { load(); }, []); // eslint-disable-line

    if (selectedId) {
        const agent = agents.find(a => a.line_id === selectedId);
        if (agent) {
            return <AiAgentDetail agent={agent} onBack={() => setSelectedId(null)} onSaved={() => load(true)} />;
        }
    }

    return (
        <>
            <div className="page-header">
                <div>
                    <h1 className="page-title">Agents IA</h1>
                    <p className="page-subtitle">
                        Vos lignes équipées d'un assistant vocal ou WhatsApp. Une seule configuration alimente les deux canaux.
                    </p>
                </div>
                <div style={{ display: 'flex', gap: '0.5rem' }}>
                    <button className="btn btn-secondary" onClick={() => load()}>
                        <Icons.Refresh className="w-4 h-4" /> Actualiser
                    </button>
                </div>
            </div>

            <div className="page-content">
                {loading && <div style={{ padding: '2rem', textAlign: 'center', color: 'var(--text-muted)' }}>Chargement…</div>}

                {!loading && agents.length === 0 && (
                    <div className="card" style={{ padding: '3rem 2rem', textAlign: 'center' }}>
                        <Icons.Sparkles className="w-12 h-12" style={{ margin: '0 auto 1rem', opacity: 0.4 }} />
                        <h3 style={{ margin: '0 0 0.5rem' }}>Aucun agent IA actif</h3>
                        <p style={{ color: 'var(--text-muted)', maxWidth: 480, margin: '0 auto', fontSize: '0.9rem' }}>
                            Activez l'IA vocale ou WhatsApp sur une ligne pour la voir apparaître ici.
                        </p>
                    </div>
                )}

                {!loading && agents.length > 0 && (
                    <div className="card">
                        <div className="table-container">
                            <table className="data-table">
                                <thead>
                                    <tr>
                                        <th>Assistant</th>
                                        <th>Ligne</th>
                                        <th>Canaux</th>
                                        <th>Appels (90j)</th>
                                        <th>Msgs WA (90j)</th>
                                        <th>Mis à jour</th>
                                        <th style={{ textAlign: 'right' }}>Actions</th>
                                    </tr>
                                </thead>
                                <tbody>
                                    {agents.map(a => (
                                        <tr key={a.line_id} style={{ cursor: 'pointer' }}
                                            onClick={(e) => { if (e.target.closest('button, a')) return; setSelectedId(a.line_id); }}>
                                            <td>
                                                <div style={{ display: 'flex', alignItems: 'center', gap: '0.6rem' }}>
                                                    <div className="ai-avatar" style={{ width: 34, height: 34, fontSize: '0.78rem' }}>
                                                        {initialsOf(a.ai_name, a.phone_number)}
                                                    </div>
                                                    <div>
                                                        <div style={{ fontWeight: 600, fontSize: '0.9rem' }}>{a.ai_name || '—'}</div>
                                                        <div style={{ fontSize: '0.74rem', color: 'var(--text-muted)' }}>
                                                            {a.ai_greeting ? (a.ai_greeting.length > 60 ? a.ai_greeting.slice(0, 60) + '…' : a.ai_greeting) : 'Pas de message d\'accueil'}
                                                        </div>
                                                    </div>
                                                </div>
                                            </td>
                                            <td style={{ fontSize: '0.85rem' }}>
                                                <div style={{ fontWeight: 600 }}>{a.label || '—'}</div>
                                                <div style={{ fontFamily: 'ui-monospace, monospace', color: 'var(--text-muted)', fontSize: '0.78rem' }}>
                                                    {a.phone_number}
                                                </div>
                                            </td>
                                            <td>
                                                <div style={{ display: 'flex', gap: '0.3rem', flexWrap: 'wrap' }}>
                                                    {a.voice_ai_active && <span className="badge badge-info" style={{ display: 'inline-flex', alignItems: 'center', gap: '0.25rem' }}><Icons.Phone className="w-3 h-3" /> Voix</span>}
                                                    {a.whatsapp_bot_active && <span className="badge badge-success" style={{ display: 'inline-flex', alignItems: 'center', gap: '0.25rem' }}><Icons.Chat className="w-3 h-3" /> WhatsApp</span>}
                                                    {a.whatsapp_active && !a.whatsapp_bot_active && <span className="badge badge-warning" style={{ display: 'inline-flex', alignItems: 'center', gap: '0.25rem' }}><Icons.Chat className="w-3 h-3" /> Bot off</span>}
                                                </div>
                                            </td>
                                            <td style={{ fontSize: '0.9rem', fontVariantNumeric: 'tabular-nums', fontWeight: 600 }}>
                                                {a.stats?.human_calls_90d || 0}
                                            </td>
                                            <td style={{ fontSize: '0.9rem', fontVariantNumeric: 'tabular-nums', fontWeight: 600 }}>
                                                {a.stats?.human_wa_messages_90d || 0}
                                            </td>
                                            <td style={{ fontSize: '0.8rem', color: 'var(--text-muted)' }}>
                                                {a.updated_at ? formatDateTime(a.updated_at) : '—'}
                                            </td>
                                            <td style={{ textAlign: 'right' }}>
                                                <button className="btn btn-secondary btn-sm" onClick={() => setSelectedId(a.line_id)}
                                                    style={{ display: 'inline-flex', alignItems: 'center', gap: '0.3rem' }}>
                                                    <Icons.Edit className="w-3 h-3" /> Configurer
                                                </button>
                                            </td>
                                        </tr>
                                    ))}
                                </tbody>
                            </table>
                        </div>
                        <div style={{ padding: '0.75rem 1rem', borderTop: '1px solid var(--border)', fontSize: '0.8rem', color: 'var(--text-muted)' }}>
                            {agents.length} agent{agents.length > 1 ? 's' : ''}
                        </div>
                    </div>
                )}
            </div>
        </>
    );
};

const AiAgentDetail = ({ agent, onSaved, onBack }) => {
    const { notify } = useApp();
    const [tab, setTab] = useState('overview');
    const initial = useMemo(() => ({
        ai_name: agent.ai_name || '',
        ai_greeting: agent.ai_greeting || '',
        voice_ai_prompt: agent.voice_ai_prompt || '',
        ai_knowledge: agent.ai_knowledge || '',
    }), [agent.line_id]); // eslint-disable-line
    const [form, setForm] = useState(initial);
    const [saving, setSaving] = useState(false);
    const dirty = JSON.stringify(form) !== JSON.stringify(initial);

    useEffect(() => { setForm(initial); }, [initial]);

    const save = async () => {
        setSaving(true);
        try {
            const r = await api.put(`/my/lines/${agent.line_id}`, form);
            if (r?.error) { notify.error(r.error); return; }
            notify.success('Agent enregistré');
            onSaved?.();
        } catch (e) { notify.error('Erreur enregistrement'); }
        finally { setSaving(false); }
    };

    // Auto-apprentissage state
    const [learnSources, setLearnSources] = useState({ calls: true, whatsapp: true });
    const [learnDays, setLearnDays] = useState(90);
    const [learnLoading, setLearnLoading] = useState(false);
    const [learnResult, setLearnResult] = useState(null);

    const runLearnPreview = async () => {
        const sources = Object.entries(learnSources).filter(([_, v]) => v).map(([k]) => k);
        if (sources.length === 0) { notify.error('Sélectionnez au moins une source'); return; }
        setLearnLoading(true);
        setLearnResult(null);
        try {
            const r = await api.post(`/my/ai-agents/${agent.line_id}/learn-preview`, {
                sources, days: learnDays, max_items: 50,
            });
            if (r?.error) { notify.error(r.error); return; }
            setLearnResult(r);
        } catch (e) { notify.error('Erreur analyse'); }
        finally { setLearnLoading(false); }
    };

    const applyLearnSuggestion = () => {
        if (!learnResult?.suggested_merged) return;
        setForm(f => ({ ...f, ai_knowledge: learnResult.suggested_merged }));
        notify.success('Connaissance enrichie : pensez à enregistrer.');
        setLearnResult(null);
    };

    const subtitle = [
        agent.voice_ai_active && '📞 Voix IA active',
        agent.whatsapp_bot_active && '💬 Bot WhatsApp actif',
        !agent.voice_ai_active && !agent.whatsapp_bot_active && agent.whatsapp_active && '💬 WhatsApp (bot désactivé)',
    ].filter(Boolean).join(' · ');

    const knowledgeChars = (form.ai_knowledge || '').length;
    const promptChars   = (form.voice_ai_prompt || '').length;

    return (
        <div className="page-content">
            {onBack && (
                <button className="btn btn-secondary btn-sm" onClick={onBack}
                    style={{ display: 'inline-flex', alignItems: 'center', gap: '0.35rem', marginBottom: '0.75rem' }}>
                    <Icons.ArrowLeft className="w-4 h-4" /> Retour aux agents
                </button>
            )}
            <div className="ai-hero">
                <div className="ai-hero-avatar">{initialsOf(form.ai_name || agent.ai_name, agent.phone_number)}</div>
                <div className="ai-hero-body">
                    <h1 className="ai-hero-name">{form.ai_name || agent.label || agent.phone_number}</h1>
                    <div className="ai-hero-sub">{agent.phone_number}{subtitle && ` · ${subtitle}`}</div>
                    <div className="ai-hero-badges">
                        {agent.voice_ai_active && <span className="ai-chip">📞 Voix</span>}
                        {agent.whatsapp_bot_active && <span className="ai-chip">💬 WhatsApp</span>}
                        {agent.whatsapp_active && !agent.whatsapp_bot_active && <span className="ai-chip">💬 WA (bot off)</span>}
                    </div>
                </div>
            </div>

            <div className="ai-stats">
                <div className="ai-stat">
                    <div className="ai-stat-label">Appels humains (90j)</div>
                    <div className="ai-stat-value">{agent.stats?.human_calls_90d || 0}</div>
                </div>
                <div className="ai-stat">
                    <div className="ai-stat-label">Msgs WA humains (90j)</div>
                    <div className="ai-stat-value">{agent.stats?.human_wa_messages_90d || 0}</div>
                </div>
                <div className="ai-stat">
                    <div className="ai-stat-label">Savoir métier</div>
                    <div className="ai-stat-value">{knowledgeChars.toLocaleString('fr-CH')} <span style={{ fontSize: '0.7rem', color: 'var(--text-muted)', fontWeight: 500 }}>car.</span></div>
                </div>
                <div className="ai-stat">
                    <div className="ai-stat-label">Prompt</div>
                    <div className="ai-stat-value">{promptChars.toLocaleString('fr-CH')} <span style={{ fontSize: '0.7rem', color: 'var(--text-muted)', fontWeight: 500 }}>car.</span></div>
                </div>
            </div>

            <div className="ai-tabs">
                <button className={`ai-tab ${tab === 'overview' ? 'is-active' : ''}`} onClick={() => setTab('overview')}>🎯 Vue d'ensemble</button>
                <button className={`ai-tab ${tab === 'identity' ? 'is-active' : ''}`} onClick={() => setTab('identity')}>👤 Identité</button>
                <button className={`ai-tab ${tab === 'prompt' ? 'is-active' : ''}`} onClick={() => setTab('prompt')}>📝 Prompt</button>
                <button className={`ai-tab ${tab === 'knowledge' ? 'is-active' : ''}`} onClick={() => setTab('knowledge')}>📚 Savoir</button>
                <button className={`ai-tab ${tab === 'learn' ? 'is-active' : ''}`} onClick={() => setTab('learn')}>🧠 Apprendre</button>
            </div>

            {tab === 'overview' && (
                <>
                    <div className="ai-card">
                        <h3>Comment ça marche</h3>
                        <p className="ai-card-hint" style={{ marginBottom: '0.5rem' }}>
                            La même configuration alimente <strong>l'IA vocale</strong> et le <strong>bot WhatsApp</strong>. Modifiez l'identité, le prompt ou le savoir dans les onglets dédiés.
                        </p>
                        <ul style={{ margin: 0, paddingLeft: '1.2rem', color: 'var(--text-muted)', fontSize: '0.88rem', lineHeight: 1.7 }}>
                            <li><strong>Identité</strong> - le nom de votre assistant et son message d'accueil.</li>
                            <li><strong>Prompt</strong> - comment il doit se comporter (ton, rôle, limites).</li>
                            <li><strong>Savoir</strong> - les faits concrets (horaires, tarifs, processus).</li>
                            <li><strong>Apprendre</strong> - extrait du vocabulaire et des infos depuis vos conversations humaines passées.</li>
                        </ul>
                    </div>

                    <div className="ai-card">
                        <h3>Résumé du prompt</h3>
                        <p className="ai-card-hint">Aperçu rapide des premières lignes (cliquez sur l'onglet Prompt pour éditer).</p>
                        <pre className="ai-learn-pre" style={{ background: 'var(--bg-page)', maxHeight: 180 }}>
                            {form.voice_ai_prompt ? form.voice_ai_prompt.slice(0, 600) + (form.voice_ai_prompt.length > 600 ? '…' : '') : '(aucun prompt configuré)'}
                        </pre>
                    </div>
                </>
            )}

            {tab === 'identity' && (
                <div className="ai-card">
                    <h3>Identité de l'assistant</h3>
                    <p className="ai-card-hint">Le nom et le message d'accueil utilisés au téléphone et sur WhatsApp.</p>
                    <label className="ai-field">
                        <div className="ai-field-label">Nom de l'assistant</div>
                        <input
                            type="text" className="input"
                            value={form.ai_name}
                            onChange={e => setForm(f => ({ ...f, ai_name: e.target.value }))}
                            placeholder="ex: Camille"
                        />
                    </label>
                    <label className="ai-field">
                        <div className="ai-field-label">Message d'accueil (vocal)</div>
                        <textarea
                            className="input" rows={3}
                            value={form.ai_greeting}
                            onChange={e => setForm(f => ({ ...f, ai_greeting: e.target.value }))}
                            placeholder="Bonjour, je suis Camille, l'assistante de…"
                        />
                    </label>
                </div>
            )}

            {tab === 'prompt' && (
                <div className="ai-card">
                    <h3>Prompt / instructions</h3>
                    <p className="ai-card-hint">Comment l'assistant doit se comporter : rôle, ton, limites, priorités.</p>
                    <textarea
                        className="input" rows={18}
                        value={form.voice_ai_prompt}
                        onChange={e => setForm(f => ({ ...f, voice_ai_prompt: e.target.value }))}
                        placeholder="Tu es l'assistante de…. Tu réponds toujours en français, de manière polie et concise…"
                        style={{ width: '100%', fontFamily: 'inherit' }}
                    />
                </div>
            )}

            {tab === 'knowledge' && (
                <div className="ai-card">
                    <h3>Savoir métier</h3>
                    <p className="ai-card-hint">Informations factuelles utilisées par l'assistant : horaires, tarifs, processus, vocabulaire.</p>
                    <textarea
                        className="input" rows={22}
                        value={form.ai_knowledge}
                        onChange={e => setForm(f => ({ ...f, ai_knowledge: e.target.value }))}
                        placeholder="HORAIRES: lundi-vendredi 8h-18h&#10;TARIFS:…&#10;PROCESSUS:…"
                        style={{ width: '100%', fontFamily: 'inherit' }}
                    />
                </div>
            )}

            {tab === 'learn' && (
                <div className="ai-card">
                    <h3>🧠 Apprendre des conversations humaines</h3>
                    <p className="ai-card-hint">
                        Analyse les communications passées émises par <strong>un humain</strong> (pas par le bot) pour extraire ton, vocabulaire et infos répétées, puis te propose un complément à ajouter au savoir métier.
                    </p>

                    <div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap', alignItems: 'center', marginBottom: '0.75rem' }}>
                        <label style={{ display: 'flex', alignItems: 'center', gap: '0.4rem' }}>
                            <input
                                type="checkbox" checked={learnSources.calls}
                                onChange={e => setLearnSources(s => ({ ...s, calls: e.target.checked }))}
                            />
                            Transcripts d'appels ({agent.stats?.human_calls_90d || 0})
                        </label>
                        <label style={{ display: 'flex', alignItems: 'center', gap: '0.4rem' }}>
                            <input
                                type="checkbox" checked={learnSources.whatsapp}
                                onChange={e => setLearnSources(s => ({ ...s, whatsapp: e.target.checked }))}
                            />
                            Messages WA humains ({agent.stats?.human_wa_messages_90d || 0})
                        </label>
                        <label style={{ display: 'flex', alignItems: 'center', gap: '0.4rem' }}>
                            Période :
                            <select className="input" value={learnDays} onChange={e => setLearnDays(parseInt(e.target.value))}>
                                <option value={30}>30 jours</option>
                                <option value={90}>90 jours</option>
                                <option value={180}>180 jours</option>
                                <option value={365}>1 an</option>
                            </select>
                        </label>
                        <button
                            className="btn btn-primary"
                            onClick={runLearnPreview}
                            disabled={learnLoading || (!learnSources.calls && !learnSources.whatsapp)}
                        >
                            {learnLoading ? 'Analyse…' : '🧠 Analyser et proposer'}
                        </button>
                    </div>

                    {learnResult && (
                        <div className="ai-learn-result">
                            <div className="muted" style={{ fontSize: '0.8rem', marginBottom: '0.4rem' }}>
                                {learnResult.samples_count} échantillon(s) analysé(s)
                                {learnResult.samples_breakdown && ` (${learnResult.samples_breakdown.calls} appels, ${learnResult.samples_breakdown.whatsapp} WhatsApp)`}
                                {learnResult.model && ` · modèle: ${learnResult.model}`}
                            </div>
                            {learnResult.suggested_addition ? (
                                <>
                                    <div style={{ fontWeight: 600, marginBottom: '0.3rem' }}>Proposition à ajouter au savoir :</div>
                                    <pre className="ai-learn-pre">{learnResult.suggested_addition}</pre>
                                    <div style={{ display: 'flex', gap: '0.5rem' }}>
                                        <button className="btn btn-primary btn-sm" onClick={applyLearnSuggestion}>
                                            ✓ Fusionner avec mon savoir
                                        </button>
                                        <button className="btn btn-sm" onClick={() => setLearnResult(null)}>Annuler</button>
                                    </div>
                                </>
                            ) : (
                                <p className="muted" style={{ margin: 0 }}>{learnResult.reason || 'Aucune suggestion à proposer.'}</p>
                            )}
                        </div>
                    )}
                </div>
            )}

            <div className="ai-save-bar">
                <button className="btn btn-primary" onClick={save} disabled={saving || !dirty}>
                    {saving ? 'Enregistrement…' : '💾 Enregistrer'}
                </button>
                {dirty && (
                    <span style={{ display: 'inline-flex', alignItems: 'center', gap: '0.4rem', fontSize: '0.82rem', color: 'var(--text-muted)' }}>
                        <span className="ai-dirty-dot" /> Modifications non enregistrées
                    </span>
                )}
            </div>
        </div>
    );
};

ReactDOM.createRoot(document.getElementById('app')).render(
    <AppProvider><ModulesProvider><PhoneHistoryProvider><App /></PhoneHistoryProvider></ModulesProvider></AppProvider>
);
