// Helvia Inbox PWA
const { useState, useEffect, useCallback, useRef, useMemo, createContext, useContext } = React;

const CONFIG = {
  API_URL: (() => {
    const p = new URLSearchParams(location.search);
    if (p.get('api')) return p.get('api').replace(/\/$/, '');
    if (location.hostname === 'localhost' || location.hostname === '127.0.0.1') {
      return 'http://127.0.0.1:8787';
    }
    return 'https://api.helvia.app';
  })(),
};

const STORAGE_TOKEN = 'helvia_live_token';
const STORAGE_USER  = 'helvia_live_user';

// ==================== API ====================
const api = {
  token: null,
  async req(path, opts = {}) {
    const headers = { 'Content-Type': 'application/json', ...opts.headers };
    if (this.token) headers.Authorization = `Bearer ${this.token}`;
    const resp = await fetch(`${CONFIG.API_URL}${path}`, { ...opts, headers });
    if (resp.status === 401 && this.token) {
      // Session perdue — purge.
      localStorage.removeItem(STORAGE_TOKEN);
      localStorage.removeItem(STORAGE_USER);
      api.token = null;
      window.dispatchEvent(new CustomEvent('helvia-live-logout'));
    }
    try { return await resp.json(); } catch { return { error: 'invalid response' }; }
  },
  get(p)         { return api.req(p); },
  post(p, b)     { return api.req(p, { method: 'POST', body: JSON.stringify(b || {}) }); },
};

// ==================== ICONS ====================
const Icons = {
  Helvia: ({ size = 22 }) => (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none">
      <rect x="10" y="3" width="4" height="18" fill="currentColor"/>
      <rect x="3" y="10" width="18" height="4" fill="currentColor"/>
    </svg>
  ),
  WA: ({ size = 22 }) => (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="currentColor">
      <path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.435 9.884-9.884 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z" />
    </svg>
  ),
  Back: () => <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="M19 12H5M12 19l-7-7 7-7" /></svg>,
  Search: () => <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="11" cy="11" r="8" /><line x1="21" y1="21" x2="16.65" y2="16.65" /></svg>,
  More: () => <svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="5" r="2" /><circle cx="12" cy="12" r="2" /><circle cx="12" cy="19" r="2" /></svg>,
  Refresh: () => <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="23 4 23 10 17 10" /><polyline points="1 20 1 14 7 14" /><path d="M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15" /></svg>,
  Send: () => <svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>,
  Template: () => <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M3 9h18M8 14h8M8 17h5"/></svg>,
  Check: () => <svg width="14" height="14" viewBox="0 0 18 18" fill="currentColor"><path d="M14.27 4.31L7.49 11.09 5.62 9.23l-.71.71 2.58 2.58 7.49-7.49z"/></svg>,
  DoubleCheck: () => <svg width="16" height="16" viewBox="0 0 18 18" fill="currentColor"><path d="M11.27 4.31L4.49 11.09 2.62 9.23l-.71.71 2.58 2.58 7.49-7.49z"/><path d="M16.27 4.31L9.49 11.09l-1-1-.71.71 1.71 1.71 7.49-7.49z"/></svg>,
  Logout: () => <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M9 21H5a2 2 0 01-2-2V5a2 2 0 012-2h4M16 17l5-5-5-5M21 12H9" /></svg>,
  Phone: () => <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M22 16.92v3a2 2 0 01-2.18 2A19.79 19.79 0 013 4.18 2 2 0 015 2h3a2 2 0 012 1.72c.127.96.361 1.903.7 2.81a2 2 0 01-.45 2.11L8.91 9.91a16 16 0 006 6l1.27-1.27a2 2 0 012.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0122 16.92z" /></svg>,
  Bot: () => <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="6" width="18" height="14" rx="3" /><circle cx="9" cy="13" r="1.2" /><circle cx="15" cy="13" r="1.2" /><path d="M12 2v4M8 18h8" /></svg>,
  Chat: () => <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M21 11.5a8.38 8.38 0 01-.9 3.8 8.5 8.5 0 01-7.6 4.7 8.38 8.38 0 01-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 01-.9-3.8 8.5 8.5 0 014.7-7.6 8.38 8.38 0 013.8-.9h.5a8.48 8.48 0 018 8v.5z" /></svg>,
  Archive: () => <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><polyline points="21 8 21 21 3 21 3 8" /><rect x="1" y="3" width="22" height="5" rx="1" /><line x1="10" y1="12" x2="14" y2="12" /></svg>,
  Plus: () => <svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"><path d="M12 5v14M5 12h14" /></svg>,
  Edit: () => <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 013 3L7 19l-4 1 1-4 12.5-12.5z"/></svg>,
  Dialpad: () => <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="6" cy="6" r="1.2"/><circle cx="12" cy="6" r="1.2"/><circle cx="18" cy="6" r="1.2"/><circle cx="6" cy="12" r="1.2"/><circle cx="12" cy="12" r="1.2"/><circle cx="18" cy="12" r="1.2"/><circle cx="6" cy="18" r="1.2"/><circle cx="12" cy="18" r="1.2"/><circle cx="18" cy="18" r="1.2"/></svg>,
  UserPlus: () => <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M16 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"/><circle cx="8.5" cy="7" r="4"/><path d="M20 8v6M23 11h-6"/></svg>,
  Users: () => <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2" /><circle cx="9" cy="7" r="4" /><path d="M23 21v-2a4 4 0 00-3-3.87M16 3.13a4 4 0 010 7.75" /></svg>,
  Globe: () => <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10" /><path d="M2 12h20M12 2a15.3 15.3 0 014 10 15.3 15.3 0 01-4 10 15.3 15.3 0 01-4-10 15.3 15.3 0 014-10z" /></svg>,
  WA: () => <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M17.5 14.4c-.3-.2-1.8-.9-2.1-1-.3-.1-.5-.2-.7.2-.2.3-.8 1-1 1.2-.2.2-.4.2-.7.1-.3-.2-1.3-.5-2.4-1.5-.9-.8-1.5-1.8-1.7-2.1-.2-.3 0-.5.1-.7.1-.1.3-.4.5-.5.1-.2.2-.3.3-.5.1-.2.1-.4 0-.5-.1-.2-.7-1.7-1-2.3-.3-.6-.5-.5-.7-.5h-.6c-.2 0-.5.1-.8.4-.3.3-1.1 1.1-1.1 2.6 0 1.5 1.1 3 1.3 3.2.2.2 2.2 3.4 5.4 4.8.8.3 1.4.5 1.8.6.8.2 1.5.2 2 .1.6-.1 1.8-.7 2-1.4.2-.7.2-1.3.2-1.4-.1-.1-.3-.2-.6-.3zM12 2C6.5 2 2 6.5 2 12c0 1.7.4 3.3 1.2 4.7L2 22l5.4-1.4c1.4.7 2.9 1.1 4.6 1.1 5.5 0 10-4.5 10-10S17.5 2 12 2z"/></svg>,
  Star: ({ filled }) => <svg width="18" height="18" viewBox="0 0 24 24" fill={filled ? 'currentColor' : 'none'} stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" /></svg>,
  PhoneIn: () => <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="16 2 16 8 22 8" /><path d="M22 2l-7 7M16 13a6 6 0 01-6 6H5a2 2 0 01-2-2v-3a10 10 0 0110-10h1" /></svg>,
  PhoneOut: () => <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="8 2 8 8 2 8" /><path d="M2 2l7 7M8 13a6 6 0 006 6h1a2 2 0 002-2v-3a10 10 0 00-10-10H5" /></svg>,
  PhoneMissed: () => <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M16 2l4 4-6 6M8 13a6 6 0 006 6h1a2 2 0 002-2v-3" /><path d="M22 2l-7 7" /></svg>,
  Voicemail: () => <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="6" cy="12" r="4" /><circle cx="18" cy="12" r="4" /><line x1="10" y1="12" x2="14" y2="12" /></svg>,
  Settings: () => <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="3" /><path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 11-2.83 2.83l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-4 0v-.09a1.65 1.65 0 00-1-1.51 1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 11-2.83-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 010-4h.09a1.65 1.65 0 001.51-1 1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 112.83-2.83l.06.06a1.65 1.65 0 001.82.33h0a1.65 1.65 0 001-1.51V3a2 2 0 014 0v.09a1.65 1.65 0 001 1.51h0a1.65 1.65 0 001.82-.33l.06-.06a2 2 0 112.83 2.83l-.06.06a1.65 1.65 0 00-.33 1.82v0a1.65 1.65 0 001.51 1H21a2 2 0 010 4h-.09a1.65 1.65 0 00-1.51 1z" /></svg>,
  ArchiveBox: ({ size = 36 }) => <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><polyline points="21 8 21 21 3 21 3 8" /><rect x="1" y="3" width="22" height="5" rx="1" /><line x1="10" y1="12" x2="14" y2="12" /></svg>,
};

// ==================== ARCHIVES (local) ====================
const STORAGE_ARCHIVED = 'helvia_live_archived';
const loadArchived = () => {
  try { return new Set(JSON.parse(localStorage.getItem(STORAGE_ARCHIVED) || '[]')); }
  catch { return new Set(); }
};
const saveArchived = (set) => {
  localStorage.setItem(STORAGE_ARCHIVED, JSON.stringify([...set]));
};

// ==================== UTILS ====================
function fmtTime(d) {
  if (!d) return '';
  const dt = new Date(d.includes('T') ? d : d.replace(' ', 'T') + 'Z');
  const now = new Date();
  const sameDay = dt.toDateString() === now.toDateString();
  if (sameDay) return dt.toLocaleTimeString('fr-CH', { hour: '2-digit', minute: '2-digit' });
  const diffDays = Math.floor((now - dt) / (24 * 3600 * 1000));
  if (diffDays < 7) return dt.toLocaleDateString('fr-CH', { weekday: 'short' });
  return dt.toLocaleDateString('fr-CH', { day: '2-digit', month: '2-digit' });
}
function fmtDay(d) {
  if (!d) return '';
  const dt = new Date(d.includes('T') ? d : d.replace(' ', 'T') + 'Z');
  const today = new Date();
  const y = new Date(); y.setDate(y.getDate() - 1);
  if (dt.toDateString() === today.toDateString()) return "Aujourd'hui";
  if (dt.toDateString() === y.toDateString()) return 'Hier';
  return dt.toLocaleDateString('fr-CH', { weekday: 'long', day: 'numeric', month: 'long' });
}
function initials(name) {
  if (!name) return '?';
  const parts = name.trim().split(/\s+/);
  if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
  return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
}
function avatarVariant(s) {
  if (!s) return '';
  let h = 0;
  for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) | 0;
  return ['', 'v1', 'v2', 'v3', 'v4'][Math.abs(h) % 5];
}
function fmtPhone(n) {
  if (!n) return '';
  const s = String(n).replace(/[^\d+]/g, '');
  // Suisse : +41 XX XXX XX XX
  if (s.startsWith('+41') && s.length === 12) {
    return `+41 ${s.slice(3, 5)} ${s.slice(5, 8)} ${s.slice(8, 10)} ${s.slice(10, 12)}`;
  }
  // France : +33 X XX XX XX XX
  if (s.startsWith('+33') && s.length === 12) {
    return `+33 ${s.slice(3, 4)} ${s.slice(4, 6)} ${s.slice(6, 8)} ${s.slice(8, 10)} ${s.slice(10, 12)}`;
  }
  // Générique international : +XX(X) puis groupes de 2-3
  if (s.startsWith('+') && s.length > 7) {
    const cc = s.slice(0, 3);
    const rest = s.slice(3);
    const groups = [];
    let i = 0;
    if (rest.length % 2 === 1) { groups.push(rest.slice(0, 3)); i = 3; }
    for (; i < rest.length; i += 2) groups.push(rest.slice(i, i + 2));
    return `${cc} ${groups.join(' ')}`.trim();
  }
  return s;
}
function fmtDuration(sec) {
  if (!sec) return '—';
  const m = Math.floor(sec / 60); const s = sec % 60;
  return m ? `${m}m${s ? String(s).padStart(2, '0') + 's' : ''}` : `${s}s`;
}
function parseTs(d) {
  if (!d) return 0;
  return new Date(d.includes('T') ? d : d.replace(' ', 'T') + 'Z').getTime() || 0;
}

const CHANNEL_META = {
  whatsapp: { label: 'WhatsApp', css: 'ch-wa' },
  widget: { label: 'Widget web', css: 'ch-widget' },
  messenger: { label: 'Messenger', css: 'ch-msg' },
};
const CHANNEL_FILTERS = [
  { id: 'all', label: 'Tous', icon: 'Chat' },
  { id: 'whatsapp', label: 'WhatsApp', icon: 'WA' },
  { id: 'widget', label: 'Widget', icon: 'Globe' },
];

function normalizeWaConv(c) {
  return {
    uid: `wa:${c.id}`,
    channel: 'whatsapp',
    id: c.id,
    contact_name: c.contact_name || c.contact_number,
    contact_number: c.contact_number,
    last_message_body: c.last_message_body,
    last_message_at: c.last_message_at,
    unread_count: c.unread_count || 0,
    bot_enabled: c.bot_enabled,
    line_phone: c.line_phone,
    _raw: c,
  };
}
function normalizeWidgetConv(c, widget) {
  let preview = 'Visiteur web';
  try { if (c.origin) preview = new URL(c.origin).hostname; } catch {}
  return {
    uid: `widget:${widget.id}:${c.id}`,
    channel: 'widget',
    id: c.id,
    widgetId: widget.id,
    widgetName: widget.name,
    contact_name: preview,
    contact_number: (c.visitor_id || 'visiteur').slice(0, 12),
    last_message_body: `${c.messages_count || 0} message${(c.messages_count || 0) > 1 ? 's' : ''}`,
    last_message_at: c.last_message_at,
    unread_count: 0,
    bot_enabled: 1,
    line_phone: widget.phone_number,
    _raw: c,
  };
}
async function fetchUnifiedConversations(lineId) {
  const waQ = lineId ? `?line_id=${lineId}` : '';
  const [waR, widgetsR] = await Promise.all([
    api.get(`/my/whatsapp/conversations${waQ}`),
    api.get('/my/widgets'),
  ]);
  const wa = (waR?.conversations || []).map(normalizeWaConv);
  const widgets = widgetsR?.widgets || [];
  const widgetLists = await Promise.all(
    widgets.map(w => api.get(`/my/widgets/${w.id}/conversations`).then(r => ({ w, convs: r?.conversations || [] })).catch(() => ({ w, convs: [] })))
  );
  const widgetConvs = widgetLists.flatMap(({ w, convs }) => convs.map(c => normalizeWidgetConv(c, w)));
  return [...wa, ...widgetConvs].sort((a, b) => parseTs(b.last_message_at) - parseTs(a.last_message_at));
}

const STORAGE_FAVORITES = 'helvia_live_favorites';
const loadFavorites = () => {
  try { return new Set(JSON.parse(localStorage.getItem(STORAGE_FAVORITES) || '[]')); }
  catch { return new Set(); }
};
const saveFavorites = (set) => localStorage.setItem(STORAGE_FAVORITES, JSON.stringify([...set]));

const CALL_FILTERS = [
  { id: 'all', label: 'Tous', icon: 'Phone' },
  { id: 'inbound', label: 'Reçus', icon: 'PhoneIn' },
  { id: 'outbound', label: 'Émis', icon: 'PhoneOut' },
  { id: 'missed', label: 'Manqués', icon: 'PhoneMissed' },
];
const CALL_STATUS_LABEL = {
  completed: 'Terminé', ringing: 'Sonne', 'in-progress': 'En cours',
  busy: 'Occupé', 'no-answer': 'Sans réponse', failed: 'Échoué',
  canceled: 'Annulé', missed: 'Manqué',
};
function callPeer(call) {
  return call.direction === 'inbound' ? (call.from_number || '—') : (call.to_number || '—');
}
function isMissedCall(call) {
  return ['missed', 'no-answer', 'busy', 'canceled'].includes(call.status);
}

// ==================== TOAST ====================
const AppCtx = createContext(null);
const useApp = () => useContext(AppCtx);

function Toast({ msg, type, onDone }) {
  useEffect(() => {
    const delay = type === 'error' ? 6000 : 3500;
    const t = setTimeout(onDone, delay);
    return () => clearTimeout(t);
  }, [onDone, type]);
  return <div className={`toast ${type || ''}`} onClick={onDone}>{msg}</div>;
}

// ==================== TOP BAR ====================
function TopBarWithLineSelector({ subtitle, right }) {
  const { lines, lineId, setLineId } = useApp();
  const [open, setOpen] = useState(false);
  const ref = useRef(null);

  useEffect(() => {
    if (!open) return;
    const h = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', h);
    document.addEventListener('touchstart', h);
    return () => {
      document.removeEventListener('mousedown', h);
      document.removeEventListener('touchstart', h);
    };
  }, [open]);

  const current = lines.find(l => l.id === lineId) || lines[0] || null;

  // Auto-sélection : si pas de ligne choisie mais des lignes existent, prend la 1ère.
  useEffect(() => {
    if (!lineId && lines.length > 0) setLineId(lines[0].id);
  }, [lines, lineId]);

  const fmtLineSub = (l) => {
    if (!l) return '—';
    return l.label || l.description || l.line_label || 'Ligne WhatsApp';
  };

  return (
    <div className="topbar gradient safe-top" ref={ref}>
      <div className="safe-extend" />
      <button className="topbar-line-btn" onClick={() => setOpen(o => !o)} disabled={lines.length <= 1}>
        <div className="topbar-line-icon"><Icons.Phone /></div>
        <div className="topbar-line-text">
          <div className="topbar-line-num">{current ? (current.phone_number || '—') : 'Aucune ligne'}</div>
          <div className="topbar-line-sub">
            {current ? fmtLineSub(current) : (subtitle || '')}
            {subtitle && current ? ` · ${subtitle}` : ''}
          </div>
        </div>
        {lines.length > 1 && (
          <svg className={`topbar-line-chevron ${open ? 'open' : ''}`} width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
            <polyline points="6 9 12 15 18 9" />
          </svg>
        )}
      </button>
      {right && <div className="topbar-actions">{right}</div>}

      {open && lines.length > 1 && (
        <div className="line-dropdown">
          {lines.map(l => (
            <button key={l.id}
              className={`line-dropdown-item ${l.id === lineId ? 'active' : ''}`}
              onClick={() => { setLineId(l.id); setOpen(false); }}>
              <div className="line-dropdown-icon"><Icons.WA size={16} /></div>
              <div className="line-dropdown-text">
                <div className="line-dropdown-num">{l.phone_number || '—'}</div>
                <div className="line-dropdown-sub">{fmtLineSub(l)}</div>
              </div>
              {l.id === lineId && <div className="line-dropdown-check"><Icons.Check /></div>}
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

// ==================== AUTH ====================
function AuthScreen({ onLogin }) {
  const [step, setStep]     = useState('email');
  const [email, setEmail]   = useState('');
  const [code, setCode]     = useState('');
  const [loading, setLoad]  = useState(false);
  const [error, setError]   = useState('');

  // Magic link ?email=&code=
  useEffect(() => {
    const p = new URLSearchParams(location.search);
    const e = p.get('email'); const c = p.get('code');
    if (e && c) {
      setEmail(e); setLoad(true);
      fetch(`${CONFIG.API_URL}/auth/verify-code`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email: e.toLowerCase(), code: c }),
      }).then(r => r.json()).then(data => {
        if (data.success && data.token) {
          history.replaceState({}, '', location.pathname);
          onLogin(data.token, data.user || { email: e });
        } else { setStep('code'); setError(data.error || 'Lien expiré'); }
      }).catch(() => setError('Erreur réseau')).finally(() => setLoad(false));
    } else if (e) { setEmail(e); setStep('code'); }
  }, []);

  const requestCode = async (e) => {
    e?.preventDefault();
    if (!email.includes('@')) { setError('Email invalide'); return; }
    setLoad(true); setError('');
    try {
      const r = await fetch(`${CONFIG.API_URL}/auth/request-code`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email: email.toLowerCase().trim() }),
      }).then(x => x.json());
      if (r.success) setStep('code');
      else setError(r.error || 'Erreur envoi');
    } catch { setError('Erreur réseau'); }
    finally { setLoad(false); }
  };

  const verify = async (e) => {
    e?.preventDefault();
    if (code.length < 4) return;
    setLoad(true); setError('');
    try {
      const r = await fetch(`${CONFIG.API_URL}/auth/verify-code`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email: email.toLowerCase(), code }),
      }).then(x => x.json());
      if (r.success && r.token) onLogin(r.token, r.user || { email });
      else setError(r.error || 'Code invalide');
    } catch { setError('Erreur réseau'); }
    finally { setLoad(false); }
  };

  return (
    <div className="auth-splash">
      <div className="auth-splash-bg" />
      <div className="auth-splash-inner">
        <div className="auth-splash-logo">
          <div className="auth-splash-cross">
            <img src="assets/img/helvia-logotype-favicon.svg" alt="Helvia" />
          </div>
        </div>
        <p className="auth-splash-tagline">
          {step === 'email'
            ? 'Conversations, appels & contacts en direct'
            : <>Entrez le code reçu à <strong>{email}</strong></>}
        </p>

        {step === 'email' ? (
          <form onSubmit={requestCode} className="auth-splash-form">
            <input type="email" value={email} onChange={e => setEmail(e.target.value)}
              placeholder="vous@exemple.com" className="auth-splash-input" autoFocus autoComplete="email" inputMode="email" />
            {error && <div className="auth-splash-error">{error}</div>}
            <button type="submit" disabled={loading || !email.includes('@')} className="auth-splash-btn">
              {loading ? <span className="auth-splash-spinner" /> : null}
              {loading ? 'Envoi…' : 'Recevoir le code'}
            </button>
          </form>
        ) : (
          <form onSubmit={verify} className="auth-splash-form">
            <input type="text" value={code} onChange={e => setCode(e.target.value.replace(/\D/g, '').slice(0, 6))}
              placeholder="• • • • • •" className="auth-splash-input auth-splash-code" maxLength="6" autoFocus inputMode="numeric" />
            {error && <div className="auth-splash-error">{error}</div>}
            <button type="submit" disabled={loading || code.length < 4} className="auth-splash-btn">
              {loading ? <span className="auth-splash-spinner" /> : null}
              {loading ? 'Vérification…' : 'Se connecter'}
            </button>
            <button type="button" onClick={() => { setStep('email'); setCode(''); setError(''); }} className="auth-splash-link">
              Changer d'email
            </button>
          </form>
        )}

        <div className="auth-splash-foot">
          <img src="assets/img/helvia-logotype-white.svg" alt="Helvia" className="auth-splash-wordmark-img" />
        </div>
      </div>
    </div>
  );
}

// ==================== CONV LIST ITEM ====================
function ChannelBadge({ channel }) {
  const meta = CHANNEL_META[channel] || { label: channel, css: 'ch-msg' };
  return <span className={`conv-channel ${meta.css}`}>{meta.label}</span>;
}

function ConvList({ items, onOpen, onArchive, archiveLabel }) {
  const [menuFor, setMenuFor] = useState(null);
  const longPressTimer = useRef(null);

  const startPress = (uid) => {
    longPressTimer.current = setTimeout(() => setMenuFor(uid), 500);
  };
  const cancelPress = () => {
    if (longPressTimer.current) { clearTimeout(longPressTimer.current); longPressTimer.current = null; }
  };

  return (
    <div className="inbox-list">
      {items.map(c => {
        const name = c.contact_name || c.contact_number;
        const variant = avatarVariant(c.contact_number || c.contact_name);
        const unread = c.unread_count > 0;
        return (
          <div key={c.uid} className="conv-row-wrap">
            <button className="conv-row"
              onClick={() => onOpen(c)}
              onContextMenu={(e) => { e.preventDefault(); setMenuFor(c.uid); }}
              onTouchStart={() => startPress(c.uid)}
              onTouchEnd={cancelPress}
              onTouchMove={cancelPress}
              onMouseDown={() => startPress(c.uid)}
              onMouseUp={cancelPress}
              onMouseLeave={cancelPress}>
              <div className={`conv-avatar ${variant}`}>{initials(name)}</div>
              <div className="conv-main">
                <div className="conv-top">
                  <div className="conv-name">{name}</div>
                  <div className={`conv-time ${unread ? 'unread' : ''}`}>{fmtTime(c.last_message_at)}</div>
                </div>
                <div className="conv-bottom">
                  <div className="conv-preview">
                    <ChannelBadge channel={c.channel} />
                    {c.bot_enabled === 1 && c.channel === 'whatsapp' && <span className="conv-bot">BOT</span>}
                    {c.last_message_body || (c.contact_number || '—')}
                  </div>
                  {unread > 0 && <span className="unread-badge">{c.unread_count}</span>}
                </div>
              </div>
            </button>
          </div>
        );
      })}
      {menuFor && (
        <>
          <div className="drawer-overlay" onClick={() => setMenuFor(null)} />
          <div className="drawer safe-bottom">
            <div className="drawer-handle" />
            <button className="drawer-row" onClick={() => { onArchive(menuFor); setMenuFor(null); }}>
              <div className="drawer-row-icon"><Icons.Archive /></div>
              <div className="drawer-row-text">
                <div className="drawer-row-label">{archiveLabel}</div>
              </div>
            </button>
            <button className="drawer-row" onClick={() => setMenuFor(null)}>
              <div className="drawer-row-icon"><Icons.More /></div>
              <div className="drawer-row-text">
                <div className="drawer-row-label">Annuler</div>
              </div>
            </button>
          </div>
        </>
      )}
    </div>
  );
}

// ==================== INBOX LIST ====================
function InboxView({ onOpen }) {
  const { notify, lineId } = useApp();
  const [convs, setConvs] = useState([]);
  const [loading, setLoading] = useState(true);
  const [search, setSearch] = useState('');
  const [channelFilter, setChannelFilter] = useState('all');
  const [refreshing, setRefresh] = useState(false);
  const [archived, setArchivedSet] = useState(() => loadArchived());

  const toggleArchive = (uid) => {
    const next = new Set(archived);
    if (next.has(uid)) next.delete(uid); else next.add(uid);
    setArchivedSet(next);
    saveArchived(next);
    notify(next.has(uid) ? 'Conversation archivée' : 'Conversation désarchivée', 'success');
  };

  const load = useCallback(async (showSpin = true) => {
    if (showSpin) setLoading(true); else setRefresh(true);
    try {
      const list = await fetchUnifiedConversations(lineId);
      setConvs(list);
    } catch { notify('Erreur réseau', 'error'); }
    finally { setLoading(false); setRefresh(false); }
  }, [lineId, notify]);

  useEffect(() => { load(); }, [load]);
  useEffect(() => {
    const t = setInterval(() => load(false), 20000);
    return () => clearInterval(t);
  }, [load]);

  const filtered = useMemo(() => {
    let list = convs.filter(c => !archived.has(c.uid));
    if (channelFilter !== 'all') list = list.filter(c => c.channel === channelFilter);
    if (search.trim()) {
      const s = search.toLowerCase();
      list = list.filter(c =>
        (c.contact_name || '').toLowerCase().includes(s) ||
        (c.contact_number || '').includes(s) ||
        (c.last_message_body || '').toLowerCase().includes(s) ||
        (CHANNEL_META[c.channel]?.label || '').toLowerCase().includes(s)
      );
    }
    return list;
  }, [convs, search, archived, channelFilter]);

  const archivedCount = useMemo(() => convs.filter(c => archived.has(c.uid)).length, [convs, archived]);
  const unreadTotal = useMemo(() => filtered.reduce((s, c) => s + (c.unread_count || 0), 0), [filtered]);

  return (
    <div className="shell">
      <TopBarWithLineSelector
        subtitle={unreadTotal > 0 ? `${unreadTotal} non lu${unreadTotal > 1 ? 's' : ''}` : 'Tous canaux'}
        right={(
          <button className="topbar-icon-btn" onClick={() => load(false)} disabled={refreshing}>
            <span className={refreshing ? 'spin' : ''} style={{ display: 'inline-flex' }}><Icons.Refresh /></span>
          </button>
        )} />

      <div className="shell-main">
        <div className="search-bar">
          <input className="search-input" type="search" placeholder="Rechercher une conversation…"
            value={search} onChange={e => setSearch(e.target.value)} />
        </div>
        <div className="seg-wrap">
          <div className="seg-control">
            {CHANNEL_FILTERS.map(f => {
              const Ico = Icons[f.icon];
              return (
                <button key={f.id} className={`seg-btn ${channelFilter === f.id ? 'on' : ''}`}
                  onClick={() => setChannelFilter(f.id)}>
                  {Ico && <Ico />}<span>{f.label}</span>
                </button>
              );
            })}
          </div>
        </div>

        {loading ? (
          <div className="empty-state">
            <div className="loading-spinner" style={{ width: 32, height: 32 }} />
          </div>
        ) : filtered.length === 0 ? (
          <div className="empty-state">
            <div className="empty-icon"><Icons.Chat /></div>
            <div className="empty-title">
              {search || channelFilter !== 'all' ? 'Aucun résultat' : 'Aucune conversation'}
            </div>
            <div className="empty-desc">
              WhatsApp, widgets web et autres canaux apparaissent ici dans une seule boîte de réception.
            </div>
          </div>
        ) : (
          <ConvList items={filtered} onOpen={onOpen}
            onArchive={toggleArchive} archiveLabel="Archiver" />
        )}
        {archivedCount > 0 && filtered.length > 0 && (
          <div className="inbox-foot">
            <Icons.ArchiveBox size={16} /> {archivedCount} archivée{archivedCount > 1 ? 's' : ''}
          </div>
        )}
      </div>
    </div>
  );
}

// ==================== APPELS ====================
function CallsView({ onOpenCall }) {
  const { notify, lineId } = useApp();
  const [calls, setCalls] = useState([]);
  const [loading, setLoading] = useState(true);
  const [filter, setFilter] = useState('all');
  const [search, setSearch] = useState('');

  const load = useCallback(async () => {
    setLoading(true);
    try {
      let url = '/my/calls?page=1&limit=50';
      if (lineId) url += `&line_id=${lineId}`;
      if (filter === 'inbound') url += '&direction=inbound';
      else if (filter === 'outbound') url += '&direction=outbound';
      else if (filter === 'missed') url += '&status=missed';
      const r = await api.get(url);
      if (r?.error) notify(r.error, 'error');
      else setCalls(r.calls || []);
    } catch { notify('Erreur réseau', 'error'); }
    finally { setLoading(false); }
  }, [lineId, filter, notify]);

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

  const filtered = useMemo(() => {
    if (!search.trim()) return calls;
    const s = search.toLowerCase();
    return calls.filter(c =>
      (c.from_number || '').includes(s) ||
      (c.to_number || '').includes(s) ||
      (c.line_phone || '').includes(s)
    );
  }, [calls, search]);

  return (
    <div className="shell">
      <TopBarWithLineSelector subtitle="Historique" />
      <div className="shell-main scroll-y">
        <div className="search-bar">
          <input className="search-input" type="search" placeholder="Rechercher un numéro…"
            value={search} onChange={e => setSearch(e.target.value)} />
        </div>
        <div className="seg-wrap">
          <div className="seg-control">
            {CALL_FILTERS.map(f => {
              const Ico = Icons[f.icon];
              return (
                <button key={f.id} className={`seg-btn ${filter === f.id ? 'on' : ''}`}
                  onClick={() => setFilter(f.id)}>
                  {Ico && <Ico />}<span>{f.label}</span>
                </button>
              );
            })}
          </div>
        </div>
        {loading ? (
          <div className="empty-state"><div className="loading-spinner" style={{ width: 28, height: 28 }} /></div>
        ) : filtered.length === 0 ? (
          <div className="empty-state">
            <div className="empty-icon"><Icons.Phone /></div>
            <div className="empty-title">Aucun appel</div>
            <div className="empty-desc">Entrants, sortants, manqués et messages vocaux s'affichent ici.</div>
          </div>
        ) : (
          <div className="calls-list">
            {filtered.map(c => {
              const peer = callPeer(c);
              const missed = isMissedCall(c);
              const hasVm = !!c.recording_url;
              const dirKey = missed ? 'missed' : c.direction;
              const dirLabel = missed ? 'Manqué' : c.direction === 'inbound' ? 'Reçu' : 'Émis';
              return (
                <button key={c.id || c.call_sid} className={`call-row dir-${dirKey}`} onClick={() => onOpenCall && onOpenCall(c)}>
                  <div className={`call-row-icon ${dirKey}`}>
                    {hasVm ? <Icons.Voicemail /> : missed ? <Icons.PhoneMissed /> : c.direction === 'inbound' ? <Icons.PhoneIn /> : <Icons.PhoneOut />}
                  </div>
                  <div className="call-row-main">
                    <div className="call-row-name">
                      <span className={`call-row-name-text ${missed ? 'missed' : ''}`}>{fmtPhone(peer)}</span>
                    </div>
                    <div className="call-row-meta">
                      <span className={`call-row-dir tag-${dirKey}`}>{dirLabel}</span>
                      {c.duration ? <span className="call-row-meta-sep">{fmtDuration(c.duration)}</span> : null}
                      {c.line_phone ? <span className="call-row-meta-sep">via {fmtPhone(c.line_phone)}</span> : null}
                    </div>
                  </div>
                  <div className="call-row-side">
                    <div className="call-row-time">{fmtTime(c.created_at)}</div>
                    {hasVm && <span className="call-row-vm-dot" aria-label="Message vocal" />}
                  </div>
                </button>
              );
            })}
          </div>
        )}
      </div>
    </div>
  );
}

// ==================== CALL DETAIL ====================
function AudioPlayer({ src }) {
  const audioRef = useRef(null);
  const [playing, setPlaying] = useState(false);
  const [cur, setCur] = useState(0);
  const [dur, setDur] = useState(0);
  const [rate, setRate] = useState(1);

  useEffect(() => {
    const a = audioRef.current; if (!a) return;
    const onTime = () => setCur(a.currentTime || 0);
    const onMeta = () => setDur(a.duration || 0);
    const onEnd = () => { setPlaying(false); setCur(0); a.currentTime = 0; };
    a.addEventListener('timeupdate', onTime);
    a.addEventListener('loadedmetadata', onMeta);
    a.addEventListener('ended', onEnd);
    return () => {
      a.removeEventListener('timeupdate', onTime);
      a.removeEventListener('loadedmetadata', onMeta);
      a.removeEventListener('ended', onEnd);
    };
  }, []);

  const toggle = () => {
    const a = audioRef.current; if (!a) return;
    if (a.paused) { a.play(); setPlaying(true); } else { a.pause(); setPlaying(false); }
  };
  const seek = (e) => {
    const a = audioRef.current; if (!a || !dur) return;
    const rect = e.currentTarget.getBoundingClientRect();
    const x = (e.clientX || (e.touches && e.touches[0]?.clientX) || 0) - rect.left;
    const ratio = Math.max(0, Math.min(1, x / rect.width));
    a.currentTime = ratio * dur;
    setCur(a.currentTime);
  };
  const skip = (delta) => {
    const a = audioRef.current; if (!a) return;
    a.currentTime = Math.max(0, Math.min((a.duration || 0), a.currentTime + delta));
  };
  const setSpeed = (r) => {
    const a = audioRef.current; if (!a) return;
    a.playbackRate = r; setRate(r);
  };
  const dl = () => { const link = document.createElement('a'); link.href = src; link.download = 'message-vocal.mp3'; link.target = '_blank'; link.click(); };

  const pct = dur ? (cur / dur) * 100 : 0;

  return (
    <div className="audio-player">
      <audio ref={audioRef} src={src} preload="metadata" />
      <div className="ap-top">
        <button className="ap-play" onClick={toggle} aria-label={playing ? 'Pause' : 'Lecture'}>
          {playing ? (
            <svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="5" width="4" height="14" rx="1"/><rect x="14" y="5" width="4" height="14" rx="1"/></svg>
          ) : (
            <svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>
          )}
        </button>
        <div className="ap-info">
          <div className="ap-times">
            <span>{fmtDuration(Math.floor(cur))}</span>
            <span>{fmtDuration(Math.floor(dur))}</span>
          </div>
          <div className="ap-progress" onClick={seek}>
            <div className="ap-progress-bar" style={{ width: pct + '%' }} />
            <div className="ap-progress-thumb" style={{ left: pct + '%' }} />
          </div>
        </div>
      </div>
      <div className="ap-actions">
        <button className="ap-action" onClick={() => skip(-10)} aria-label="Reculer 10s">
          <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 12a9 9 0 1 0 3-6.7L3 8"/><path d="M3 3v5h5"/></svg>
          <span>10s</span>
        </button>
        <div className="ap-speeds">
          {[1, 1.25, 1.5, 2].map(r => (
            <button key={r} className={`ap-speed ${rate === r ? 'on' : ''}`} onClick={() => setSpeed(r)}>{r}×</button>
          ))}
        </div>
        <button className="ap-action" onClick={() => skip(10)} aria-label="Avancer 10s">
          <span>10s</span>
          <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 12a9 9 0 1 1-3-6.7L21 8"/><path d="M21 3v5h-5"/></svg>
        </button>
        <button className="ap-action ap-dl" onClick={dl} aria-label="Télécharger">
          <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
        </button>
      </div>
    </div>
  );
}

function CallDetailView({ call, onBack }) {
  const peer = callPeer(call);
  const missed = isMissedCall(call);
  const hasVm = !!call.recording_url;
  const dt = parseTs(call.created_at);
  const dateStr = dt ? dt.toLocaleString('fr-FR', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric', hour: '2-digit', minute: '2-digit' }) : '';
  const dirLabel = missed ? 'Appel manqué' : call.direction === 'inbound' ? 'Appel entrant' : 'Appel sortant';
  const IconBig = hasVm ? Icons.Voicemail : missed ? Icons.PhoneMissed : call.direction === 'inbound' ? Icons.PhoneIn : Icons.PhoneOut;

  return (
    <div className="shell slide-in">
      <div className="topbar gradient safe-top">
        <div className="safe-extend" />
        <button className="topbar-back" onClick={onBack}><Icons.Back /></button>
        <div className="topbar-title">
          <div className="topbar-name">Détail de l'appel</div>
          <div className="topbar-sub">{dirLabel}</div>
        </div>
        <div className="topbar-actions">
          <a className="topbar-icon-btn" href={`tel:${peer}`} aria-label="Rappeler"><Icons.Phone /></a>
        </div>
      </div>
      <div className="shell-main scroll-y">
        <div className="call-detail">
          <div className={`call-detail-hero ${missed ? 'missed' : call.direction}`}>
            <div className="call-detail-iconbig"><IconBig /></div>
            <div className="call-detail-peer">{fmtPhone(peer)}</div>
            <div className="call-detail-dir">{dirLabel}</div>
            <div className="call-detail-date">{dateStr}</div>
          </div>

          {hasVm && (
            <div className="call-detail-section">
              <div className="call-detail-label">Message vocal</div>
              <AudioPlayer src={call.recording_url} />
            </div>
          )}

          <div className="call-detail-section">
            <div className="call-detail-label">Informations</div>
            <div className="call-detail-rows">
              <div className="call-detail-row"><span>Statut</span><strong>{CALL_STATUS_LABEL[call.status] || call.status || '—'}</strong></div>
              {call.duration != null && <div className="call-detail-row"><span>Durée</span><strong>{fmtDuration(call.duration)}</strong></div>}
              {call.line_phone && <div className="call-detail-row"><span>Ligne</span><strong>{fmtPhone(call.line_phone)}</strong></div>}
              {call.from_number && <div className="call-detail-row"><span>De</span><strong>{fmtPhone(call.from_number)}</strong></div>}
              {call.to_number && <div className="call-detail-row"><span>Vers</span><strong>{fmtPhone(call.to_number)}</strong></div>}
              {call.call_sid && <div className="call-detail-row"><span>SID</span><strong className="mono">{call.call_sid}</strong></div>}
            </div>
          </div>

          {call.transcript && (
            <div className="call-detail-section">
              <div className="call-detail-label">Transcription</div>
              <div className="call-detail-transcript">{call.transcript}</div>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

// ==================== CONTACTS ====================
function ContactsView({ onOpenConv }) {
  const { notify, lineId } = useApp();
  const [contacts, setContacts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [search, setSearch] = useState('');
  const [favorites, setFavorites] = useState(() => loadFavorites());

  useEffect(() => {
    let cancelled = false;
    (async () => {
      setLoading(true);
      try {
        const [convs, callsR] = await Promise.all([
          fetchUnifiedConversations(lineId),
          api.get(`/my/calls?page=1&limit=100${lineId ? `&line_id=${lineId}` : ''}`),
        ]);
        const map = new Map();
        const upsert = (key, data) => {
          const prev = map.get(key);
          if (!prev || parseTs(data.last_at) >= parseTs(prev.last_at)) map.set(key, { key, ...prev, ...data });
          else map.set(key, { key, ...prev, ...data, last_at: prev.last_at, preview: prev.preview });
        };
        convs.forEach(c => {
          const key = c.channel === 'widget' ? `widget:${c.contact_number}` : `tel:${c.contact_number}`;
          upsert(key, {
            name: c.contact_name,
            phone: c.contact_number,
            channel: c.channel,
            preview: c.last_message_body,
            last_at: c.last_message_at,
            conv: c,
            type: 'chat',
          });
        });
        (callsR?.calls || []).forEach(call => {
          const peer = callPeer(call);
          if (!peer) return;
          upsert(`tel:${peer}`, {
            name: fmtPhone(peer),
            phone: peer,
            channel: 'phone',
            preview: `Appel ${call.direction === 'inbound' ? 'entrant' : 'sortant'}`,
            last_at: call.created_at,
            type: 'call',
          });
        });
        if (!cancelled) setContacts([...map.values()].sort((a, b) => parseTs(b.last_at) - parseTs(a.last_at)));
      } catch { if (!cancelled) notify('Erreur chargement contacts', 'error'); }
      finally { if (!cancelled) setLoading(false); }
    })();
    return () => { cancelled = true; };
  }, [lineId, notify]);

  const toggleFavorite = (key) => {
    const next = new Set(favorites);
    if (next.has(key)) next.delete(key); else next.add(key);
    setFavorites(next);
    saveFavorites(next);
  };

  const filtered = useMemo(() => {
    let list = contacts;
    if (search.trim()) {
      const s = search.toLowerCase();
      list = list.filter(c =>
        (c.name || '').toLowerCase().includes(s) ||
        (c.phone || '').includes(s)
      );
    }
    return list;
  }, [contacts, search]);

  const favs = filtered.filter(c => favorites.has(c.key));
  const others = filtered.filter(c => !favorites.has(c.key));

  const renderRow = (c) => (
    <button key={c.key} className={`contact-row${c.conv ? '' : ' static'}`}
      onClick={() => c.conv && onOpenConv(c.conv)} disabled={!c.conv}>
      <div className={`conv-avatar ${avatarVariant(c.phone || c.name)}`} style={{ width: 44, height: 44, fontSize: '0.9rem' }}>
        {initials(c.name || c.phone)}
      </div>
      <div className="contact-row-main">
        <div className="contact-row-name">{c.name || fmtPhone(c.phone)}</div>
        <div className="contact-row-sub">{c.preview || fmtPhone(c.phone)}</div>
      </div>
      <button className={`contact-fav-btn${favorites.has(c.key) ? ' on' : ''}`}
        onClick={(e) => { e.stopPropagation(); toggleFavorite(c.key); }}
        aria-label={favorites.has(c.key) ? 'Retirer des favoris' : 'Ajouter aux favoris'}>
        <Icons.Star filled={favorites.has(c.key)} />
      </button>
    </button>
  );

  return (
    <div className="shell">
      <TopBarWithLineSelector subtitle="Clients & prospects" />
      <div className="shell-main scroll-y">
        <div className="search-bar">
          <input className="search-input" type="search" placeholder="Rechercher un contact…"
            value={search} onChange={e => setSearch(e.target.value)} autoFocus={false} />
        </div>
        {loading ? (
          <div className="empty-state"><div className="loading-spinner" style={{ width: 28, height: 28 }} /></div>
        ) : filtered.length === 0 ? (
          <div className="empty-state">
            <div className="empty-icon"><Icons.Users /></div>
            <div className="empty-title">Aucun contact</div>
            <div className="empty-desc">Vos contacts sont agrégés depuis les conversations et les appels.</div>
          </div>
        ) : (
          <div className="contacts-list">
            {favs.length > 0 && (
              <>
                <div className="section-label">Favoris</div>
                {favs.map(renderRow)}
              </>
            )}
            <div className="section-label">{favs.length ? 'Tous les contacts' : 'Contacts'}</div>
            {others.map(renderRow)}
          </div>
        )}
      </div>
    </div>
  );
}

// ==================== COMPTE ====================
function AccountView() {
  const { user, logout, lines } = useApp();
  const waLines = lines.filter(l => l.whatsapp_enabled !== 0).length;
  return (
    <div className="shell">
      <TopBarWithLineSelector subtitle="Compte" />
      <div className="shell-main scroll-y">
        <div className="settings-profile">
          <div className="settings-avatar">{initials(user?.name || user?.email)}</div>
          <div className="settings-profile-info">
            <div className="settings-name">{user?.name || user?.email}</div>
            <div className="settings-mail">{user?.email}</div>
          </div>
        </div>

        <div className="section-label">Paramètres</div>
        <div className="settings-group">
          <button className="drawer-row" onClick={() => {
            localStorage.removeItem(STORAGE_ARCHIVED);
            location.reload();
          }}>
            <div className="drawer-row-icon"><Icons.Archive /></div>
            <div className="drawer-row-text">
              <div className="drawer-row-label">Vider les archives</div>
              <div className="drawer-row-desc">Désarchiver toutes les conversations</div>
            </div>
          </button>
        </div>

        <div className="section-label">Numéros & intégrations</div>
        <div className="settings-group">
          <div className="account-stat-row">
            <span>Lignes actives</span><strong>{lines.length}</strong>
          </div>
          <div className="account-stat-row">
            <span>Lignes WhatsApp</span><strong>{waLines}</strong>
          </div>
          <button className="drawer-row" onClick={() => window.open('https://my.helvia.app', '_blank')}>
            <div className="drawer-row-icon"><Icons.Phone /></div>
            <div className="drawer-row-text">
              <div className="drawer-row-label">Gérer numéros & widgets</div>
              <div className="drawer-row-desc">my.helvia.app — lignes, widgets, templates</div>
            </div>
          </button>
        </div>

        <div className="section-label">Facturation & utilisateurs</div>
        <div className="settings-group">
          <button className="drawer-row" onClick={() => window.open('https://my.helvia.app', '_blank')}>
            <div className="drawer-row-icon"><Icons.Template /></div>
            <div className="drawer-row-text">
              <div className="drawer-row-label">Facturation & équipe</div>
              <div className="drawer-row-desc">Abonnements, factures, utilisateurs</div>
            </div>
          </button>
        </div>

        <div className="settings-group">
          <button className="drawer-row danger" onClick={logout}>
            <div className="drawer-row-icon"><Icons.Logout /></div>
            <div className="drawer-row-text">
              <div className="drawer-row-label">Déconnexion</div>
              <div className="drawer-row-desc">{user?.email}</div>
            </div>
          </button>
        </div>

        <div className="settings-foot">Helvia Live · v1.0</div>
      </div>
    </div>
  );
}

// ==================== NOUVEAU (actions) ====================
function NewActionSheet({ onClose, onMessage, onCalls }) {
  return (
    <div className="tpl-sheet-backdrop" onClick={onClose}>
      <div className="action-sheet" onClick={e => e.stopPropagation()}>
        <div className="action-sheet-head">
          <div className="tpl-sheet-title">Nouveau</div>
          <button className="tpl-sheet-close" onClick={onClose}>✕</button>
        </div>
        <button className="action-row" onClick={() => { onClose(); onMessage(); }}>
          <div className="action-row-icon"><Icons.Chat /></div>
          <div className="action-row-text">
            <div className="action-row-label">Nouveau message</div>
            <div className="action-row-desc">WhatsApp — choisir un contact existant</div>
          </div>
        </button>
        <button className="action-row" onClick={() => { onClose(); onCalls(); }}>
          <div className="action-row-icon"><Icons.Phone /></div>
          <div className="action-row-text">
            <div className="action-row-label">Voir les appels</div>
            <div className="action-row-desc">Historique entrants, sortants, manqués</div>
          </div>
        </button>
        <button className="action-row" onClick={() => window.open('https://my.helvia.app', '_blank')}>
          <div className="action-row-icon"><Icons.Globe /></div>
          <div className="action-row-text">
            <div className="action-row-label">Ouvrir my.helvia.app</div>
            <div className="action-row-desc">Templates, widgets, facturation</div>
          </div>
        </button>
      </div>
    </div>
  );
}

// ==================== DIALER (pavé tactile) ====================
function DialerView({ onClose }) {
  const { notify } = useApp();
  const [num, setNum] = useState('');
  const keys = ['1','2','3','4','5','6','7','8','9','*','0','#'];
  const push = (k) => setNum(n => (n + k).slice(0, 20));
  const back = () => setNum(n => n.slice(0, -1));
  const call = () => {
    if (!num.trim()) return;
    window.location.href = `tel:${num}`;
  };
  return (
    <div className="shell">
      <div className="topbar gradient safe-top">
        <div className="safe-extend" />
        <button className="topbar-back" onClick={onClose}><Icons.Back /></button>
        <div className="topbar-title">
          <div className="topbar-name">Composer un numéro</div>
          <div className="topbar-sub">Pavé tactile</div>
        </div>
      </div>
      <div className="shell-main dialer-wrap">
        <div className="dialer-display">
          <div className="dialer-num">{num || <span className="dialer-placeholder">Entrez un numéro</span>}</div>
          {num && <button className="dialer-back" onClick={back} aria-label="Effacer">⌫</button>}
        </div>
        <div className="dialer-grid">
          {keys.map(k => (
            <button key={k} className="dialer-key" onClick={() => push(k)}>{k}</button>
          ))}
        </div>
        <div className="dialer-actions">
          <button className="dialer-call" onClick={call} disabled={!num.trim()}>
            <Icons.Phone /> Appeler
          </button>
        </div>
      </div>
    </div>
  );
}

// ==================== NEW CONTACT ====================
function NewContactView({ onClose, onSaved }) {
  const { notify } = useApp();
  const [name, setName] = useState('');
  const [phone, setPhone] = useState('');
  const save = () => {
    const n = name.trim(); const p = phone.trim();
    if (!n || !p) { notify('Nom et numéro requis', 'error'); return; }
    try {
      const raw = localStorage.getItem('helvia_live_contacts') || '[]';
      const list = JSON.parse(raw);
      list.unshift({ id: 'local-' + Date.now(), name: n, phone: p, created_at: new Date().toISOString() });
      localStorage.setItem('helvia_live_contacts', JSON.stringify(list));
      notify('Contact ajouté', 'success');
      onSaved && onSaved();
      onClose();
    } catch (e) { notify('Erreur', 'error'); }
  };
  return (
    <div className="shell">
      <div className="topbar gradient safe-top">
        <div className="safe-extend" />
        <button className="topbar-back" onClick={onClose}><Icons.Back /></button>
        <div className="topbar-title">
          <div className="topbar-name">Nouveau contact</div>
          <div className="topbar-sub">Ajout local</div>
        </div>
      </div>
      <div className="shell-main" style={{ padding: '1rem' }}>
        <div className="form-field">
          <label className="form-label">Nom</label>
          <input className="form-input" value={name} onChange={e => setName(e.target.value)} placeholder="Jean Dupont" autoFocus />
        </div>
        <div className="form-field">
          <label className="form-label">Numéro</label>
          <input className="form-input" type="tel" value={phone} onChange={e => setPhone(e.target.value)} placeholder="+41 79 123 45 67" />
        </div>
        <button className="btn-brand btn-block" onClick={save}>Enregistrer</button>
      </div>
    </div>
  );
}

// ==================== NEW MESSAGE (selector + dialer) ====================
function NewMessageView({ onClose, onOpenConv }) {
  const { notify, lineId } = useApp();
  const [convs, setConvs] = useState([]);
  const [loading, setLoading] = useState(true);
  const [search, setSearch] = useState('');

  useEffect(() => {
    fetchUnifiedConversations(lineId).then(list => {
      setConvs(list.filter(c => c.channel === 'whatsapp'));
    }).finally(() => setLoading(false));
  }, [lineId]);

  const filtered = useMemo(() => {
    if (!search.trim()) return convs;
    const s = search.toLowerCase();
    return convs.filter(c =>
      (c.contact_name || '').toLowerCase().includes(s) ||
      (c.contact_number || '').includes(s)
    );
  }, [convs, search]);

  return (
    <div className="shell">
      <div className="topbar gradient safe-top">
        <div className="safe-extend" />
        <button className="topbar-back" onClick={onClose}><Icons.Back /></button>
        <div className="topbar-title">
          <div className="topbar-name">Nouveau message</div>
          <div className="topbar-sub">Choisir un contact</div>
        </div>
      </div>
      <div className="shell-main">
        <div className="search-bar">
          <input className="search-input" type="search" placeholder="Rechercher un contact…"
            value={search} onChange={e => setSearch(e.target.value)} autoFocus />
        </div>
        {loading ? (
          <div className="empty-state"><div className="loading-spinner" style={{ width: 28, height: 28 }} /></div>
        ) : filtered.length === 0 ? (
          <div className="empty-state">
            <div className="empty-icon"><Icons.Chat /></div>
            <div className="empty-title">Aucun contact</div>
            <div className="empty-desc">L'envoi vers un nouveau numéro nécessite un template WhatsApp approuvé. Utilisez my.helvia.app pour gérer vos templates.</div>
          </div>
        ) : (
          <ConvList items={filtered} onOpen={(c) => { onClose(); onOpenConv(c); }} onArchive={() => {}} archiveLabel="" />
        )}
      </div>
    </div>
  );
}

// ==================== BOTTOM NAV ====================
const FAB_BY_TAB = {
  conversations: { icon: 'Edit',     label: 'Message', aria: 'Nouveau message' },
  calls:         { icon: 'Dialpad',  label: 'Appeler', aria: 'Composer un numéro' },
  contacts:      { icon: 'UserPlus', label: 'Contact', aria: 'Ajouter un contact' },
  account:       { icon: 'Plus',     label: 'Nouveau', aria: 'Nouveau' },
};
function BottomNav({ tab, onTab, onNew }) {
  const fab = FAB_BY_TAB[tab] || FAB_BY_TAB.account;
  const FabIcon = Icons[fab.icon];
  return (
    <div className="bottom-nav safe-bottom">
      <button className={`nav-tab ${tab === 'conversations' ? 'active' : ''}`} onClick={() => onTab('conversations')}>
        <Icons.Chat />
        <span>Conversations</span>
      </button>
      <button className={`nav-tab ${tab === 'calls' ? 'active' : ''}`} onClick={() => onTab('calls')}>
        <Icons.Phone />
        <span>Appels</span>
      </button>
      <div className="nav-fab-wrap">
        <button key={tab} className={`nav-fab fab-${tab}`} onClick={onNew} aria-label={fab.aria}>
          <FabIcon />
        </button>
      </div>
      <button className={`nav-tab ${tab === 'contacts' ? 'active' : ''}`} onClick={() => onTab('contacts')}>
        <Icons.Users />
        <span>Contacts</span>
      </button>
      <button className={`nav-tab ${tab === 'account' ? 'active' : ''}`} onClick={() => onTab('account')}>
        <Icons.Settings />
        <span>Compte</span>
      </button>
    </div>
  );
}

// ==================== WIDGET THREAD ====================
function WidgetThreadView({ conv, onBack }) {
  const { notify } = useApp();
  const [msgs, setMsgs] = useState([]);
  const [loading, setLoading] = useState(true);
  const scrollRef = useRef(null);

  useEffect(() => {
    setLoading(true);
    api.get(`/my/widgets/${conv.widgetId}/conversations/${conv.id}/messages`)
      .then(r => {
        if (r?.error) notify(r.error, 'error');
        else setMsgs(r.messages || []);
      })
      .catch(() => notify('Erreur réseau', 'error'))
      .finally(() => setLoading(false));
  }, [conv.widgetId, conv.id, notify]);

  useEffect(() => {
    requestAnimationFrame(() => {
      if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
    });
  }, [msgs, loading]);

  const name = conv.contact_name || conv.widgetName || 'Widget web';

  return (
    <div className="thread-screen">
      <div className="topbar safe-top">
        <div className="safe-extend" />
        <button className="topbar-back" onClick={onBack}><Icons.Back /></button>
        <div className="topbar-avatar">{initials(name)}</div>
        <div className="topbar-title">
          <div className="topbar-name">{name}</div>
          <div className="topbar-sub">{conv.widgetName || 'Widget'} · lecture seule</div>
        </div>
      </div>
      <div className="thread-bg" ref={scrollRef}>
        {loading ? (
          <div className="empty-state"><div className="loading-spinner" style={{ width: 28, height: 28 }} /></div>
        ) : msgs.length === 0 ? (
          <div className="empty-state"><div className="empty-desc">Aucun message.</div></div>
        ) : msgs.map(m => {
          const out = m.role === 'assistant';
          return (
            <div key={m.id} className={`msg-row ${out ? 'out' : 'in'}`}>
              <div className="msg-bubble">
                <div className="msg-text">{m.content}</div>
                <span className="msg-foot">{fmtTime(m.created_at)}{out && <span className="msg-by-bot"> bot</span>}</span>
              </div>
            </div>
          );
        })}
      </div>
      <div className="thread-readonly-banner">
        Conversation widget — répondez depuis my.helvia.app si la prise en main humaine est activée.
      </div>
    </div>
  );
}

// ==================== THREAD ====================
function ThreadView({ conv, onBack }) {
  if (conv.channel === 'widget') return <WidgetThreadView conv={conv} onBack={onBack} />;
  const { notify } = useApp();
  const [msgs, setMsgs]       = useState([]);
  const [loading, setLoading] = useState(true);
  const [draft, setDraft]     = useState('');
  const [sending, setSending] = useState(false);
  const [botEnabled, setBot]  = useState(conv.bot_enabled === 1);
  const [data, setData]       = useState(conv);
  const [windowOpen, setWindow] = useState(true);
  const [templates, setTemplates] = useState([]);
  const [tplPickerOpen, setTplPicker] = useState(false);
  const [tplSending, setTplSending] = useState(false);
  const scrollRef = useRef(null);

  const load = useCallback(async () => {
    setLoading(true);
    try {
      const r = await api.get(`/my/whatsapp/conversations/${conv.id}/messages`);
      if (r?.error) notify(r.error, 'error');
      else {
        setMsgs(r.messages || []);
        if (r.conversation) {
          setData(r.conversation);
          setBot(r.conversation.bot_enabled === 1);
          // Fenêtre 24h : si dernier message inbound > 24h on le notifie.
          if (r.conversation.last_inbound_at) {
            const last = new Date(r.conversation.last_inbound_at.replace(' ', 'T') + 'Z');
            setWindow((Date.now() - last.getTime()) < 24 * 3600 * 1000);
          }
        }
      }
    } catch { notify('Erreur réseau', 'error'); }
    finally { setLoading(false); }
  }, [conv.id, notify]);

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

  useEffect(() => {
    const t = setInterval(load, 15000);
    return () => clearInterval(t);
  }, [load]);

  // Charge les templates approuvés + l'état réel de la fenêtre 24h.
  useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        const r = await api.get(`/my/whatsapp/conversations/${conv.id}/templates`);
        if (cancelled || r?.error) return;
        setTemplates(r.templates || []);
        if (typeof r.window_open === 'boolean') setWindow(r.window_open);
      } catch {}
    })();
    return () => { cancelled = true; };
  }, [conv.id]);

  const sendTemplate = async (tpl, variables) => {
    if (tplSending) return;
    setTplSending(true);
    try {
      const r = await api.post(`/my/whatsapp/conversations/${conv.id}/reply`,
        { template_id: tpl.id, variables, take_over: botEnabled ? 1 : 0 });
      if (r?.error) {
        const detail = r?.detail?.message || r?.detail?.error_message;
        const code = r?.detail?.code;
        notify(detail ? `${r.error}${code ? ` (${code})` : ''}: ${detail}` : r.error, 'error');
      } else {
        setTplPicker(false);
        setBot(false);
        notify('Template envoyé', 'success');
        load();
      }
    } catch (e) { notify('Erreur envoi template: ' + (e?.message || 'réseau'), 'error'); }
    finally { setTplSending(false); }
  };

  useEffect(() => {
    requestAnimationFrame(() => {
      if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
    });
  }, [msgs, loading]);

  const send = async () => {
    const text = draft.trim();
    if (!text || sending) return;
    setSending(true);
    try {
      const r = await api.post(`/my/whatsapp/conversations/${conv.id}/reply`,
        { body: text, take_over: botEnabled ? 1 : 0 });
      if (r?.error) {
        const detail = r?.detail?.message || r?.detail?.error_message;
        const code = r?.detail?.code;
        notify(detail ? `${r.error}${code ? ` (${code})` : ''}: ${detail}` : r.error, 'error');
        console.warn('[WA reply] error', r);
      } else {
        setDraft('');
        setBot(false);
        load();
      }
    } catch (e) { notify('Erreur envoi: ' + (e?.message || 'réseau'), 'error'); }
    finally { setSending(false); }
  };

  const toggleBot = async () => {
    const next = !botEnabled;
    setBot(next);
    try {
      const r = await api.post(`/my/whatsapp/conversations/${conv.id}/bot`, { enabled: next ? 1 : 0 });
      if (r?.error) { setBot(!next); notify(r.error, 'error'); }
      else notify(next ? 'Bot activé pour ce contact' : 'Bot désactivé — vous prenez la main', 'success');
    } catch { setBot(!next); notify('Erreur', 'error'); }
  };

  const name = data.contact_name || data.contact_number;
  const variant = avatarVariant(data.contact_number || name);

  // Group msgs by day
  const grouped = useMemo(() => {
    const out = [];
    let lastDay = null;
    msgs.forEach(m => {
      const day = (m.created_at || '').slice(0, 10);
      if (day !== lastDay) {
        out.push({ kind: 'day', day, key: 'd' + day + Math.random() });
        lastDay = day;
      }
      out.push({ kind: 'msg', m, key: m.id });
    });
    return out;
  }, [msgs]);

  return (
    <div className="thread-screen">
      <div className="topbar safe-top">
        <div className="safe-extend" />
        <button className="topbar-back" onClick={onBack}><Icons.Back /></button>
        <div className={`topbar-avatar ${variant}`} style={{ width: 36, height: 36, fontSize: '0.85rem' }}>
          {initials(name)}
        </div>
        <div className="topbar-title">
          <div className="topbar-name">{name}</div>
          <div className="topbar-sub">
            {data.contact_number}
            {data.line_phone ? ` · via ${data.line_phone}` : ''}
          </div>
        </div>
        <div className="topbar-actions">
          <button className={`topbar-icon-btn${botEnabled ? ' active-bot' : ''}`} onClick={toggleBot}
            title={botEnabled ? 'Bot actif (cliquer pour reprendre)' : 'Bot inactif (cliquer pour activer)'}>
            <Icons.Bot />
          </button>
        </div>
      </div>

      {!windowOpen && (
        <div className="thread-banner">
          <span>Fenêtre 24h dépassée — utilisez un template approuvé.</span>
          <button className="thread-banner-btn" onClick={() => setTplPicker(true)}>
            Choisir un template ({templates.length})
          </button>
        </div>
      )}

      <div className="thread-bg" ref={scrollRef}>
        {loading && (
          <div className="empty-state">
            <div className="loading-spinner" style={{ width: 28, height: 28 }} />
          </div>
        )}
        {!loading && grouped.length === 0 && (
          <div className="empty-state">
            <div className="empty-desc">Aucun message encore.</div>
          </div>
        )}
        {grouped.map(item => {
          if (item.kind === 'day') {
            return <div key={item.key} className="day-sep"><span>{fmtDay(item.day)}</span></div>;
          }
          const m = item.m;
          const out = m.direction === 'outbound';
          let media = null;
          if (m.media_urls) {
            try {
              const urls = JSON.parse(m.media_urls);
              media = urls.map((u, i) => (
                <a key={i} className="msg-media-link" href={u} target="_blank" rel="noreferrer">📎 Pièce jointe {i + 1}</a>
              ));
            } catch {}
          }
          const isRead = m.status === 'read';
          const isDelivered = m.status === 'delivered' || isRead;
          return (
            <div key={item.key} className={`msg-row ${out ? 'out' : 'in'}`}>
              <div className="msg-bubble">
                <div className="msg-text">{m.body || (m.num_media > 0 ? '📎 Média' : '')}</div>
                {media}
                <span className="msg-foot">
                  {m.sent_by === 'bot' && <span className="msg-by-bot">bot</span>}
                  {fmtTime(m.created_at)}
                  {out && (
                    <span className={`msg-status ${isRead ? 'read' : ''}`}>
                      {isDelivered ? <Icons.DoubleCheck /> : <Icons.Check />}
                    </span>
                  )}
                </span>
              </div>
            </div>
          );
        })}
      </div>

      <div className="compose-wrap">
        <button className="compose-tpl" onClick={() => setTplPicker(true)}
          title={`Templates approuvés (${templates.length})`}>
          <Icons.Template />
        </button>
        <div className="compose-input-wrap">
          <textarea className="compose-input" rows={1}
            placeholder={windowOpen ? 'Message' : 'Fenêtre fermée — utilisez un template'}
            value={draft}
            onChange={e => setDraft(e.target.value)}
            onKeyDown={e => {
              if (e.key === 'Enter' && !e.shiftKey && !e.metaKey) {
                e.preventDefault();
                send();
              }
            }} />
        </div>
        <button className="compose-send" disabled={sending || !draft.trim()} onClick={send}>
          <Icons.Send />
        </button>
      </div>

      {tplPickerOpen && (
        <TemplatePicker
          templates={templates}
          onClose={() => setTplPicker(false)}
          onSend={sendTemplate}
          sending={tplSending}
        />
      )}
    </div>
  );
}

// ==================== TEMPLATE PICKER ====================
function TemplatePicker({ templates, onClose, onSend, sending }) {
  const [selected, setSelected] = useState(null);
  const [vars, setVars] = useState({});

  // Variables détectées dans le body : {{1}}, {{2}}, ...
  const varKeys = useMemo(() => {
    if (!selected?.body) return [];
    const set = new Set();
    selected.body.replace(/\{\{\s*(\w+)\s*\}\}/g, (_, k) => { set.add(k); return ''; });
    return Array.from(set).sort((a, b) => Number(a) - Number(b));
  }, [selected]);

  const preview = useMemo(() => {
    if (!selected?.body) return '';
    return selected.body.replace(/\{\{\s*(\w+)\s*\}\}/g, (m, k) => vars[k] || m);
  }, [selected, vars]);

  const canSend = !!selected && varKeys.every(k => (vars[k] || '').trim().length > 0);

  return (
    <div className="tpl-sheet-backdrop" onClick={onClose}>
      <div className="tpl-sheet" onClick={e => e.stopPropagation()}>
        <div className="tpl-sheet-head">
          <div className="tpl-sheet-title">{selected ? 'Préparer le template' : 'Choisir un template'}</div>
          <button className="tpl-sheet-close" onClick={onClose}>✕</button>
        </div>

        {!selected && (
          <div className="tpl-list">
            {templates.length === 0 && (
              <div className="tpl-empty">
                Aucun template approuvé. Créez et faites approuver un template depuis my.helvia.app.
              </div>
            )}
            {templates.map(t => (
              <button key={t.id} className="tpl-row" onClick={() => { setSelected(t); setVars({}); }}>
                <div className="tpl-row-head">
                  <span className="tpl-row-name">{t.friendly_name}</span>
                  <span className="tpl-row-tag">{t.category || 'utility'} · {t.language || 'fr'}</span>
                </div>
                <div className="tpl-row-body">{t.body}</div>
              </button>
            ))}
          </div>
        )}

        {selected && (
          <div className="tpl-form">
            <div className="tpl-form-head">
              <button className="tpl-back" onClick={() => setSelected(null)}>‹ Retour</button>
              <span className="tpl-row-tag">{selected.category || 'utility'} · {selected.language || 'fr'}</span>
            </div>
            <div className="tpl-name">{selected.friendly_name}</div>

            {varKeys.length > 0 && (
              <div className="tpl-vars">
                {varKeys.map(k => (
                  <label key={k} className="tpl-var">
                    <span>Variable {`{{${k}}}`}</span>
                    <input
                      value={vars[k] || ''}
                      onChange={e => setVars(v => ({ ...v, [k]: e.target.value }))}
                      placeholder={`Valeur pour {{${k}}}`}
                    />
                  </label>
                ))}
              </div>
            )}

            <div className="tpl-preview-label">Aperçu</div>
            <div className="tpl-preview">{preview}</div>

            <button
              className="tpl-send"
              disabled={!canSend || sending}
              onClick={() => onSend(selected, vars)}>
              {sending ? 'Envoi…' : 'Envoyer le template'}
            </button>
          </div>
        )}
      </div>
    </div>
  );
}

// ==================== ROOT ====================
function AppProvider({ children, value }) {
  return <AppCtx.Provider value={value}>{children}</AppCtx.Provider>;
}

const STORAGE_LINE = 'helvia_live_line';

function App() {
  const [user, setUser]   = useState(null);
  const [booting, setBoot]= useState(true);
  const [toast, setToast] = useState(null);
  const [selected, setSelected] = useState(null);
  const [tab, setTab] = useState('conversations');
  const [composing, setComposing] = useState(false);
  const [newOpen, setNewOpen] = useState(false);
  const [dialerOpen, setDialerOpen] = useState(false);
  const [contactOpen, setContactOpen] = useState(false);
  const [selectedCall, setSelectedCall] = useState(null);
  const [contactsRev, setContactsRev] = useState(0);
  const handleFab = () => {
    if (tab === 'conversations') setComposing(true);
    else if (tab === 'calls') setDialerOpen(true);
    else if (tab === 'contacts') setContactOpen(true);
    else setNewOpen(true);
  };
  const [lines, setLines] = useState([]);
  const [lineId, setLineIdState] = useState(() => {
    const v = localStorage.getItem(STORAGE_LINE);
    return v ? parseInt(v) : null;
  });
  const setLineId = useCallback((id) => {
    setLineIdState(id);
    if (id) localStorage.setItem(STORAGE_LINE, String(id));
    else localStorage.removeItem(STORAGE_LINE);
  }, []);

  const notify = useCallback((msg, type) => setToast({ msg, type }), []);

  const handleLogin = useCallback((token, u) => {
    api.token = token;
    localStorage.setItem(STORAGE_TOKEN, token);
    localStorage.setItem(STORAGE_USER, JSON.stringify(u));
    setUser(u);
  }, []);

  const logout = useCallback(() => {
    api.token = null;
    localStorage.removeItem(STORAGE_TOKEN);
    localStorage.removeItem(STORAGE_USER);
    setUser(null); setSelected(null);
    notify('Déconnecté', 'success');
  }, [notify]);

  useEffect(() => {
    const t = localStorage.getItem(STORAGE_TOKEN);
    const u = localStorage.getItem(STORAGE_USER);
    if (t && u) {
      api.token = t;
      try { setUser(JSON.parse(u)); } catch {}
      api.get('/auth/me').then(r => {
        if (r?.user) setUser(prev => ({ ...prev, ...r.user }));
        else if (r?.error) { api.token = null; localStorage.removeItem(STORAGE_TOKEN); localStorage.removeItem(STORAGE_USER); setUser(null); }
      }).catch(() => {});
    }
    setBoot(false);
    const onLogout = () => { setUser(null); setSelected(null); };
    window.addEventListener('helvia-live-logout', onLogout);
    return () => window.removeEventListener('helvia-live-logout', onLogout);
  }, []);

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

  if (booting) {
    return <div className="app-loading"><div className="loading-spinner" /></div>;
  }

  const showBottomNav = user && !selected && !selectedCall;

  return (
    <AppProvider value={{ user, notify, logout, lines, lineId, setLineId }}>
      <div className={`app-root ${showBottomNav ? 'has-bottom-nav' : ''}`}>
        {!user ? (
          <AuthScreen onLogin={handleLogin} />
        ) : selected ? (
          <ThreadView conv={selected} onBack={() => setSelected(null)} />
        ) : composing ? (
          <NewMessageView onClose={() => setComposing(false)} onOpenConv={setSelected} />
        ) : dialerOpen ? (
          <DialerView onClose={() => setDialerOpen(false)} />
        ) : contactOpen ? (
          <NewContactView onClose={() => setContactOpen(false)} onSaved={() => setContactsRev(r => r + 1)} />
        ) : tab === 'conversations' ? (
          <InboxView onOpen={setSelected} />
        ) : tab === 'calls' ? (
          selectedCall ? <CallDetailView call={selectedCall} onBack={() => setSelectedCall(null)} />
                       : <CallsView onOpenCall={setSelectedCall} />
        ) : tab === 'contacts' ? (
          <ContactsView onOpenConv={(c) => { setTab('conversations'); setSelected(c); }} />
        ) : tab === 'account' ? (
          <AccountView />
        ) : null}
        {showBottomNav && (
          <BottomNav tab={tab} onTab={setTab} onNew={handleFab} />
        )}
      </div>
      {newOpen && (
        <NewActionSheet
          onClose={() => setNewOpen(false)}
          onMessage={() => setComposing(true)}
          onCalls={() => setTab('calls')}
        />
      )}
      {toast && <Toast msg={toast.msg} type={toast.type} onDone={() => setToast(null)} />}
    </AppProvider>
  );
}

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