// Vival PWA — Screens
const { RAYONS, PRODUCTS, CUSTOMERS, ORDERS } = VivalData;

// ====== App state context ======
const AppCtx = createContext(null);

function useApp() { return useContext(AppCtx); }

function AppProvider({ children }) {
  // Restaure la derniere route visitee (sinon catalog par defaut)
  const initialRoute = (() => {
    try {
      const saved = localStorage.getItem('vival_last_route');
      // Liste blanche: routes "stables" qu'on autorise a la restauration
      // (on evite 'print' / 'unknown-barcode' / etc qui sont transitoires)
      const allowed = ['catalog', 'orders', 'customers', 'delivery', 'dashboard',
        'temperatures', 'admin-catalog', 'reports', 'marketplace', 'settings'];
      if (saved && allowed.includes(saved)) return saved;
    } catch (_) {}
    return 'catalog';
  })();
  const [route, _setRoute] = useState(initialRoute);
  const setRoute = (r) => {
    _setRoute(r);
    try { localStorage.setItem('vival_last_route', r); } catch (_) {}
  };
  const [cart, setCart] = useState([]); // [{pid, qty}]
  const [customer, setCustomer] = useState(null);
  const [selectedProduct, setSelectedProduct] = useState(null);
  const [scanOpen, setScanOpen] = useState(false);
  const [cartDrawer, setCartDrawer] = useState(false);
  const [customerPicker, setCustomerPicker] = useState(false);
  const [orders, setOrders] = useState(ORDERS);
  const [selectedOrder, setSelectedOrder] = useState(null);
  const [printOrder, setPrintOrder] = useState(null);
  const [toast, setToast] = useState(null);
  const [lastScanAt, setLastScanAt] = useState(null);
  const pendingScanRef = useRef(null);
  const [unknownBarcode, setUnknownBarcode] = useState(null);
  const [receivingOpen, setReceivingOpen] = useState(false);

  // Tour guidé onboarding
  const [tourActive, setTourActive] = useState(false);
  const [tourStepIndex, setTourStepIndex] = useState(0);
  const startTour = () => { setTourStepIndex(0); setTourActive(true); };
  const stopTour = () => {
    setTourActive(false);
    try { localStorage.setItem('vival_tour_done_v1', '1'); } catch {}
  };
  const nextStep = () => setTourStepIndex(i => i + 1);
  const prevStep = () => setTourStepIndex(i => Math.max(0, i - 1));

  const showToast = (msg) => { setToast(msg); setTimeout(() => setToast(null), 2000); };
  const notifyScan = () => setLastScanAt(Date.now());
  const setPendingScanCallback = (cb) => { pendingScanRef.current = cb; };

  const addToCart = (pid, qty = 1) => {
    const p = PRODUCTS.find(x => x.id === pid);
    if (p && !p.byWeight && (p.stock || 0) <= 0) {
      showToast(`${p.name} — rupture de stock`);
      return;
    }
    setCart(prev => {
      const existing = prev.find(l => l.pid === pid);
      if (existing) return prev.map(l => l.pid === pid ? { ...l, qty: l.qty + qty } : l);
      return [...prev, { pid, qty }];
    });
    showToast(`${p?.name} ajouté`);
  };
  const addManualLine = ({ name, price, tvaRate = 0.20 }) => {
    const trimmed = (name || '').trim();
    if (!trimmed) return;
    const pid = `manual-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
    setCart(prev => [...prev, { pid, qty: 1, manual: true, name: trimmed, price: Number(price) || 0, tvaRate: Number(tvaRate) || 0.20 }]);
    showToast(`${trimmed} ajouté`);
  };
  const updateQty = (pid, qty) => {
    if (qty <= 0) setCart(prev => prev.filter(l => l.pid !== pid));
    else setCart(prev => prev.map(l => l.pid === pid ? { ...l, qty } : l));
  };
  const updateLinePrice = (pid, newPrice) => {
    // newPrice null => restaure le prix catalogue
    setCart(prev => prev.map(l => {
      if (l.pid !== pid) return l;
      if (l.manual) return { ...l, price: Number(newPrice) || 0 };
      if (newPrice == null || newPrice === '') { const { price, ...rest } = l; return rest; }
      return { ...l, price: Number(newPrice) || 0 };
    }));
  };
  const clearCart = () => { setCart([]); setCustomer(null); };

  const cartTotal = useMemo(() =>
    cart.reduce((sum, l) => {
      if (l.manual) return sum + (l.price || 0) * l.qty;
      const p = PRODUCTS.find(x => x.id === l.pid);
      if (!p) return sum;
      const unit = l.price != null ? Number(l.price) : p.price;
      return sum + unit * l.qty;
    }, 0)
  , [cart]);

  const nextOrderId = () => {
    const maxNum = orders.reduce((m, o) => {
      const n = parseInt((o.id || '').split('-')[1]) || 0;
      return n > m ? n : m;
    }, 2470);
    return `V-${maxNum + 1}`;
  };

  const validateOrder = async (payment, extras = {}) => {
    if (cart.length === 0) return;
    const { discountPct = 0, notes = '' } = extras;
    // Calcul totaux
    let ttc = 0, ht = 0, tva = 0;
    const lines = cart.map((l) => {
      if (l.manual) {
        const rate = Number(l.tvaRate) || 0.20;
        const unit = Number(l.price) || 0;
        const lineTtc = unit * l.qty;
        const lineHt = lineTtc / (1 + rate);
        ttc += lineTtc; ht += lineHt; tva += (lineTtc - lineHt);
        return { pid: l.pid, qty: l.qty, unitPrice: unit, tvaRate: rate, name: l.name, brand: null, format: null, manual: true };
      }
      const p = PRODUCTS.find((x) => x.id === l.pid) || {};
      const rate = p.tvaRate ?? 0.055;
      const unit = l.price != null ? Number(l.price) : (p.price || 0);
      const lineTtc = unit * l.qty;
      const lineHt = lineTtc / (1 + rate);
      ttc += lineTtc; ht += lineHt; tva += (lineTtc - lineHt);
      return { pid: l.pid, qty: l.qty, unitPrice: unit, tvaRate: rate, name: p.name, brand: p.brand, format: p.format };
    });
    const discountAmount = ttc * (discountPct / 100);
    const discountedTtc = ttc - discountAmount;
    const discountedHt = ht - (ht * discountPct / 100);
    const discountedTva = tva - (tva * discountPct / 100);
    let cashier = null;
    try { cashier = JSON.parse(sessionStorage.getItem('vival_cashier') || 'null'); } catch {}
    const newOrder = {
      cashierId: cashier?.id || null,
      cashierName: cashier?.name || null,
      id: nextOrderId(),
      customerId: customer?.id || null,
      customerName: customer?.name || 'Client libre',
      lines,
      status: 'en-cours',
      createdAt: new Date().toISOString(),
      payment,
      notes: notes || null,
      discountPct,
      discountAmount: Number(discountAmount.toFixed(2)),
      totalTtc: Number(discountedTtc.toFixed(2)),
      totalHt: Number(discountedHt.toFixed(2)),
      totalTva: Number(discountedTva.toFixed(2)),
    };
    setOrders([newOrder, ...orders]);
    setPrintOrder(newOrder);
    setRoute('print');
    clearCart();
    // Décrément stock local + Supabase
    const updatedProducts = PRODUCTS.map(p => {
      const line = lines.find(l => !l.manual && l.pid === p.id);
      if (!line || p.byWeight) return p;
      return { ...p, stock: Math.max(0, (p.stock || 0) - line.qty) };
    });
    VivalData.PRODUCTS.splice(0, VivalData.PRODUCTS.length, ...updatedProducts);
    // Loyalty: +1 point par euro dépensé
    if (customer?.id) {
      const pts = Math.floor(newOrder.totalTtc);
      const newTotal = (customer.loyaltyPoints || 0) + pts;
      const idx = VivalData.CUSTOMERS.findIndex(c => c.id === customer.id);
      if (idx !== -1) VivalData.CUSTOMERS[idx] = { ...VivalData.CUSTOMERS[idx], loyaltyPoints: newTotal };
      setCustomer({ ...customer, loyaltyPoints: newTotal });
      try { await window.sb.from('customers').update({ loyalty_points: newTotal }).eq('id', customer.id); } catch {}
    }
    try { await window.saveOrder(newOrder); }
    catch (e) { console.error('saveOrder', e); showToast('Commande non synchronisée : ' + e.message); }
    try {
      for (const l of lines) {
        const p = updatedProducts.find(x => x.id === l.pid);
        if (p && !p.byWeight) await window.sb.from('products').update({ stock: p.stock }).eq('id', p.id);
      }
    } catch (e) { console.error('stock sync', e); }
  };

  return (
    <AppCtx.Provider value={{
      route, setRoute,
      cart, setCart, addToCart, addManualLine, updateQty, updateLinePrice, clearCart, cartTotal,
      customer, setCustomer,
      selectedProduct, setSelectedProduct,
      scanOpen, setScanOpen,
      cartDrawer, setCartDrawer,
      customerPicker, setCustomerPicker,
      orders, setOrders,
      selectedOrder, setSelectedOrder,
      printOrder, setPrintOrder,
      validateOrder,
      toast,
      showToast,
      lastScanAt, notifyScan,
      pendingScanRef, setPendingScanCallback,
      unknownBarcode, setUnknownBarcode,
      receivingOpen, setReceivingOpen,
      tourActive, tourStepIndex, startTour, stopTour, nextStep, prevStep,
    }}>{children}</AppCtx.Provider>
  );
}

// ===== Sidebar + BottomNav =====
const NAV_ITEMS = [
  { id: 'dashboard', label: 'Accueil', icon: 'home' },
  { id: 'catalog', label: 'Vente', icon: 'shopping-cart' },
  { id: 'orders', label: 'Commandes', icon: 'list' },
  { id: 'delivery', label: 'Livraison', icon: 'truck' },
  { id: 'customers', label: 'Clients', icon: 'user' },
  { id: 'temperatures', label: 'Températures', icon: 'surgeles' },
  { id: 'admin-catalog', label: 'Catalogue', icon: 'economat' },
  { id: 'reports', label: 'Rapports', icon: 'tag' },
  { id: 'marketplace', label: 'Réseau', icon: 'star' },
  { id: 'settings', label: 'Réglages', icon: 'settings' },
];

// Niveaux d'alerte températures (par moment) :
// 0 = OK  | 1 = rappel doux (toast)  | 2 = alerte (bandeau permanent)  | 3 = blocage (modal plein écran)
// Seuils matin : 10h rappel / 11h30 alerte / 13h blocage
// Seuils aprem : 17h rappel / 18h30 alerte / 20h blocage
function computeTempLevel(hour, minute, moment) {
  const mins = hour * 60 + minute;
  if (moment === 'matin') {
    if (mins >= 13 * 60) return 3;
    if (mins >= 11 * 60 + 30) return 2;
    if (mins >= 10 * 60) return 1;
    return 0;
  } else {
    if (mins >= 20 * 60) return 3;
    if (mins >= 18 * 60 + 30) return 2;
    if (mins >= 17 * 60) return 1;
    return 0;
  }
}

// Web Audio API — bip généré en live (pas de fichier)
function playTempBeep(level) {
  try {
    const AC = window.AudioContext || window.webkitAudioContext;
    if (!AC) return;
    const ctx = new AC();
    const now = ctx.currentTime;
    // level 1 = 1 bip court, level 2 = 2 bips, level 3 = 4 bips plus forts
    const nBips = level === 3 ? 4 : level === 2 ? 2 : 1;
    const freq = level === 3 ? 1000 : level === 2 ? 820 : 660;
    const gain = level === 3 ? 0.35 : level === 2 ? 0.25 : 0.18;
    for (let i = 0; i < nBips; i++) {
      const o = ctx.createOscillator();
      const g = ctx.createGain();
      o.type = 'sine';
      o.frequency.value = freq;
      const start = now + i * 0.35;
      const end = start + 0.22;
      g.gain.setValueAtTime(0, start);
      g.gain.linearRampToValueAtTime(gain, start + 0.02);
      g.gain.setValueAtTime(gain, end - 0.05);
      g.gain.linearRampToValueAtTime(0, end);
      o.connect(g); g.connect(ctx.destination);
      o.start(start); o.stop(end);
    }
    setTimeout(() => ctx.close && ctx.close(), (nBips * 0.35 + 0.3) * 1000);
  } catch {}
}

function useTemperatureAlert() {
  const [state, setState] = useState({ matin: 0, aprem: 0 });
  useEffect(() => {
    let alive = true;
    const check = async () => {
      try {
        const storeId = window.VivalData?._storeId || null;
        let q = window.sb.from('temperature_equipments').select('id').eq('active', true);
        if (storeId) q = q.eq('store_id', storeId);
        const { data: equips } = await q;
        const equipIds = (equips || []).map(e => e.id);
        if (!equipIds.length) { if (alive) setState({ matin: 0, aprem: 0 }); return; }
        const today = new Date();
        const isoToday = today.getFullYear() + '-' + String(today.getMonth()+1).padStart(2,'0') + '-' + String(today.getDate()).padStart(2,'0');
        const { data: readings } = await window.sb.from('temperature_readings')
          .select('moment, equipment_id').in('equipment_id', equipIds).eq('reading_date', isoToday);
        const morningDone = new Set((readings || []).filter(r => r.moment === 'matin').map(r => r.equipment_id)).size >= equipIds.length;
        const aftDone = new Set((readings || []).filter(r => r.moment === 'aprem').map(r => r.equipment_id)).size >= equipIds.length;
        const h = today.getHours(), mi = today.getMinutes();
        if (alive) setState({
          matin: morningDone ? 0 : computeTempLevel(h, mi, 'matin'),
          aprem: aftDone ? 0 : computeTempLevel(h, mi, 'aprem'),
        });
      } catch {}
    };
    check();
    const t = setInterval(check, 60 * 1000); // toutes les minutes
    return () => { alive = false; clearInterval(t); };
  }, []);
  // Compat: ancienne API boolean attendue par useNotifications
  return {
    matin: state.matin > 0,
    aprem: state.aprem > 0,
    matinLevel: state.matin,
    apremLevel: state.aprem,
    maxLevel: Math.max(state.matin, state.aprem),
  };
}

// Joue un son à chaque MONTÉE de niveau (pas en boucle) + relance périodiquement pour niveau 3
function useTemperatureSound(alert) {
  const prevRef = useRef({ matinLevel: 0, apremLevel: 0 });
  useEffect(() => {
    const prev = prevRef.current;
    // Montée de niveau ?
    if (alert.matinLevel > prev.matinLevel && alert.matinLevel > 0) playTempBeep(alert.matinLevel);
    if (alert.apremLevel > prev.apremLevel && alert.apremLevel > 0) playTempBeep(alert.apremLevel);
    prevRef.current = { matinLevel: alert.matinLevel, apremLevel: alert.apremLevel };
  }, [alert.matinLevel, alert.apremLevel]);

  // Répétition niveau 3 : rebip toutes les 3 min tant que pas fait
  useEffect(() => {
    if (alert.maxLevel < 3) return;
    const t = setInterval(() => playTempBeep(3), 3 * 60 * 1000);
    return () => clearInterval(t);
  }, [alert.maxLevel]);
}

// Bandeau permanent (niveau 2) + Modal bloquante (niveau 3)
function TemperatureAlertBanner() {
  const app = useApp();
  const { route, setRoute } = app;
  const alert = useTemperatureAlert();
  useTemperatureSound(alert);
  const [snoozeUntil, setSnoozeUntil] = useState(() => {
    try { return Number(sessionStorage.getItem('vival_temp_snooze') || 0); } catch { return 0; }
  });
  const now = Date.now();
  const isSnoozed = snoozeUntil > now;

  // Ne rien afficher sur l'écran températures lui-même
  if (route === 'temperatures') return null;
  if (alert.maxLevel === 0) return null;

  const missing = [];
  if (alert.matinLevel > 0) missing.push('matin');
  if (alert.apremLevel > 0) missing.push('après-midi');
  const label = missing.join(' + ');

  const goNow = () => { setRoute('temperatures'); };
  const snooze15 = () => {
    const until = Date.now() + 15 * 60 * 1000;
    sessionStorage.setItem('vival_temp_snooze', String(until));
    setSnoozeUntil(until);
  };

  // Niveau 3 : modal bloquante (sauf si snoozed)
  if (alert.maxLevel >= 3 && !isSnoozed) {
    return (
      <div style={{
        position:'fixed', inset:0, zIndex:400,
        background:'rgba(220, 20, 20, 0.88)',
        display:'flex', alignItems:'center', justifyContent:'center',
        animation:'tempPulseBg 1.5s ease-in-out infinite',
      }}>
        <div style={{
          background:'#fff', borderRadius:14, padding:28, maxWidth:460, margin:16,
          textAlign:'center', boxShadow:'0 30px 80px rgba(0,0,0,0.5)',
        }}>
          <div style={{fontSize:56, marginBottom:8}}>⚠️</div>
          <h2 style={{margin:'0 0 10px', color:'#c00', fontSize:22, fontWeight:900}}>
            RELEVÉ TEMPÉRATURE {label.toUpperCase()} NON FAIT
          </h2>
          <p style={{margin:'0 0 6px', fontSize:15, color:'var(--ink-1)'}}>
            Il est <strong>{new Date().toLocaleTimeString('fr-FR', {hour:'2-digit', minute:'2-digit'})}</strong>.
          </p>
          <p style={{margin:'0 0 20px', fontSize:13, color:'var(--ink-3)'}}>
            C'est une obligation HACCP. À réaliser dès maintenant.
          </p>
          <button onClick={goNow} style={{
            background:'#c00', color:'#fff', border:0, padding:'14px 24px',
            fontSize:16, fontWeight:800, borderRadius:8, cursor:'pointer', width:'100%', marginBottom:8,
          }}>
            Faire le relevé maintenant →
          </button>
          <button onClick={snooze15} style={{
            background:'transparent', color:'var(--ink-3)', border:'1px solid var(--line-1)',
            padding:'8px 14px', fontSize:12, borderRadius:6, cursor:'pointer',
          }}>
            Reporter 15 min
          </button>
        </div>
      </div>
    );
  }

  // Niveau 2 : bandeau permanent (orange) en haut
  if (alert.maxLevel >= 2) {
    return (
      <div style={{
        position:'fixed', top:0, left:0, right:0, zIndex:200,
        background:'#f97316', color:'#fff', padding:'10px 16px',
        display:'flex', alignItems:'center', gap:12, justifyContent:'space-between', flexWrap:'wrap',
        boxShadow:'0 2px 10px rgba(0,0,0,0.15)',
      }}>
        <div style={{display:'flex', alignItems:'center', gap:10, flex:1, minWidth:0}}>
          <span style={{fontSize:20}}>⚠️</span>
          <strong>Relevé {label} non fait — {new Date().toLocaleTimeString('fr-FR',{hour:'2-digit',minute:'2-digit'})}</strong>
        </div>
        <button onClick={goNow} style={{
          background:'#fff', color:'#c2410c', border:0, padding:'6px 14px',
          fontSize:13, fontWeight:800, borderRadius:6, cursor:'pointer',
        }}>
          Faire maintenant →
        </button>
      </div>
    );
  }

  // Niveau 1 : simple pastille rouge sur l'icône sidebar (déjà géré) — rien à afficher en overlay
  return null;
}

function Sidebar() {
  const { route, setRoute } = useApp();
  const tempAlert = useTemperatureAlert();
  const hasTempAlert = tempAlert.matin || tempAlert.aprem;
  const haccpEnabled = useModule('haccp');
  const caisseEnabled = useModule('caisse_app');
  // Filtre les items selon les modules actifs
  const visibleItems = NAV_ITEMS.filter(item => {
    if (item.id === 'temperatures' && !haccpEnabled) return false;
    if ((item.id === 'catalog' || item.id === 'orders' || item.id === 'delivery' || item.id === 'customers') && !caisseEnabled) return false;
    return true;
  });
  return (
    <aside className="app-sidebar">
      <div className="sb-logo"><img src={VivalData._customLogo || "/assets/vival-logo.png"} alt="Vival" /></div>
      {visibleItems.map(item => (
        <button key={item.id} className="sb-item" aria-current={route === item.id} onClick={() => setRoute(item.id)} data-tour={"nav-" + item.id} style={{position:'relative'}}>
          <Icon name={item.icon} />
          <span className="sb-item-label">{item.label}</span>
          {item.id === 'temperatures' && hasTempAlert && (
            <span style={{position:'absolute', top:6, right:6, width:8, height:8, background:'#ef4444', borderRadius:'50%', boxShadow:'0 0 0 2px #fff'}} />
          )}
        </button>
      ))}
      <div className="sb-spacer"></div>
      <NotificationsBell variant="sidebar" />
      <button className="sb-item" title="Déconnexion" onClick={() => { sessionStorage.removeItem('vival-auth'); location.reload(); }}>
        <Icon name="logout" />
      </button>
    </aside>
  );
}

function useNotifications() {
  const { orders, setRoute } = useApp();
  const products = window.VivalData.PRODUCTS;
  const todayStart = new Date(); todayStart.setHours(0, 0, 0, 0);
  const tempAlert = useTemperatureAlert();

  const notifs = [];
  if (tempAlert.matin) {
    notifs.push({
      id: 'temp-matin',
      kind: 'temp',
      icon: 'surgeles',
      title: 'Relevé température matin manquant',
      body: 'Pense à relever les températures des frigos/surgelés',
      at: new Date().toISOString(),
      action: () => setRoute('temperatures'),
    });
  }
  if (tempAlert.aprem) {
    notifs.push({
      id: 'temp-aprem',
      kind: 'temp',
      icon: 'surgeles',
      title: 'Relevé température après-midi manquant',
      body: 'Pense à relever les températures avant la fermeture',
      at: new Date().toISOString(),
      action: () => setRoute('temperatures'),
    });
  }
  const pending = orders.filter(o => o.status === 'en-cours');
  for (const o of pending) {
    const cust = window.VivalData.CUSTOMERS.find(c => c.id === o.customerId);
    notifs.push({
      id: 'ord-' + o.id,
      kind: 'order',
      icon: 'shopping-cart',
      title: `Commande ${o.id} à préparer`,
      body: (cust?.name || o.customerName || 'Client libre') + ' · ' + (o.lines?.length || 0) + ' articles',
      at: o.createdAt,
      action: () => setRoute('orders'),
    });
  }
  const ready = orders.filter(o => o.status === 'prete');
  for (const o of ready) {
    const cust = window.VivalData.CUSTOMERS.find(c => c.id === o.customerId);
    notifs.push({
      id: 'rdy-' + o.id,
      kind: 'delivery',
      icon: 'truck',
      title: `${o.id} prête à livrer`,
      body: (cust?.name || 'Client libre') + (cust?.address ? ' · ' + cust.address : ''),
      at: o.createdAt,
      action: () => setRoute('delivery'),
    });
  }
  // Notifications stock uniquement si le module stock est actif
  if (getModules().stock) {
    const out = products.filter(p => p.stock === 0);
    if (out.length) {
      notifs.push({
        id: 'stock-out',
        kind: 'stock',
        icon: 'alert',
        title: `${out.length} produit${out.length > 1 ? 's' : ''} en rupture`,
        body: out.slice(0, 3).map(p => p.name).join(', ') + (out.length > 3 ? '…' : ''),
        at: new Date().toISOString(),
        action: () => setRoute('admin-catalog'),
      });
    }
    const low = products.filter(p => p.stock > 0 && p.stock <= 3);
    if (low.length) {
      notifs.push({
        id: 'stock-low',
        kind: 'stock',
        icon: 'alert',
        title: `${low.length} produit${low.length > 1 ? 's' : ''} en stock faible`,
        body: low.slice(0, 3).map(p => p.name).join(', ') + (low.length > 3 ? '…' : ''),
        at: new Date().toISOString(),
        action: () => setRoute('admin-catalog'),
      });
    }
  }
  notifs.sort((a, b) => (b.at || '').localeCompare(a.at || ''));
  return notifs;
}

function NotificationsBell({ variant = 'topbar' }) {
  const notifs = useNotifications();
  const [open, setOpen] = useState(false);
  const [lastSeen, setLastSeen] = useState(() => localStorage.getItem('vival_notif_seen') || '');
  const refW = useRef(null);

  const unread = notifs.filter(n => (n.at || '') > lastSeen).length;

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

  const markAllSeen = () => {
    const now = new Date().toISOString();
    setLastSeen(now);
    localStorage.setItem('vival_notif_seen', now);
  };

  const openPanel = () => {
    setOpen(!open);
    if (!open) markAllSeen();
  };

  const trigger = variant === 'sidebar'
    ? <button className="sb-item" onClick={openPanel} aria-label="Notifications" title="Notifications" style={{position:'relative'}}>
        <Icon name="bell" />
        {unread > 0 && <span className="notif-badge">{unread > 9 ? '9+' : unread}</span>}
      </button>
    : <button className="tb-action" onClick={openPanel} aria-label="Notifications">
        <Icon name="bell" />
        {unread > 0 && <span className="notif-badge">{unread > 9 ? '9+' : unread}</span>}
      </button>;

  return (
    <div ref={refW} style={{position:'relative'}}>
      {trigger}
      {open && (
        <div className={'notif-panel notif-panel-' + variant}>
          <div className="notif-head">
            <strong>Notifications</strong>
            <span className="muted" style={{fontSize:12}}>{notifs.length}</span>
          </div>
          <div className="notif-list">
            {notifs.length === 0 && (
              <div className="notif-empty">
                <Icon name="check-circle" />
                <div>Rien à signaler</div>
                <div className="muted" style={{fontSize:12}}>Aucune commande en attente, stock sain.</div>
              </div>
            )}
            {notifs.map(n => (
              <button key={n.id} className="notif-item" onClick={() => { n.action && n.action(); setOpen(false); }}>
                <div className={'notif-icon notif-icon-' + n.kind}><Icon name={n.icon} /></div>
                <div className="notif-text">
                  <div className="notif-title">{n.title}</div>
                  <div className="notif-body muted">{n.body}</div>
                </div>
              </button>
            ))}
          </div>
        </div>
      )}
    </div>
  );
}

function Topbar() {
  const { route, setCartDrawer, cart } = useApp();
  const title = NAV_ITEMS.find(n => n.id === route)?.label || '';
  return (
    <header className="app-topbar">
      <div className="tb-logo"><img src={VivalData._customLogo || "/assets/vival-logo.png"} alt="Vival" style={{maxWidth:'100%', maxHeight:'100%', objectFit:'contain'}} /></div>
      <div className="tb-title">{title}</div>
      <NotificationsBell variant="topbar" />
      {route === 'catalog' && cart.length > 0 && (
        <button className="tb-action" onClick={() => setCartDrawer(true)}><Icon name="shopping-cart" /><span className="dot"></span></button>
      )}
    </header>
  );
}

function BottomNav() {
  const { route, setRoute } = useApp();
  return (
    <nav className="app-bottomnav">
      {NAV_ITEMS.map(item => (
        <button key={item.id} className="bn-item" aria-current={route === item.id} onClick={() => setRoute(item.id)} data-tour={"nav-" + item.id}>
          <Icon name={item.icon} />
          <span className="bn-item-label">{item.label}</span>
        </button>
      ))}
    </nav>
  );
}

// ===== Cart panel =====
function CartPanel({ inDrawer = false }) {
  const { cart, updateQty, updateLinePrice, clearCart, cartTotal, customer, setCustomer, setCustomerPicker, validateOrder, setCartDrawer, addManualLine } = useApp();
  const [paying, setPaying] = useState(false);
  const [manualOpen, setManualOpen] = useState(false);

  const close = () => setCartDrawer(false);

  return (
    <div className={inDrawer ? '' : 'cart'}>
      <div className="cart-header">
        {inDrawer && <Button variant="ghost" size="sm" icon="close" onClick={close} />}
        <h2 className="cart-title">Panier</h2>
        {cart.length > 0 && <span className="cart-count-pill">{cart.length}</span>}
      </div>

      {customer ? (
        <div className="cart-customer">
          <div className="cart-customer-avatar">{customer.name.split(' ').slice(-1)[0][0]}</div>
          <div className="cart-customer-info">
            <div className="cart-customer-name">{customer.name}</div>
            <div className="cart-customer-meta"><Icon name="phone" style={{width:12,height:12}}/> {customer.phone}{customer.loyaltyPoints ? ` · ${customer.loyaltyPoints} pts fidélité` : ''}</div>
          </div>
          <Button variant="ghost" size="sm" icon="close" onClick={() => setCustomer(null)} />
        </div>
      ) : (
        <div className="cart-customer">
          <Button variant="outline" size="sm" icon="user" block onClick={() => setCustomerPicker(true)}>Associer un client</Button>
        </div>
      )}

      <div className="cart-lines">
        {cart.length === 0 ? (
          <div className="cart-empty">
            <Icon name="shopping-cart" />
            <div className="cart-empty-title">Panier vide</div>
            <div style={{fontSize:13}}>Tapez sur un produit pour l'ajouter</div>
          </div>
        ) : cart.map(line => {
          if (line.manual) {
            const unit = Number(line.price) || 0;
            return (
              <div key={line.pid} className="cart-line">
                <div className="cart-line-thumb" style={{background:'var(--warning-soft, #fff4e5)'}}><Icon name="edit" style={{color:'var(--warning, #ea580c)'}} /></div>
                <div className="cart-line-info">
                  <div className="cart-line-name">{line.name}</div>
                  <div className="cart-line-sub">Ligne libre · {formatPrice(unit).full}</div>
                  <div style={{marginTop:6}}>
                    <CartLineQtyEdit value={line.qty} onChange={q => updateQty(line.pid, q)} byWeight={false} />
                  </div>
                </div>
                <CartLinePrice unit={unit} qty={line.qty} onChangeUnit={(n) => updateLinePrice(line.pid, n)} />
              </div>
            );
          }
          const p = PRODUCTS.find(x => x.id === line.pid);
          if (!p) return null;
          const step = p.byWeight ? 0.1 : 1;
          const effectiveUnit = line.price != null ? Number(line.price) : p.price;
          return (
            <div key={line.pid} className="cart-line">
              <div className="cart-line-thumb">{p.image ? <img src={p.image} alt="" style={{width:'100%',height:'100%',objectFit:'contain'}} /> : <Icon name={p.rayon} />}</div>
              <div className="cart-line-info">
                <div className="cart-line-name">{p.name}</div>
                <div className="cart-line-sub">{p.format} · {formatPrice(effectiveUnit).full}</div>
                {!p.byWeight && line.qty > p.stock && (
                  <div style={{fontSize:12, color:'var(--danger, #c02424)', fontWeight:600, marginTop:2}}>Stock insuffisant (reste {p.stock})</div>
                )}
                <div style={{marginTop:6}}>
                  <CartLineQtyEdit value={line.qty} onChange={q => updateQty(line.pid, q)} byWeight={p.byWeight} />
                </div>
              </div>
              <CartLinePrice unit={effectiveUnit} qty={line.qty} onChangeUnit={(n) => updateLinePrice(line.pid, n)} />
            </div>
          );
        })}
      </div>

      <div style={{padding:'8px 20px', borderTop: cart.length ? '1px solid var(--line-2)' : '0'}}>
        <Button variant="outline" size="sm" icon="plus" block onClick={() => setManualOpen(true)}>Ajouter une ligne libre</Button>
      </div>

      {cart.length > 0 && (
        <>
          <CartTotals cart={cart} cartTotal={cartTotal} />
          <div className="cart-actions">
            <Button variant="primary" size="lg" icon="check-circle" block onClick={() => setPaying(true)}>Valider la commande</Button>
            <Button variant="ghost" size="sm" icon="trash" block onClick={clearCart}>Vider le panier</Button>
          </div>
        </>
      )}

      {manualOpen && <ManualLineModal onClose={() => setManualOpen(false)} onAdd={(data) => { addManualLine(data); setManualOpen(false); }} />}
      {paying && <CheckoutModal onClose={() => setPaying(false)} cartTotal={cartTotal} customer={customer} validateOrder={validateOrder} />}
    </div>
  );
}

function CartLineQtyEdit({ value, onChange, byWeight }) {
  const [editing, setEditing] = useState(false);
  const format = (v) => byWeight ? String(v).replace('.', ',') : String(v);
  const [val, setVal] = useState(format(value));
  const inputRef = useRef(null);
  useEffect(() => { if (editing && inputRef.current) inputRef.current.select(); }, [editing]);
  useEffect(() => { if (!editing) setVal(format(value)); }, [value, editing]);

  const commit = () => {
    let n = Number(String(val).replace(',', '.'));
    if (isNaN(n) || n < 0) { setEditing(false); return; }
    if (!byWeight) n = Math.floor(n);
    onChange(n);
    setEditing(false);
  };
  const cancel = () => { setVal(format(value)); setEditing(false); };

  if (editing) {
    return (
      <div onClick={e => e.stopPropagation()} style={{display:'inline-flex', alignItems:'center', gap:4}}>
        <input
          ref={inputRef}
          type="text"
          inputMode={byWeight ? 'decimal' : 'numeric'}
          value={val}
          autoFocus
          onChange={e => setVal(e.target.value)}
          onKeyDown={e => { if (e.key === 'Enter') commit(); else if (e.key === 'Escape') cancel(); }}
          onBlur={commit}
          style={{width:72, padding:'4px 8px', fontSize:13, border:'2px solid var(--vival-red)', borderRadius:6, textAlign:'center', fontWeight:700}}
        />
        <span style={{fontSize:12, color:'var(--ink-3)', fontWeight:700}}>{byWeight ? 'kg' : 'u'}</span>
      </div>
    );
  }
  const step = byWeight ? 0.1 : 1;
  return (
    <div className="qty-control" onClick={e => e.stopPropagation()}>
      <button className="qty-btn" onClick={() => onChange(Math.max(0, Number((value - step).toFixed(3))))}><Icon name="minus" /></button>
      <span
        className="qty-value"
        title="Cliquer pour saisir directement"
        onClick={() => setEditing(true)}
        style={{cursor:'pointer', minWidth:48}}
      >
        {byWeight ? `${Number(value).toFixed(value % 1 === 0 ? 0 : 2)}kg` : value}
      </span>
      <button className="qty-btn" onClick={() => onChange(Number((value + step).toFixed(3)))}><Icon name="plus" /></button>
    </div>
  );
}

function CartLinePrice({ unit, qty, onChangeUnit }) {
  const [editing, setEditing] = useState(false);
  const [val, setVal] = useState(String(unit ?? 0).replace('.', ','));
  const inputRef = useRef(null);
  useEffect(() => { if (editing && inputRef.current) inputRef.current.select(); }, [editing]);
  useEffect(() => { if (!editing) setVal(String(unit ?? 0).replace('.', ',')); }, [unit, editing]);

  const commit = () => {
    const n = Number(String(val).replace(',', '.'));
    if (!isNaN(n) && n >= 0) onChangeUnit(n);
    setEditing(false);
  };
  const cancel = () => { setVal(String(unit ?? 0).replace('.', ',')); setEditing(false); };

  if (editing) {
    return (
      <div onClick={e => e.stopPropagation()} style={{display:'flex', alignItems:'center', gap:4}}>
        <input
          ref={inputRef}
          type="text"
          inputMode="decimal"
          value={val}
          autoFocus
          onChange={e => setVal(e.target.value)}
          onKeyDown={e => { if (e.key === 'Enter') commit(); else if (e.key === 'Escape') cancel(); }}
          onBlur={commit}
          style={{width:72, padding:'4px 6px', fontSize:13, border:'2px solid var(--vival-red)', borderRadius:6, textAlign:'right', fontWeight:700}}
        />
        <span style={{fontSize:11, color:'var(--ink-3)'}}>€/u</span>
      </div>
    );
  }
  return (
    <div
      className="cart-line-price"
      title="Cliquer pour modifier le prix unitaire"
      onClick={(e) => { e.stopPropagation(); setEditing(true); }}
      style={{cursor:'pointer', textAlign:'right'}}
    >
      <div>{formatPrice((unit || 0) * qty).full}</div>
    </div>
  );
}

function ManualLineModal({ onClose, onAdd }) {
  const [name, setName] = useState('');
  const [price, setPrice] = useState('');
  const [tvaRate, setTvaRate] = useState(0.20);
  const inputStyle = { width: '100%', padding: '10px 12px', border: '1px solid var(--line-1)', borderRadius: 8, fontSize: 14 };
  const priceNum = Number(String(price).replace(',', '.')) || 0;
  const canAdd = name.trim().length > 0 && priceNum > 0;
  const submit = () => {
    if (!canAdd) return;
    onAdd({ name: name.trim(), price: priceNum, tvaRate: Number(tvaRate) });
  };
  return (
    <Modal open={true} onClose={onClose} title="Ligne libre"
      footer={<>
        <Button variant="ghost" onClick={onClose}>Annuler</Button>
        <Button variant="primary" icon="plus" onClick={submit} disabled={!canAdd}>Ajouter au panier</Button>
      </>}
    >
      <div style={{display:'grid', gap:12}}>
        <div style={{fontSize:13, color:'var(--ink-3)'}}>
          Pour ajouter un produit ou service qui n'est pas dans le catalogue (retrait pharmacie, bouteille de gaz, dépannage…).
        </div>
        <label style={{fontSize:13}}>
          <div style={{fontWeight:700, marginBottom:4}}>Libellé</div>
          <input type="text" value={name} autoFocus placeholder="ex: Bouteille de gaz" onChange={e => setName(e.target.value)} onKeyDown={e => e.key === 'Enter' && submit()} style={inputStyle} />
        </label>
        <div style={{display:'grid', gridTemplateColumns:'1fr 1fr', gap:12}}>
          <label style={{fontSize:13}}>
            <div style={{fontWeight:700, marginBottom:4}}>Prix TTC (€)</div>
            <input type="text" inputMode="decimal" value={price} placeholder="0,00" onChange={e => setPrice(e.target.value)} onKeyDown={e => e.key === 'Enter' && submit()} style={inputStyle} />
          </label>
          <label style={{fontSize:13}}>
            <div style={{fontWeight:700, marginBottom:4}}>TVA</div>
            <select value={tvaRate} onChange={e => setTvaRate(parseFloat(e.target.value))} style={{...inputStyle, background:'#fff'}}>
              <option value={0.055}>5,5 % (alimentaire)</option>
              <option value={0.10}>10 % (restauration)</option>
              <option value={0.20}>20 % (alcool / non-alim.)</option>
              <option value={0.021}>2,1 % (presse, médic.)</option>
              <option value={0}>0 %</option>
            </select>
          </label>
        </div>
        <div style={{fontSize:12, color:'var(--ink-3)'}}>Cette ligne ne décrémente pas le stock et n'est pas comptée dans les marges du tableau de bord.</div>
      </div>
    </Modal>
  );
}

function computeTva(cart) {
  const byRate = {}; // rate -> { ht, tva, ttc }
  let ht = 0, tva = 0, ttc = 0;
  for (const l of cart) {
    let rate, unit;
    if (l.manual) {
      rate = Number(l.tvaRate) || 0.20;
      unit = Number(l.price) || 0;
    } else {
      const p = PRODUCTS.find(x => x.id === l.pid);
      if (!p) continue;
      rate = p.tvaRate ?? 0.055;
      unit = l.price != null ? Number(l.price) : p.price;
    }
    const lineTtc = unit * l.qty;
    const lineHt = lineTtc / (1 + rate);
    const lineTva = lineTtc - lineHt;
    ttc += lineTtc; ht += lineHt; tva += lineTva;
    if (!byRate[rate]) byRate[rate] = { ht: 0, tva: 0, ttc: 0 };
    byRate[rate].ht += lineHt;
    byRate[rate].tva += lineTva;
    byRate[rate].ttc += lineTtc;
  }
  return { ht, tva, ttc, byRate };
}

function CheckoutModal({ onClose, cartTotal, customer, validateOrder }) {
  const [discount, setDiscount] = useState('');
  const [notes, setNotes] = useState('');
  const { cart } = useApp();
  const discountPct = Math.max(0, Math.min(100, Number((discount || '').toString().replace(',', '.')) || 0));
  const discountAmt = cartTotal * discountPct / 100;
  const finalTotal = cartTotal - discountAmt;
  const hasAlcohol = cart.some(l => { if (l.manual) return false; const p = PRODUCTS.find(x => x.id === l.pid); return p?.isAlcohol; });
  const [ageConfirmed, setAgeConfirmed] = useState(false);
  const pay = (method) => {
    if (hasAlcohol && !ageConfirmed) { alert('Merci de confirmer que le client a au moins 18 ans.'); return; }
    onClose();
    validateOrder(method, { discountPct, notes: notes.trim() });
  };
  return (
    <Modal open={true} onClose={onClose} title="Encaisser"
      footer={<Button variant="ghost" onClick={onClose}>Annuler</Button>}>
      <div style={{display:'grid', gap:14}}>
        <div className="card" style={{padding:12, background:'var(--surface-1)'}}>
          <div style={{display:'flex', justifyContent:'space-between'}}><span className="muted">Sous-total</span><strong>{formatPrice(cartTotal).full}</strong></div>
          {discountPct > 0 && <div style={{display:'flex', justifyContent:'space-between', color:'var(--danger, #c02424)'}}><span>Remise −{discountPct}%</span><span>−{formatPrice(discountAmt).full}</span></div>}
          <div style={{display:'flex', justifyContent:'space-between', marginTop:8, paddingTop:8, borderTop:'1px solid var(--line-2)'}}><span style={{fontWeight:700}}>Total à payer</span><strong style={{fontSize:18}}>{formatPrice(finalTotal).full}</strong></div>
          {customer && <div className="muted" style={{fontSize:12, marginTop:4}}>Client : {customer.name}</div>}
        </div>

        <div>
          <div className="kpi-label" style={{marginBottom:4}}>Remise (%)</div>
          <div style={{display:'flex', gap:6, flexWrap:'wrap', marginBottom:6}}>
            {[0, 5, 10, 15, 20].map(v => (
              <button key={v} className="chip" aria-pressed={discountPct === v} onClick={() => setDiscount(String(v))}>{v === 0 ? 'Aucune' : `−${v}%`}</button>
            ))}
          </div>
          <Input type="number" min={0} max={100} placeholder="Autre %" value={discount} onChange={e => setDiscount(e.target.value)} />
        </div>

        <div>
          <div className="kpi-label" style={{marginBottom:4}}>Note sur la commande (optionnel)</div>
          <textarea value={notes} onChange={e => setNotes(e.target.value)} rows={2} placeholder="Ex: laisser chez le voisin" style={{width:'100%', padding:'10px 12px', border:'1px solid var(--line)', borderRadius:8, fontSize:14, fontFamily:'inherit', resize:'vertical'}} />
        </div>

        {hasAlcohol && (
          <label style={{display:'flex', alignItems:'flex-start', gap:8, padding:'10px 12px', background:'var(--warning-soft, #fff4e5)', border:'1px solid var(--warning, #ea580c)', borderRadius:8, cursor:'pointer'}}>
            <input type="checkbox" checked={ageConfirmed} onChange={e => setAgeConfirmed(e.target.checked)} style={{marginTop:3}} />
            <span style={{fontSize:13}}><strong>Produit alcoolisé</strong> — Je confirme que le client est majeur (18+ ans).</span>
          </label>
        )}

        <div>
          <div className="kpi-label" style={{marginBottom:6}}>Mode de paiement</div>
          <div style={{display:'grid', gridTemplateColumns:'1fr 1fr', gap:8}}>
            <Button variant="outline" size="lg" icon="cash" onClick={() => pay('espèces')}>Espèces</Button>
            <Button variant="outline" size="lg" icon="credit-card" onClick={() => pay('CB')}>Carte bancaire</Button>
            <Button variant="outline" size="lg" icon="truck" onClick={() => pay('livraison')}>À la livraison</Button>
            <Button variant="outline" size="lg" icon="clock" onClick={() => pay('en attente')}>Plus tard</Button>
          </div>
        </div>
      </div>
    </Modal>
  );
}

function BonLivraisonTotals({ lines, discountPct = 0 }) {
  const cart = lines.map(l => l.manual
    ? { pid: l.pid, qty: l.qty, manual: true, price: Number(l.unitPrice ?? l.price) || 0, tvaRate: Number(l.tvaRate) || 0.20, name: l.name }
    : { pid: l.pid, qty: l.qty });
  const { ht, tva, ttc, byRate } = computeTva(cart);
  const rates = Object.keys(byRate).map(Number).sort((a, b) => a - b);
  const discountAmt = ttc * (discountPct / 100);
  const finalTtc = ttc - discountAmt;
  return (
    <div className="bl-totals">
      <div className="row"><span>Sous-total HT</span><span>{formatPrice(ht).full}</span></div>
      {rates.map(r => (
        <div key={r} className="row"><span>TVA {(r * 100).toFixed(1).replace('.0','')} % sur {formatPrice(byRate[r].ht).full}</span><span>{formatPrice(byRate[r].tva).full}</span></div>
      ))}
      <div className="row"><span>Total TVA</span><span>{formatPrice(tva).full}</span></div>
      {discountPct > 0 && <div className="row" style={{color:'#c02424'}}><span>Remise −{discountPct}%</span><span>−{formatPrice(discountAmt).full}</span></div>}
      <div className="row grand"><span>Total TTC</span><span>{formatPrice(finalTtc).full}</span></div>
    </div>
  );
}

function CartTotals({ cart, cartTotal }) {
  const { ht, tva, byRate } = computeTva(cart);
  const rates = Object.keys(byRate).map(Number).sort((a, b) => a - b);
  return (
    <div className="cart-totals">
      <div className="cart-total-row"><span>Total HT</span><span>{formatPrice(ht).full}</span></div>
      {rates.map(r => (
        <div key={r} className="cart-total-row" style={{fontSize:13}}>
          <span className="muted">TVA {(r * 100).toFixed(1).replace('.0','')} %</span>
          <span>{formatPrice(byRate[r].tva).full}</span>
        </div>
      ))}
      <div className="cart-total-row"><span>Total TVA</span><span>{formatPrice(tva).full}</span></div>
      <div className="cart-total-row grand"><span>Total TTC</span><span>{formatPrice(cartTotal).full}</span></div>
    </div>
  );
}

// ===== Catalog screen =====
function CatalogScreen() {
  const app = useApp();
  const { addToCart, addManualLine, setSelectedProduct, setScanOpen, cart, cartTotal, setCartDrawer } = app;
  const [activeRayon, setActiveRayon] = useState(RAYONS[0].id);
  const [activeSub, setActiveSub] = useState('Tout');
  const [query, setQuery] = useState('');
  const [manualOpen, setManualOpen] = useState(false);

  const rayon = RAYONS.find(r => r.id === activeRayon);

  const dragScroll = (el) => {
    if (!el || el.dataset.dragBound) return;
    el.dataset.dragBound = '1';
    let isDown = false, startX = 0, startLeft = 0, moved = 0;
    el.addEventListener('mousedown', (e) => {
      if (e.button !== 0) return;
      isDown = true; moved = 0;
      startX = e.pageX; startLeft = el.scrollLeft;
      el.classList.add('dragging');
    });
    const up = () => { if (isDown) { isDown = false; el.classList.remove('dragging'); } };
    window.addEventListener('mouseup', up);
    window.addEventListener('mousemove', (e) => {
      if (!isDown) return;
      const dx = e.pageX - startX;
      moved = Math.max(moved, Math.abs(dx));
      el.scrollLeft = startLeft - dx;
    });
    el.addEventListener('click', (e) => {
      if (moved > 5) { e.preventDefault(); e.stopPropagation(); }
    }, true);
  };

  const filtered = useMemo(() => {
    let list = PRODUCTS.filter(p => p.isActive !== false);
    if (query.trim()) {
      const q = query.toLowerCase();
      return list.filter(p => p.name.toLowerCase().includes(q) || p.format.toLowerCase().includes(q));
    }
    list = list.filter(p => p.rayon === activeRayon);
    if (activeSub !== 'Tout') list = list.filter(p => p.sub === activeSub);
    return list;
  }, [query, activeRayon, activeSub]);

  return (
    <div className="catalog">
      <div className="catalog-main">
        <div className="catalog-toolbar">
          <div className="catalog-search">
            <Input icon="search" size="lg" placeholder="Rechercher un produit ou scanner un EAN…" value={query}
              onChange={e => setQuery(e.target.value)}
              onKeyDown={e => {
                if (e.key === 'Enter' && /^\d{8,14}$/.test(query.trim())) {
                  const ean = normalizeEan(query.trim()); // 12 chiffres -> 13 chiffres auto
                  const p = PRODUCTS.find(x => x.barcode === ean);
                  if (p) { addToCart(p.id); setQuery(''); }
                  else { app.setUnknownBarcode(ean); setQuery(''); }
                }
              }} />
          </div>
          <Button variant="outline" size="lg" icon="scan-barcode" onClick={() => setScanOpen(true)}>Scanner</Button>
          <Button variant="outline" size="lg" icon="edit" onClick={() => setManualOpen(true)}>Ligne libre</Button>
        </div>

        {!query && <PinnedRow onAdd={(pid) => addToCart(pid)} />}
        {!query && (
          <>
            <div className="rayons-scroller" ref={dragScroll} onWheel={(e) => { if (e.deltaY !== 0 && Math.abs(e.deltaY) > Math.abs(e.deltaX)) { e.currentTarget.scrollLeft += e.deltaY; } }}>
              {RAYONS.map(r => (
                <button key={r.id} className="chip" aria-pressed={r.id === activeRayon} onClick={() => { setActiveRayon(r.id); setActiveSub('Tout'); }}>
                  <Icon name={r.id} />
                  {r.name}
                </button>
              ))}
            </div>
            {rayon.subs.length > 1 && (
              <div className="subrayons" ref={dragScroll} onWheel={(e) => { if (e.deltaY !== 0 && Math.abs(e.deltaY) > Math.abs(e.deltaX)) { e.currentTarget.scrollLeft += e.deltaY; } }}>
                {rayon.subs.map(s => (
                  <button key={s} className="subrayon-chip" aria-pressed={s === activeSub} onClick={() => setActiveSub(s)}>{s}</button>
                ))}
              </div>
            )}
          </>
        )}

        <div className="products-scroll">
          {filtered.length === 0 ? (
            <div className="empty-state"><Icon name="search" /><div>Aucun produit ne correspond à « {query} »</div></div>
          ) : (
            <div className="products-grid">
              {filtered.map(p => (
                <ProductTile key={p.id} product={p}
                  onAdd={() => addToCart(p.id, p.byWeight ? 1 : 1)}
                  onOpen={() => setSelectedProduct(p)} />
              ))}
            </div>
          )}
          <LastSyncLabel />
        </div>
      </div>
      <CartPanel />
      {cart.length > 0 && (
        <button className="floating-cart active" onClick={() => setCartDrawer(true)}>
          <Icon name="shopping-cart" />
          <span>{formatPrice(cartTotal).full}</span>
          <span className="floating-cart-count">{cart.length}</span>
        </button>
      )}
      {manualOpen && <ManualLineModal onClose={() => setManualOpen(false)} onAdd={(data) => { addManualLine(data); setManualOpen(false); }} />}
    </div>
  );
}

// ===== Dashboard =====
function toDateInput(d) {
  const y = d.getFullYear();
  const m = String(d.getMonth() + 1).padStart(2, '0');
  const day = String(d.getDate()).padStart(2, '0');
  return `${y}-${m}-${day}`;
}

function getPeriodRange(period, customFrom, customTo) {
  const now = new Date();
  const end = new Date(now); end.setHours(23, 59, 59, 999);
  const start = new Date(now);
  if (period === 'today') {
    start.setHours(0, 0, 0, 0);
  } else if (period === 'week') {
    start.setDate(start.getDate() - 6);
    start.setHours(0, 0, 0, 0);
  } else if (period === 'month') {
    start.setDate(1);
    start.setHours(0, 0, 0, 0);
  } else if (period === 'custom') {
    const s = new Date(customFrom); s.setHours(0, 0, 0, 0);
    const e = new Date(customTo); e.setHours(23, 59, 59, 999);
    return { from: s, to: e };
  }
  return { from: start, to: end };
}

// ========== Import export caisse PDF/CSV ==========
async function extractPdfLines(file) {
  if (!window.pdfjsLib) throw new Error('pdf.js non chargé. Recharge la page.');
  const buf = await file.arrayBuffer();
  const pdf = await window.pdfjsLib.getDocument({ data: buf }).promise;
  const lines = [];
  for (let p = 1; p <= pdf.numPages; p++) {
    const page = await pdf.getPage(p);
    const content = await page.getTextContent();
    const byY = new Map();
    for (const item of content.items) {
      const y = Math.round(item.transform[5]);
      if (!byY.has(y)) byY.set(y, []);
      byY.get(y).push({ x: item.transform[4], str: item.str });
    }
    const sortedY = [...byY.keys()].sort((a, b) => b - a);
    for (const y of sortedY) {
      const row = byY.get(y).sort((a, b) => a.x - b.x).map(i => i.str).join(' ').replace(/\s+/g, ' ').trim();
      if (row) lines.push(row);
    }
  }
  return lines;
}

// Convertit "19/04/2026" en date ISO "2026-04-19"
function parseFrDate(s) {
  if (!s) return null;
  const m = s.match(/(\d{1,2})\/(\d{1,2})\/(\d{2,4})/);
  if (!m) return null;
  let [, d, mo, y] = m;
  if (y.length === 2) y = (Number(y) > 50 ? '19' : '20') + y;
  return `${y}-${mo.padStart(2, '0')}-${d.padStart(2, '0')}`;
}

// Extrait la plage de dates du header FABRE CORROX :
// "Résultats de vente articles (PLU)19/04/2026 Au 19/04/2026"
function extractPluDateRange(lines) {
  for (const line of lines.slice(0, 15)) {
    const m = line.match(/\(PLU\)\s*(\d{1,2}\/\d{1,2}\/\d{2,4})\s+Au\s+(\d{1,2}\/\d{1,2}\/\d{2,4})/i);
    if (m) return { from: parseFrDate(m[1]), to: parseFrDate(m[2]) };
    // Format alternatif sans parenthèses
    const m2 = line.match(/(\d{1,2}\/\d{1,2}\/\d{2,4})\s+Au\s+(\d{1,2}\/\d{1,2}\/\d{2,4})/i);
    if (m2) return { from: parseFrDate(m2[1]), to: parseFrDate(m2[2]) };
  }
  return { from: null, to: null };
}

function parsePluLine(raw) {
  // EAN ≥ 10 chiffres au début
  const m = raw.match(/^(\d{10,14})\s+(.+)$/);
  if (!m) return null;
  const ean = normalizeEan(m[1]); // 12 chiffres -> 13 chiffres auto (caisse tronque parfois la cle)
  const rest = m[2];
  const toNum = (s) => Number(String(s).replace(',', '.')) || 0;
  const nums = [...rest.matchAll(/(-?\d+[,.]?\d*)/g)].map(x => x[1]);
  if (nums.length < 6) return null;
  // 8 derniers nombres potentiels : [PV_HT, PV_TTC, QTY, CA_TTC, CA_HT, TVA, 100, MARGE]
  const n = nums.length;
  const marge = toNum(nums[n - 1]);
  const tva = toNum(nums[n - 3]);
  const ca_ht = toNum(nums[n - 4]);
  const ca_ttc = toNum(nums[n - 5]);
  const qty = toNum(nums[n - 6]);
  const pv_ttc = n >= 8 ? toNum(nums[n - 7]) : 0;
  let name = rest;
  // Retire les derniers nombres
  name = name.replace(/\s*(-?\d+[,.]?\d*\s*%?\s*){4,}[A-ZÀÁÂÄÇÉÈÊËÎÏÔÖÛÜ\s.]*$/i, '').trim();
  return { ean, name: name || 'Inconnu', qty, pv_ttc, ca_ttc, ca_ht, tva_rate: tva > 0 ? tva / 100 : 0, marge };
}

function parseCsv(text) {
  const sep = text.includes(';') ? ';' : ',';
  const rows = text.split(/\r?\n/).filter(l => l.trim());
  if (rows.length < 2) return [];
  const header = rows[0].split(sep).map(h => h.trim().toLowerCase());
  const findCol = (names) => {
    for (const n of names) { const i = header.findIndex(h => h.includes(n)); if (i >= 0) return i; }
    return -1;
  };
  const iEan = findCol(['ean', 'code', 'barcode']);
  const iName = findCol(['désignation', 'designation', 'libellé', 'libelle', 'nom']);
  const iQty = findCol(['qté', 'qte', 'quantit']);
  const iCaTtc = findCol(['ca ttc', 'chiffre ttc', 'chiffre', 'ca']);
  const iTva = findCol(['tva']);
  const toNum = (s) => Number(String(s || '').replace(',', '.')) || 0;
  const out = [];
  for (let i = 1; i < rows.length; i++) {
    const cols = rows[i].split(sep);
    const eanRaw = (iEan >= 0 ? (cols[iEan] || '') : '').replace(/\D/g, '');
    const ean = normalizeEan(eanRaw); // 12 chiffres -> 13 chiffres auto
    if (!ean || ean.length < 8) continue;
    const qty = iQty >= 0 ? toNum(cols[iQty]) : 0;
    const ca_ttc = iCaTtc >= 0 ? toNum(cols[iCaTtc]) : 0;
    if (qty <= 0 || ca_ttc <= 0) continue;
    const tva = iTva >= 0 ? toNum(cols[iTva]) : 5.5;
    out.push({
      ean, name: iName >= 0 ? (cols[iName] || '') : 'Inconnu',
      qty, pv_ttc: ca_ttc / qty, ca_ttc, ca_ht: 0,
      tva_rate: tva / 100, marge: 0,
    });
  }
  return out;
}

// Recherche fuzzy par nom dans PRODUCTS
function searchProductsByName(query, limit = 3) {
  const q = (query || '').toLowerCase().trim();
  if (!q || q.length < 3) return [];
  const tokens = q.split(/\s+/).filter(t => t.length >= 2);
  if (!tokens.length) return [];
  const products = window.VivalData?.PRODUCTS || [];
  const scored = [];
  for (const p of products) {
    const name = (p.name || '').toLowerCase();
    if (!name) continue;
    let score = 0;
    for (const t of tokens) if (name.includes(t)) score++;
    if (score > 0) scored.push({ p, score, len: name.length });
  }
  return scored.sort((a, b) => b.score - a.score || a.len - b.len).slice(0, limit).map(x => x.p);
}

function UnmatchedSuggestion({ pdfName, pdfEan, onLinked, linked }) {
  const [state, setState] = useState('idle'); // idle | picking | linking | done
  const suggestions = React.useMemo(() => searchProductsByName(pdfName, 3), [pdfName]);

  if (linked) return <span style={{color:'#16a34a', fontSize:11, fontWeight:600}}>✓ Lié</span>;
  if (!suggestions.length) return <span className="muted" style={{fontSize:11}}>—</span>;

  const link = async (product) => {
    setState('linking');
    const storeId = window.VivalData?._storeId;
    if (!storeId || !pdfEan || !product.barcode) { setState('idle'); return; }
    const { error } = await window.sb.from('product_alt_eans').upsert({
      store_id: storeId, alt_ean: pdfEan, main_ean: product.barcode, source: 'manual',
    }, { onConflict: 'store_id,alt_ean' });
    if (!error) {
      // Met a jour la Map en memoire aussi
      if (window.VivalData._altEansMap) window.VivalData._altEansMap.set(pdfEan, product.barcode);
      onLinked?.();
    }
    setState('done');
  };

  if (state === 'idle') {
    const best = suggestions[0];
    return (
      <div style={{fontSize:11}}>
        <button onClick={() => setState('picking')}
          style={{background:'transparent', border:'1px solid #fca5a5', borderRadius:4, padding:'2px 6px', cursor:'pointer', fontSize:11}}>
          {best.name.slice(0, 40)}{best.name.length > 40 ? '…' : ''} ?
        </button>
      </div>
    );
  }
  if (state === 'picking') {
    return (
      <div style={{display:'grid', gap:4}}>
        {suggestions.map(p => (
          <button key={p.id} onClick={() => link(p)}
            style={{textAlign:'left', background:'#fff', border:'1px solid #e5e7eb', borderRadius:4, padding:'4px 6px', cursor:'pointer', fontSize:11}}>
            <strong>{p.name}</strong>
            {p.brand ? <span className="muted"> · {p.brand}</span> : null}
            <br/>
            <span className="muted">EAN {p.barcode} · {formatPrice(p.price).full}</span>
          </button>
        ))}
        <button onClick={() => setState('idle')} style={{background:'transparent', border:0, cursor:'pointer', fontSize:10, color:'var(--ink-3)'}}>Annuler</button>
      </div>
    );
  }
  if (state === 'linking') return <span className="muted" style={{fontSize:11}}>Lien…</span>;
  return null;
}

function enrichSalesRows(rows) {
  // Le logiciel caisse tronque souvent la clé de contrôle : PDF donne 12 chiffres
  // alors que notre base stocke des EAN-13 complets. On indexe dans les 2 sens.
  const byEan = new Map();
  const byPrefix12 = new Map();
  for (const p of window.VivalData.PRODUCTS) {
    if (!p.barcode) continue;
    byEan.set(p.barcode, p);
    if (p.barcode.length === 13) {
      const pre = p.barcode.slice(0, 12);
      if (!byPrefix12.has(pre)) byPrefix12.set(pre, p);
    }
  }
  // Map rayon → mot-clé du département caisse PLU
  // Permet de classer les lignes pesées (pas d'EAN réel) selon le rayon PDF caisse
  const inferRayonFromWeightedName = (name) => {
    const n = (name || '').toUpperCase();
    if (/FRUITS\s*ET\s*LE|FRUITS\s*LE/.test(n) || /BANANE|POMME|POIRE|TOMATE|COURGETTE|SALADE|CAROTTE|ORANGE|KIWI|RAISIN|ANANAS|CELERI|POIREAU|CONCOMBRE|ENDIVE|POMELOS|MELON|AVOCAT|BATAVIA|LAITUE|MACHE|OIGNON|FRAISE|PAMPLEMOUSSE|CITRON|MANDARIN|ABRICOT|PECHE|CERISE|PASTEQUE|COURGE|POIVRON|CHOU|FENOUIL|AUBERGINE|ARTICHAUT/.test(n)) return 'fruits-et-legumes';
    if (/BOUCHERIE|BOEUF|FAUX\s*FILET|STEAK|VIANDE|JULO\s*KG|PATE\s*CROUTE/.test(n)) return 'viandes-et-poissons';
    if (/CHARCUTERI|JAMBON.*KG/.test(n)) return 'viandes-et-poissons';
    if (/FROMAGE|PETIT\s*BASQUE|COMTE\s*KG/.test(n)) return 'produits-frais';
    if (/BOULANGERI|PAIN\s*TX/.test(n)) return 'boulangerie-et-patisserie';
    if (/CREMERIE\s*TX|CREMERIE/.test(n)) return 'produits-frais';
    if (/EPICERIE\s*TX/.test(n)) return 'epicerie-salee';
    if (/LIQUIDES/.test(n)) return 'boissons-et-alcool';
    if (/SURGELES/.test(n)) return 'surgeles';
    if (/D\.H\.P|HYGIENE/.test(n)) return 'hygiene-et-beaute';
    if (/PATISSERIE/.test(n)) return 'boulangerie-et-patisserie';
    if (/PRESSE|SERVICE\s*LA\s*POSTE|COUPON|MARKETING/.test(n)) return null; // ignorés
    return null;
  };

  return rows.map(r => {
    // Robustesse : redetecte is_weighted par regex si le flag a ete perdu lors d un refresh
    // (ex. ligne setRows apres ajout produit OFF qui oubliait la propriete)
    if (!r.is_weighted && r.ean && /^0{6,}/.test(r.ean)) {
      r = { ...r, is_weighted: true };
    }
    // Lignes PLU internes caisse (codes 000000xxx) : pas d'EAN réel, pas de PA ligne par ligne.
    // On infère le rayon depuis la désignation. La marge réelle est calculée dans l'onglet
    // "Marges rayon" (factures fournisseurs + stock + casse).
    if (r.is_weighted) {
      const rayon = inferRayonFromWeightedName(r.name);
      const tvaRate = r.tva_rate || 0.055;
      const caHt = r.ca_ttc / (1 + tvaRate);
      return {
        ...r,
        matched: true,
        rayon: rayon || 'epicerie-salee',
        sub: r.name || 'PLU caisse',
        ca_ht: caHt,
        purchase_price: 0,
        marge_ht: 0,
        marge_rate: 0,
        has_purchase: false, // marge NON calculable ligne par ligne, voir onglet Marges rayon
        is_plu_internal: true,
      };
    }
    let p = byEan.get(r.ean);
    // EAN 12 du PDF → cherche dans la base par préfixe 12
    if (!p && r.ean?.length === 12) p = byPrefix12.get(r.ean);
    // EAN 12 du PDF → essaye aussi en reconstruisant la clé EAN-13
    if (!p && r.ean?.length === 12) {
      const cs = ean13Checksum(r.ean);
      if (cs !== null) p = byEan.get(r.ean + cs);
    }
    // Fallback: EAN secondaire Casino (anciens lots / variantes) -> resolution vers EAN principal
    if (!p) {
      const altMap = window.VivalData?._altEansMap;
      if (altMap && r.ean) {
        const mainEan = altMap.get(r.ean);
        if (mainEan) p = byEan.get(mainEan);
        // Tente aussi avec prefix 12 -> checksum sur l'EAN principal
        if (!p && r.ean.length === 12) {
          const cs = ean13Checksum(r.ean);
          if (cs !== null) {
            const main13 = altMap.get(r.ean + cs);
            if (main13) p = byEan.get(main13);
          }
        }
      }
    }
    if (!p) return { ...r, matched: false };
    const tvaRate = p.tvaRate || r.tva_rate || 0.055;
    const caHt = r.ca_ttc / (1 + tvaRate);
    // PA : on utilise le prix d'achat catalogue si coherent.
    // Sinon (catalogue sans PA ou PA qui donnerait une marge aberrante < 0),
    // on estime PA = 70% du prix HT reellement vendu -> marge 30% coherente.
    let purchaseUnit = Number(p.purchasePrice) || 0;
    const unitHtVendu = r.qty > 0 ? caHt / r.qty : 0;
    const wouldBeNegative = purchaseUnit > 0 && purchaseUnit > unitHtVendu;
    if (purchaseUnit === 0 || wouldBeNegative) {
      // Fallback : 30% de marge sur le prix caisse reel
      purchaseUnit = unitHtVendu * 0.70;
    }
    const purchaseTotal = purchaseUnit * r.qty;
    const margeHt = caHt - purchaseTotal;
    const margeRate = caHt > 0 ? (margeHt / caHt) * 100 : 0;
    return {
      ...r,
      matched: true,
      product_id: p.id,
      name: p.name,
      rayon: p.rayon,
      sub: p.sub || 'Tout',
      tva_rate: tvaRate,
      ca_ht: caHt,
      purchase_price: purchaseUnit,
      marge_ht: margeHt,
      marge_rate: margeRate,
      has_purchase: purchaseUnit > 0,
    };
  });
}

// ========== Import inventaire PDF (Casino PROFRE) ==========
// Mapping heuristique : libellé famille Casino -> rayon app.
// Utilisé pour les familles jamais rencontrées. L'utilisateur peut corriger.
function guessRayonFromCasinoLabel(label) {
  const s = (label || '').toLowerCase();
  const rules = [
    [/fromage|yaourt|yogourt|cremerie|crème|creme|beurre|lait|oeuf|\boeufs?\b|charcuterie|traiteur|jambon|saucisson|pate fraiche|pâte fraiche/, 'produits-frais'],
    [/fruit|legume|légume|salade|herbe|condiment.*frais/, 'fruits-et-legumes'],
    [/viande|boeuf|porc|volaille|poulet|agneau|veau|poisson|crustac|coquillage/, 'viandes-et-poissons'],
    [/pain|boulanger|viennoiser|patisser|pâtisser|biscuit.*frais/, 'boulangerie-et-patisserie'],
    [/\bvin\b|rouge|blanc|rosé|effervescent|champagne|cremant/, 'vins'],
    [/biere|bière|panach|spiritueux|apero|apéro|rhum|whisk|pastis|pernod|porto|vodka|gin|liqueur/, 'boissons-et-alcool'],
    [/eau|soda|jus|nectar|the glac|thé glac|boisson|sirop|limonade/, 'boissons-et-alcool'],
    [/surgel|glace|frozen/, 'surgeles'],
    [/chocolat|confiser|bonbon|sucre|cereale|céréale|confiture|miel|petit.?dej|dejeuner|déjeuner|biscuit|gateau|gâteau/, 'epicerie-sucree'],
    [/pate.*sec|pâte.*sec|pates sech|pâtes sech|riz|conserve|sauce|huile|vinaigre|soupe|ap[eé]ritif|chips|plat pr[eé]par|[eé]pice|aide culinaire|condiment/, 'epicerie-salee'],
    [/hygi|bucco|dentaire|protection|cheveux|douche|savon|rasage|beaut|cosmetique|soin/, 'hygiene-et-beaute'],
    [/lessive|linge|vaisselle|nettoyant|entretien|m[eé]nager|essuie|mouchoir|papier toilette|sac poubelle|sac cong|drogue/, 'entretien-bazar'],
    [/b[eé]b[eé]|baby|couche|lingette|infant/, 'univers-bebe'],
    [/animal|animaux|chien|chat|pet food|croquette/, 'animaux'],
    [/pile|energie|énergie|ampoule|electrique|électrique|accessoire multimedia|multim[eé]dia/, 'multimedia'],
    [/bio|organic/, 'produits-bio'],
    [/sans gluten|gluten free/, 'produit-sans-gluten'],
    [/devant.*caisse|caisse|ticket|tabac|jeu|presse/, 'epicerie-sucree'], // devants caisse = impulse sucré
    [/maison|cuisine|table|bougie|parfum|jouet|loisir|deco|décor/, 'maison-et-loisirs'],
  ];
  for (const [re, rayon] of rules) if (re.test(s)) return rayon;
  return null;
}

// Parse une ligne d'inventaire Casino (format PROFRE).
// Colonnes attendues : Code | Libellé | Famille | Qté inv | Qté poids | Qté théo | TVA | PV perm | PA | ValStock... | Taux marge | Stock C/T
// L'ordre des nombres est stable : les 12 derniers "tokens" sont numériques (ou 'C'/'T' à la fin).
function parseInventoryLine(raw) {
  // Doit commencer par 13 chiffres (EAN avec zéros)
  const m = raw.match(/^(\d{13})\s+(.+)$/);
  if (!m) return null;
  const eanRaw = m[1];
  // On retire les zéros de tête pour récupérer l'EAN "natif" (peut être 8, 12 ou 13 chiffres)
  // Puis on normalise : 12 chiffres -> 13 chiffres avec clef de controle
  const ean = normalizeEan(eanRaw.replace(/^0+/, ''));
  const rest = m[2];
  const toNum = (s) => { const n = Number(String(s).replace(',', '.')); return isNaN(n) ? 0 : n; };

  // Famille "NNN - LIBELLE FAMILLE" (accepte ":" dans le label pour cas "VIN :")
  const famMatch = rest.match(/(\d{2,4})\s*-\s*([A-ZÀÁÂÄÇÉÈÊËÎÏÔÖÛÜ'&.\-\s\/:]+?)(?=\s{2,}|\s+-?\d|\s+\d+[,.]\d|$)/);
  let famCode = null, famLabel = null, designation = rest;
  if (famMatch) {
    famCode = famMatch[1];
    famLabel = famMatch[2].trim().replace(/\s+/g, ' ');
    designation = rest.slice(0, famMatch.index).trim();
  }

  // Nombres APRES la famille pour éviter que le code famille soit pris pour un prix.
  // Ordre attendu : [qte_inv, qte_poids, qte_theo, TVA, PV_TTC, PA_HT, ...]
  const afterFam = famMatch ? rest.slice(famMatch.index + famMatch[0].length) : rest;
  const nums = [...afterFam.matchAll(/(-?\d+[,.]?\d*)/g)].map(x => x[1]);
  if (nums.length < 6) return null;
  const nNums = nums.map(toNum);

  // Positions fixes 4 et 5 pour PV/PA (stable dans tous les cas observés)
  const validTva = [0, 2.1, 5.5, 10, 20];
  let tvaRate = 0.055, pvTtc = 0, paHt = 0;
  if (nNums.length >= 6) {
    pvTtc = nNums[4];
    paHt = nNums[5];
    tvaRate = validTva.includes(nNums[3]) ? nNums[3] / 100 : 0.055;
  }

  // Nettoie la désignation : retire les chiffres et pourcentages résiduels en fin
  designation = designation
    .replace(/\s+-?\d+[,.]?\d*\s*%?\s*$/g, '')
    .replace(/\s+/g, ' ')
    .trim();

  if (!designation || pvTtc <= 0) return null;
  return {
    ean,
    ean_raw: eanRaw,
    name: designation,
    family_code: famCode,
    family_label: famLabel,
    tva_rate: tvaRate,
    price: pvTtc,
    purchase_price: paHt,
  };
}

// ========== Parseur inventaire complet avec reflow des lignes tronquées ==========
// Le PDF Casino affiche le libellé produit / famille sur 2 lignes quand il est long.
// Ex: "CHOC BLC NESTLE GALAK 113 - CHOCOLATS EN" / "100G TABLETTES"
// On utilise les coordonnées X pour séparer la continuation (suffixe nom / suffixe famille).
async function parseInventoryPdfWithReflow(file) {
  if (!window.pdfjsLib) throw new Error('pdf.js non chargé. Recharge la page.');
  const buf = await file.arrayBuffer();
  const pdf = await window.pdfjsLib.getDocument({ data: buf }).promise;

  // 1) Extrait les rows avec leurs items (x, str) et un marqueur de début de page
  const rows = [];
  for (let p = 1; p <= pdf.numPages; p++) {
    const page = await pdf.getPage(p);
    const content = await page.getTextContent();
    const byY = new Map();
    for (const item of content.items) {
      const y = Math.round(item.transform[5]);
      if (!byY.has(y)) byY.set(y, []);
      byY.get(y).push({ x: item.transform[4], str: item.str });
    }
    const sortedY = [...byY.keys()].sort((a, b) => b - a);
    rows.push({ items: [], pageBreak: true }); // marqueur début de page
    for (const y of sortedY) {
      const items = byY.get(y).sort((a, b) => a.x - b.x);
      rows.push({ items });
    }
  }

  // 2) Détecte la coordonnée X du début de colonne "Famille"
  let famColStart = null;
  for (const row of rows) {
    const fullText = row.items.map(i => i.str).join(' ');
    if (/^\d{13}\s+/.test(fullText)) {
      for (let i = 0; i < row.items.length; i++) {
        const it = row.items[i];
        if (/^\d{2,4}$/.test(it.str.trim()) && i + 1 < row.items.length && /^-/.test(row.items[i+1].str.trim())) {
          famColStart = it.x; break;
        }
        if (/^\d{2,4}\s*-/.test(it.str.trim())) { famColStart = it.x; break; }
      }
      if (famColStart) break;
    }
  }

  // 3) Parse produits + recolle les continuations (avec coupure aux débuts de page)
  const skipTexts = /^(Quantite|Qte|Prix|Taux|Stock|Ecart|Code|Libelle|Famille|Valeur|inventaire|permanent|theorique|vente|achat|Compte|poids|revient|Theorique|Theoriquee|T\.V\.A|de vente|d'achat|Poids|BOUCHERIE|Page|DEFAULT|VALORISATION|STRUCTURE|INVENTAIRE|GRAND|LEMPS|REPUBLIQUE|copie|UVCI)$/i;
  // Stop le reflow tant qu'on n'a pas rencontré la 1re ligne EAN de la page
  const linesStrs = [];
  const parsed = [];
  let last = null;
  let pageStartedButNoProductYet = false;
  for (const row of rows) {
    if (row.pageBreak) {
      // Nouvelle page : casse le reflow pour ne pas coller le header à un produit précédent
      last = null;
      pageStartedButNoProductYet = true;
      continue;
    }
    const fullText = row.items.map(i => i.str).join(' ').replace(/\s+/g, ' ').trim();
    linesStrs.push(fullText);
    if (/^\d{13}\s+/.test(fullText)) {
      const r = parseInventoryLine(fullText);
      if (r) { parsed.push(r); last = r; pageStartedButNoProductYet = false; } else last = null;
    } else if (last && !pageStartedButNoProductYet && fullText && famColStart !== null && !/^[-= ]+$/.test(fullText)) {
      // Ignore les lignes qui ressemblent à du header/footer de page ou métadonnées
      if (/Page\s+\d+\/|\/app\/|DEFAULT|VALORISATION|REPUBLIQUE|Code article|Libelle article|TVA\s+\d+[,.]?\d*%|Niveau de structure|Symphony|CVC|R0\d\b|EYC/i.test(fullText)) continue;
      const nameParts = [], famParts = [];
      for (const it of row.items) {
        const s = it.str.trim();
        if (!s) continue;
        if (/^[\d.,-]+$/.test(s) || /^[CT]$/.test(s)) continue;
        if (skipTexts.test(s)) continue;
        if (it.x < famColStart - 20) nameParts.push(s);
        else famParts.push(s);
      }
      const nameSuffix = nameParts.join(' ').trim();
      const famSuffix = famParts.join(' ').trim();
      // Limite de sécurité : un suffixe anormalement long = probablement du header
      if (nameSuffix && nameSuffix.length <= 40 && /[A-ZÀ-Ü]/.test(nameSuffix)) {
        last.name = (last.name + ' ' + nameSuffix).trim();
      }
      if (famSuffix && famSuffix.length <= 40 && /[A-ZÀ-Ü]/.test(famSuffix) && last.family_label) {
        last.family_label = (last.family_label + ' ' + famSuffix).trim();
      }
    }
  }
  return { parsed, linesStrs };
}

// Helper : charge / complète la table casino_family_mapping depuis l'inventaire
async function loadCasinoMapping() {
  const { data } = await window.sb.from('casino_family_mapping').select('*');
  const map = new Map();
  for (const row of data || []) map.set(row.code, row);
  return map;
}

async function upsertCasinoFamilies(families, existingMap) {
  // families : Map<code, label>
  const toUpsert = [];
  for (const [code, label] of families) {
    const existing = existingMap.get(code);
    if (existing) continue; // déjà connu, on touche pas
    toUpsert.push({
      code,
      label,
      rayon_id: guessRayonFromCasinoLabel(label),
      is_confirmed: false,
    });
  }
  if (toUpsert.length > 0) {
    await window.sb.from('casino_family_mapping').upsert(toUpsert, { onConflict: 'code' });
  }
  return toUpsert;
}

function aggregateByRayon(rows) {
  const map = new Map();
  for (const r of rows) {
    if (!r.matched) continue;
    const key = r.rayon || 'inconnu';
    if (!map.has(key)) map.set(key, { rayon: key, ca_ttc: 0, ca_ht: 0, marge_ht: 0, nb_lignes: 0, subs: new Map(), with_purchase: 0 });
    const a = map.get(key);
    a.ca_ttc += r.ca_ttc; a.ca_ht += r.ca_ht; a.nb_lignes++;
    if (r.has_purchase) { a.marge_ht += r.marge_ht; a.with_purchase++; }
    const sk = r.sub || 'Tout';
    if (!a.subs.has(sk)) a.subs.set(sk, { sub: sk, ca_ttc: 0, ca_ht: 0, marge_ht: 0, nb_lignes: 0, with_purchase: 0 });
    const s = a.subs.get(sk);
    s.ca_ttc += r.ca_ttc; s.ca_ht += r.ca_ht; s.nb_lignes++;
    if (r.has_purchase) { s.marge_ht += r.marge_ht; s.with_purchase++; }
  }
  return [...map.values()].sort((a, b) => b.ca_ttc - a.ca_ttc);
}

function SalesImportModal({ onClose }) {
  const app = useApp();
  const [status, setStatus] = useState('idle'); // idle | parsing | ready | error
  const [error, setError] = useState(null);
  const [rows, setRows] = useState([]);
  const [filename, setFilename] = useState('');
  const [expanded, setExpanded] = useState({});
  const [saleFrom, setSaleFrom] = useState('');
  const [saleTo, setSaleTo] = useState('');
  const [saveStatus, setSaveStatus] = useState('idle'); // idle | saving | saved | error
  const [enrichStatus, setEnrichStatus] = useState('idle'); // idle | searching | done
  const [duplicateImport, setDuplicateImport] = useState(null); // {id, filename, created_at, total_ca_ttc} si doublon détecté

  // Détection doublon : dès que les dates sont fixées, on vérifie s'il existe déjà un import pour cette période
  useEffect(() => {
    if (!saleFrom || !saleTo || saveStatus === 'saved') { setDuplicateImport(null); return; }
    let cancelled = false;
    (async () => {
      try {
        const { data } = await window.sb.from('sales_imports')
          .select('id, filename, created_at, total_ca_ttc, sale_date_from, sale_date_to')
          .or(`and(sale_date_from.lte.${saleTo},sale_date_to.gte.${saleFrom})`)
          .order('created_at', { ascending: false });
        if (cancelled) return;
        setDuplicateImport((data && data.length > 0) ? data[0] : null);
      } catch (_) {}
    })();
    return () => { cancelled = true; };
  }, [saleFrom, saleTo, saveStatus]);
  const [enrichProgress, setEnrichProgress] = useState({ done: 0, total: 0, dumpHits: 0, liveHits: 0 });
  const [unmatchedEnriched, setUnmatchedEnriched] = useState([]); // [{ean, qty, ca_ttc, pv_ttc_suggested, off, selected, overrideRayon, overrideName}]
  const [importStatus, setImportStatus] = useState('idle');

  const handleFile = async (f) => {
    if (!f) return;
    setFilename(f.name);
    setStatus('parsing');
    setError(null);
    try {
      // Charge les EAN secondaires + catalogue complet (pour matcher les EAN
      // qui ne sont pas dans la liste des produits actifs)
      if (window.ensureAltEans) await window.ensureAltEans();
      if (window.ensureFullCatalog) await window.ensureFullCatalog();
      let parsed;
      if (f.name.toLowerCase().endsWith('.pdf')) {
        const lines = await extractPdfLines(f);
        // Extrait la plage de dates du header PLU
        const range = extractPluDateRange(lines);
        if (range.from) setSaleFrom(range.from);
        if (range.to) setSaleTo(range.to);
        parsed = [];
        for (const l of lines) {
          const r = parsePluLine(l);
          if (!r || r.qty <= 0 || r.ca_ttc <= 0) continue;
          // Flag les codes internes caisse (articles au poids : fruits/légumes/viande/fromage à la coupe)
          // Ils restent dans le rapport mais marqués "weighted" pour traitement à part.
          if (/^0{6,}/.test(r.ean)) r.is_weighted = true;
          parsed.push(r);
        }
      } else {
        const text = await f.text();
        parsed = parseCsv(text);
      }
      if (parsed.length === 0) {
        // Détecte si c'est un PDF d'inventaire (PROFRE) uploadé par erreur
        if (f.name.toLowerCase().endsWith('.pdf')) {
          const lines = await extractPdfLines(f);
          const header = lines.slice(0, 25).join(' ');
          if (/PROFRE|inventaire theorique|Libelle article.*Famille|psinv21p/i.test(header) || /psinv/i.test(f.name)) {
            throw new Error(
              "❌ Ce PDF est un inventaire magasin (PROFRE), pas un export de ventes.\n\n" +
              "➡️ Ferme cette fenêtre et utilise le bouton « Importer inventaire » à droite.\n\n" +
              "Le bouton « Importer export caisse » attend un PDF d'export caisse journalier (PLU, ventes)."
            );
          }
        }
        throw new Error(
          "❌ Aucune ligne de vente reconnue dans ce fichier.\n\n" +
          "Vérifie qu'il s'agit bien d'un PDF d'export caisse (format PLU / « Résultats de vente articles ») ou d'un CSV avec colonnes EAN, qté, CA TTC."
        );
      }
      const enriched = enrichSalesRows(parsed);
      setRows(enriched);
      setStatus('ready');
      // Auto-recherche OFF sur les inconnus
      // On EXCLUT les codes internes caisse (EAN de type 000000000XXX ou < 8 chiffres significatifs)
      // car ce sont des articles au poids / PLU saisis en caisse, jamais dans OpenFoodFacts.
      const isInternalCode = (ean) => !ean || /^0{6,}/.test(ean) || ean.replace(/^0+/, '').length < 8;
      const missing = enriched.filter(r => !r.matched && !isInternalCode(r.ean));
      if (missing.length > 0) {
        runOffEnrichment(missing);
      }
    } catch (e) {
      setError(e.message);
      setStatus('error');
    }
  };

  const runOffEnrichment = async (missing) => {
    setEnrichStatus('searching');
    setEnrichProgress({ done: 0, total: missing.length });

    const buildResult = (r, off, rayonSuggested) => {
      // Priorité au prix de vente permanent du PDF (colonne "Prix vente TTC" du PLU),
      // car ca_ttc/qty peut être faux en cas de remise/promotion sur le ticket.
      const pvTtcFromCa = r.qty > 0 ? r.ca_ttc / r.qty : 0;
      const pvTtc = (r.pv_ttc && r.pv_ttc > 0) ? r.pv_ttc : pvTtcFromCa;
      return {
        ean: r.ean,
        pdf_name: r.name,
        qty: r.qty,
        ca_ttc: r.ca_ttc,
        pv_ttc_suggested: Number(pvTtc.toFixed(2)),
        tva_rate: r.tva_rate || 0.055,
        off: off,
        selected: !!off,
        final_name: off?.name || r.name,
        final_brand: off?.brand || '',
        final_format: off?.format || '',
        final_rayon: rayonSuggested || 'epicerie-salee',
      };
    };

    // Appel de l'Edge Function ean-lookup par batch.
    // L'edge fn cherche d'abord dans le dump OFF France (207k produits en local),
    // puis cache ean_cache, puis API OFF live en fallback.
    const BATCH = 20;
    const results = [];
    let totalDumpHits = 0;
    let totalLiveFetched = 0;
    for (let i = 0; i < missing.length; i += BATCH) {
      const slice = missing.slice(i, i + BATCH);
      console.log(`[ean-lookup] batch ${i}/${missing.length}`, slice.map(r => r.ean));
      try {
        const res = await window.sb.functions.invoke('ean-lookup', {
          body: { eans: slice.map(r => r.ean) },
        });
        console.log('[ean-lookup] response:', res);
        const data = res?.data || {};
        const resultsMap = data.results || {};
        const stats = data.stats || {};
        totalDumpHits += stats.dump_hits || 0;
        totalLiveFetched += stats.live_fetched || 0;
        for (const r of slice) {
          const entry = resultsMap[r.ean];
          if (entry && entry.found) {
            results.push(buildResult(r, entry, entry.rayon_suggested));
          } else {
            results.push(buildResult(r, null, null));
          }
        }
      } catch (e) {
        console.error('[ean-lookup] error:', e);
        for (const r of slice) results.push(buildResult(r, null, null));
      }
      setEnrichProgress({
        done: Math.min(i + BATCH, missing.length),
        total: missing.length,
        dumpHits: totalDumpHits,
        liveHits: totalLiveFetched,
      });
      setUnmatchedEnriched([...results]);
    }

    setEnrichStatus('done');
  };

  const [importProgress, setImportProgress] = useState({ done: 0, total: 0 });

  const saveImport = async () => {
    if (!saleFrom || !saleTo) {
      alert('Vérifie les dates de vente avant d\'enregistrer.');
      return;
    }
    // Détection de doublon : un import avec la même période de vente existe déjà ?
    try {
      const { data: existing } = await window.sb.from('sales_imports')
        .select('id, filename, created_at, total_ca_ttc')
        .eq('sale_date_from', saleFrom)
        .eq('sale_date_to', saleTo)
        .order('created_at', { ascending: false });
      if (existing && existing.length > 0) {
        const prev = existing[0];
        const prevDate = new Date(prev.created_at).toLocaleString('fr-FR');
        const msg = `⚠️ Un import existe déjà pour la période du ${new Date(saleFrom).toLocaleDateString('fr-FR')} au ${new Date(saleTo).toLocaleDateString('fr-FR')} :\n\n` +
          `  Fichier : ${prev.filename}\n` +
          `  Importé le : ${prevDate}\n` +
          `  CA TTC : ${Number(prev.total_ca_ttc).toFixed(2)} €\n\n` +
          `Enregistrer quand même DOUBLERA les chiffres dans le rapport.\n\n` +
          `Si c'est une correction, supprime d'abord l'ancien import depuis l'onglet Historique.\n\n` +
          `Continuer malgré tout ?`;
        if (!confirm(msg)) { return; }
      }
    } catch (e) { console.warn('duplicate check failed', e); /* non bloquant */ }

    setSaveStatus('saving');
    try {
      const matched = rows.filter(r => r.matched);
      const unmatched = rows.filter(r => !r.matched);
      const totalTtc = rows.reduce((s, r) => s + (r.ca_ttc || 0), 0);
      const totalHt = matched.reduce((s, r) => s + (r.ca_ht || 0), 0);
      const totalMarge = matched.filter(r => r.has_purchase).reduce((s, r) => s + (r.marge_ht || 0), 0);
      const cashier = (() => { try { return JSON.parse(sessionStorage.getItem('vival_cashier') || 'null'); } catch { return null; } })();
      // Crée la ligne sales_imports
      const { data, error: e1 } = await window.sb.from('sales_imports').insert({
        store_id: window.VivalData?._storeId,
        uploaded_by: cashier?.name || cashier?.email || 'admin',
        filename,
        source_format: filename.toLowerCase().endsWith('.pdf') ? 'pdf' : 'csv',
        sale_date_from: saleFrom,
        sale_date_to: saleTo,
        total_lines: rows.length,
        total_ca_ttc: totalTtc,
        total_ca_ht: totalHt,
        total_marge_ht: totalMarge,
        unmatched_ean_count: unmatched.length,
        unmatched_ca_ttc: unmatched.reduce((s, r) => s + (r.ca_ttc || 0), 0),
      }).select('id').single();
      if (e1) throw e1;
      const importId = data.id;
      // Insert les lignes par batch 200
      const sid = window.VivalData?._storeId;
      const allLines = rows.map(r => ({
        import_id: importId,
        store_id: sid,
        ean: r.ean || null,
        product_id: r.matched ? (r.product_id || null) : null,
        rayon: r.rayon || null,
        sub: r.sub || null,
        designation: r.name || null,
        qty: Number(r.qty) || 0,
        price_vente_ttc: r.pv_ttc || (r.qty > 0 ? r.ca_ttc / r.qty : 0),
        ca_ttc: Number(r.ca_ttc) || 0,
        ca_ht: r.ca_ht ? Number(r.ca_ht) : null,
        tva_rate: Number(r.tva_rate) || null,
        purchase_price: r.purchase_price ? Number(r.purchase_price) : null,
        marge_ht: r.marge_ht ? Number(r.marge_ht) : null,
        marge_rate: r.marge_rate ? Number(r.marge_rate) : null,
        matched: !!r.matched,
      }));
      for (let i = 0; i < allLines.length; i += 200) {
        const { error: e2 } = await window.sb.from('sales_import_lines').insert(allLines.slice(i, i + 200));
        if (e2) throw e2;
      }
      // Backfill automatique des rayons manquants via regex sur designation
      // (rattrape les lignes PLU internes dont le flag is_weighted aurait pu etre perdu)
      try {
        const { data: backfill } = await window.sb.rpc('backfill_sales_rayons_from_designation', {
          p_store_id: window.VivalData._storeId,
        });
        const n = backfill?.[0]?.updated || 0;
        if (n > 0) console.log('[sales-import] ' + n + ' lignes rayonnees retroactivement');
      } catch (e) { console.warn('backfill rayons', e); }

      // Recalcule la marge reelle avec les PA des BL Casino sur ce nouveau import
      try {
        await window.sb.rpc('refresh_sales_marge_from_bl', {
          p_store_id: window.VivalData._storeId,
          p_from_date: saleFrom,
        });
      } catch (e) { console.warn('refresh marge', e); }

      setSaveStatus('saved');
      app.showToast && app.showToast(`Import enregistré (${rows.length} lignes)`);
    } catch (e) {
      console.error('saveImport', e);
      setSaveStatus('error');
      alert('Erreur d\'enregistrement : ' + (e.message || e));
    }
  };

  const importSelected = async () => {
    const selected = unmatchedEnriched.filter(r => r.selected && r.final_name?.trim());
    if (selected.length === 0) return;
    setImportStatus('importing');
    setImportProgress({ done: 0, total: selected.length });
    let ok = 0, ko = 0;
    for (let i = 0; i < selected.length; i++) {
      const r = selected[i];
      try {
        const product = {
          id: 'imported-' + r.ean,
          rayon: r.final_rayon,
          name: r.final_name.trim(),
          brand: (r.final_brand || '').trim(),
          format: (r.final_format || '').trim(),
          price: r.pv_ttc_suggested,
          badges: [],
          sub: 'Tout',
          byWeight: false,
          stock: 20,
          pinned: false,
          barcode: r.ean,
          image: r.off?.image || null,
          tvaRate: r.tva_rate || 0.055,
        };
        await window.addCustomProduct(product);
        ok++;
      } catch (e) { ko++; }
      setImportProgress({ done: i + 1, total: selected.length });
    }
    setImportStatus('done');
    app.showToast && app.showToast(`${ok} produit${ok>1?'s':''} ajouté${ok>1?'s':''} au catalogue${ko>0?` · ${ko} erreurs`:''}`);
    // Recalcule le rapport avec les nouveaux produits au catalogue
    // IMPORTANT: conserver is_weighted pour que les lignes PLU internes restent classees par rayon
    setRows(r => enrichSalesRows(r.map(x => ({ ean: x.ean, name: x.pdf_name || x.name, qty: x.qty, ca_ttc: x.ca_ttc, tva_rate: x.tva_rate, pv_ttc: x.pv_ttc, ca_ht: x.ca_ht, marge: x.marge, is_weighted: x.is_weighted }))));
    setUnmatchedEnriched(prev => prev.filter(x => !x.selected));
  };

  const matched = rows.filter(r => r.matched);
  const unmatched = rows.filter(r => !r.matched);
  const noPurchase = matched.filter(r => !r.has_purchase);
  const totalTtc = rows.reduce((s, r) => s + r.ca_ttc, 0);
  const totalHtMatched = matched.filter(r => r.has_purchase).reduce((s, r) => s + r.ca_ht, 0);
  const totalMarge = matched.filter(r => r.has_purchase).reduce((s, r) => s + r.marge_ht, 0);
  const margeRate = totalHtMatched > 0 ? (totalMarge / totalHtMatched) * 100 : 0;
  const byRayon = aggregateByRayon(rows);

  return (
    <Modal open={true} onClose={onClose} size="xl" title="Rapport de marge depuis export caisse"
      footer={<>
        {status === 'ready' && saveStatus !== 'saved' && (
          <Button variant="primary" icon="check-circle" onClick={saveImport} disabled={saveStatus === 'saving' || !saleFrom || !saleTo}>
            {saveStatus === 'saving' ? 'Enregistrement…' : "Enregistrer l'import"}
          </Button>
        )}
        {saveStatus === 'saved' && (
          <span style={{fontSize:13, color:'#166534', fontWeight:700, display:'flex', alignItems:'center', gap:6}}>
            <Icon name="check-circle" style={{width:16, height:16}} /> Import enregistré
          </span>
        )}
        <Button variant="ghost" onClick={onClose}>Fermer</Button>
      </>}
    >
      {status === 'idle' && (
        <div style={{padding:20, textAlign:'center'}}>
          <div style={{marginBottom:16, fontSize:14, color:'var(--ink-2)'}}>
            Upload le PDF ou CSV exporté par le logiciel de caisse. L'app croisera chaque EAN avec le catalogue et recalculera la marge à partir du prix d'achat stocké.
          </div>
          <label style={{display:'inline-block', padding:'12px 20px', background:'var(--vival-red)', color:'white', borderRadius:8, fontWeight:700, cursor:'pointer'}}>
            <input type="file" accept=".pdf,.csv,application/pdf,text/csv" style={{display:'none'}} onChange={e => handleFile(e.target.files?.[0])} />
            Choisir un fichier (PDF ou CSV)
          </label>
        </div>
      )}
      {status === 'parsing' && (
        <div style={{padding:40, textAlign:'center'}}>
          <div style={{fontSize:14}}>Analyse de <strong>{filename}</strong>…</div>
        </div>
      )}
      {status === 'error' && (
        <div style={{padding:20, color:'var(--danger, #c02424)'}}>
          <strong>Erreur :</strong> {error}
          <div style={{marginTop:12}}>
            <Button variant="ghost" onClick={() => { setStatus('idle'); setError(null); }}>Recommencer</Button>
          </div>
        </div>
      )}
      {status === 'ready' && (
        <div>
          {/* Bandeau dates de vente */}
          <div style={{padding:'12px 14px', marginBottom:16, background: (saleFrom && saleTo) ? 'var(--surface-1)' : '#fff7ed', border: '1px solid ' + ((saleFrom && saleTo) ? 'var(--line-1)' : 'var(--warning, #ea580c)'), borderRadius:8, display:'flex', alignItems:'center', gap:12, flexWrap:'wrap'}}>
            <Icon name="clock" style={{width:18, height:18, color: (saleFrom && saleTo) ? 'var(--ink-3)' : 'var(--warning, #ea580c)'}} />
            <div style={{fontSize:13, fontWeight:700}}>Période de vente détectée sur le PDF :</div>
            <label style={{fontSize:13, display:'flex', alignItems:'center', gap:6}}>
              Du
              <input type="date" value={saleFrom} max={saleTo || undefined} onChange={e => setSaleFrom(e.target.value)}
                style={{padding:'6px 8px', border:'1px solid var(--line-1)', borderRadius:6, fontSize:13}} />
            </label>
            <label style={{fontSize:13, display:'flex', alignItems:'center', gap:6}}>
              Au
              <input type="date" value={saleTo} min={saleFrom || undefined} onChange={e => setSaleTo(e.target.value)}
                style={{padding:'6px 8px', border:'1px solid var(--line-1)', borderRadius:6, fontSize:13}} />
            </label>
            {(!saleFrom || !saleTo) && (
              <span style={{fontSize:12, color:'var(--warning, #ea580c)', fontWeight:600}}>⚠ Non détectée automatiquement, renseigne-la à la main</span>
            )}
          </div>

          {/* Bandeau ALERTE DOUBLON : un import existe déjà pour cette période */}
          {duplicateImport && saveStatus !== 'saved' && (
            <div style={{padding:'12px 14px', marginBottom:16, background:'#fef2f2', border:'2px solid #dc2626', borderRadius:8}}>
              <div style={{display:'flex', alignItems:'flex-start', gap:12}}>
                <Icon name="alert" style={{width:20, height:20, color:'#dc2626', flexShrink:0, marginTop:2}} />
                <div style={{flex:1}}>
                  <div style={{fontSize:14, fontWeight:800, color:'#991b1b', marginBottom:4}}>
                    ⚠️ Un import existe déjà pour cette période
                  </div>
                  <div style={{fontSize:13, color:'#7f1d1d', marginBottom:8}}>
                    <strong>{duplicateImport.filename}</strong> · importé le {new Date(duplicateImport.created_at).toLocaleString('fr-FR')} · CA TTC {Number(duplicateImport.total_ca_ttc || 0).toFixed(2)} €
                  </div>
                  <div style={{fontSize:12, color:'#991b1b', lineHeight:1.5}}>
                    Si tu enregistres cet import, les chiffres <strong>seront doublés</strong> dans les rapports.
                    <br />
                    Si c'est une correction : <strong>va dans l'onglet « Historique »</strong> pour supprimer d'abord l'ancien import.
                  </div>
                </div>
              </div>
            </div>
          )}
          <div style={{display:'grid', gridTemplateColumns:'repeat(auto-fit, minmax(160px, 1fr))', gap:12, marginBottom:20}}>
            <div style={{padding:14, background:'var(--surface-1)', borderRadius:10}}>
              <div style={{fontSize:11, textTransform:'uppercase', color:'var(--ink-3)', fontWeight:700}}>CA TTC total</div>
              <div style={{fontSize:22, fontWeight:800, marginTop:4}}>{formatPrice(totalTtc).full}</div>
              <div style={{fontSize:11, color:'var(--ink-3)'}}>{rows.length} lignes</div>
            </div>
            <div style={{padding:14, background:'#dcfce7', borderRadius:10}}>
              <div style={{fontSize:11, textTransform:'uppercase', color:'#166534', fontWeight:700}}>Marge HT</div>
              <div style={{fontSize:22, fontWeight:800, marginTop:4, color:'#14532d'}}>{formatPrice(totalMarge).full}</div>
              <div style={{fontSize:11, color:'#166534'}}>{margeRate.toFixed(1).replace('.',',')} %</div>
            </div>
            {unmatched.length > 0 && (
              <div style={{padding:14, background:'#fee2e2', borderRadius:10}}>
                <div style={{fontSize:11, textTransform:'uppercase', color:'#991b1b', fontWeight:700}}>EAN inconnus</div>
                <div style={{fontSize:22, fontWeight:800, marginTop:4, color:'#991b1b'}}>{unmatched.length}</div>
                <div style={{fontSize:11, color:'#991b1b'}}>{formatPrice(unmatched.reduce((s,r)=>s+r.ca_ttc,0)).full}</div>
              </div>
            )}
            {noPurchase.length > 0 && (
              <div style={{padding:14, background:'#fff7ed', borderRadius:10}}>
                <div style={{fontSize:11, textTransform:'uppercase', color:'#9a3412', fontWeight:700}}>Sans prix d'achat</div>
                <div style={{fontSize:22, fontWeight:800, marginTop:4, color:'#9a3412'}}>{noPurchase.length}</div>
                <div style={{fontSize:11, color:'#9a3412'}}>{formatPrice(noPurchase.reduce((s,r)=>s+r.ca_ttc,0)).full}</div>
              </div>
            )}
          </div>

          <h3 style={{fontSize:15, fontWeight:800, margin:'0 0 10px'}}>Marge par rayon</h3>
          <table style={{width:'100%', borderCollapse:'collapse', fontSize:13, marginBottom:20}}>
            <thead>
              <tr style={{background:'var(--surface-1)', textAlign:'left'}}>
                <th style={{padding:'8px 10px'}}>Rayon</th>
                <th style={{padding:'8px 10px', textAlign:'right'}}>CA TTC</th>
                <th style={{padding:'8px 10px', textAlign:'right'}}>Marge HT</th>
                <th style={{padding:'8px 10px', textAlign:'right'}}>Taux</th>
              </tr>
            </thead>
            <tbody>
              {byRayon.flatMap(r => {
                const rayonObj = window.VivalData.RAYONS.find(x => x.id === r.rayon);
                const rayonName = rayonObj?.name || r.rayon;
                const rate = r.ca_ht > 0 ? (r.marge_ht / r.ca_ht) * 100 : 0;
                const isOpen = !!expanded[r.rayon];
                const rowMain = (
                  <tr key={'r-' + r.rayon}
                      onClick={() => setExpanded(s => ({...s, [r.rayon]: !isOpen}))}
                      style={{cursor:'pointer', borderBottom:'1px solid var(--line-2)'}}>
                    <td style={{padding:'8px 10px', fontWeight:700}}>
                      {isOpen ? '▼' : '▶'} {rayonName}
                      <span className="muted" style={{marginLeft:8, fontSize:11, fontWeight:400}}>{r.nb_lignes} lignes</span>
                    </td>
                    <td style={{padding:'8px 10px', textAlign:'right'}}>{formatPrice(r.ca_ttc).full}</td>
                    <td style={{padding:'8px 10px', textAlign:'right', fontWeight:700, color:'#16a34a'}}>{formatPrice(r.marge_ht).full}</td>
                    <td style={{padding:'8px 10px', textAlign:'right', fontWeight:700}}>{rate.toFixed(1).replace('.',',')} %</td>
                  </tr>
                );
                if (!isOpen) return [rowMain];
                const subRows = [...r.subs.values()].sort((a,b) => b.ca_ttc - a.ca_ttc).map(s => {
                  const srate = s.ca_ht > 0 ? (s.marge_ht / s.ca_ht) * 100 : 0;
                  return (
                    <tr key={'s-' + r.rayon + '-' + s.sub} style={{background:'#f9fafb', borderBottom:'1px solid var(--line-2)'}}>
                      <td style={{padding:'6px 10px 6px 38px', fontSize:12}}>{s.sub} <span className="muted">· {s.nb_lignes}</span></td>
                      <td style={{padding:'6px 10px', textAlign:'right', fontSize:12}}>{formatPrice(s.ca_ttc).full}</td>
                      <td style={{padding:'6px 10px', textAlign:'right', fontSize:12, color:'#16a34a'}}>{formatPrice(s.marge_ht).full}</td>
                      <td style={{padding:'6px 10px', textAlign:'right', fontSize:12}}>{srate.toFixed(1).replace('.',',')} %</td>
                    </tr>
                  );
                });
                return [rowMain, ...subRows];
              })}
            </tbody>
          </table>

          {unmatched.length > 0 && (
            <div style={{marginBottom:16}}>
              <div style={{display:'flex', alignItems:'center', gap:12, marginBottom:10, flexWrap:'wrap'}}>
                <h3 style={{fontSize:15, fontWeight:800, margin:0, color:'#991b1b', flex:1}}>
                  EAN non reconnus ({unmatched.length})
                </h3>
                {enrichStatus === 'idle' && (
                  <Button variant="primary" size="sm" icon="search"
                    onClick={() => runOffEnrichment(unmatched.filter(r => r.ean && !/^0{6,}/.test(r.ean)))}>
                    Rechercher dans OpenFoodFacts
                  </Button>
                )}
                {enrichStatus === 'done' && unmatchedEnriched.length > 0 && (() => {
                  const selCount = unmatchedEnriched.filter(x => x.selected).length;
                  return (
                    <Button variant="primary" size="sm" icon="plus" disabled={selCount === 0 || importStatus === 'importing'} onClick={importSelected}>
                      {importStatus === 'importing' ? 'Import…' : `Importer ${selCount} produit${selCount > 1 ? 's' : ''}`}
                    </Button>
                  );
                })()}
                {enrichStatus === 'done' && unmatchedEnriched.length === 0 && (
                  <Button variant="outline" size="sm" icon="search"
                    onClick={() => runOffEnrichment(unmatched.filter(r => r.ean && !/^0{6,}/.test(r.ean)))}>
                    Relancer la recherche OFF
                  </Button>
                )}
              </div>

              {/* Liste brute si l'enrichissement n'a pas produit de résultats */}
              {unmatchedEnriched.length === 0 && enrichStatus !== 'searching' && (
                <div style={{border:'1px solid var(--line-2)', borderRadius:8, padding:12, background:'#fef2f2'}}>
                  <div style={{fontSize:12, color:'#991b1b', marginBottom:8}}>
                    {enrichStatus === 'done'
                      ? 'Aucun produit trouvé dans OpenFoodFacts. Tu peux les ajouter manuellement au catalogue depuis la fiche produit.'
                      : 'Clique « Rechercher dans OpenFoodFacts » pour enrichir automatiquement ces EAN.'}
                  </div>
                  <table style={{width:'100%', fontSize:12, borderCollapse:'collapse'}}>
                    <thead><tr style={{textAlign:'left', color:'#991b1b'}}>
                      <th style={{padding:'4px 8px'}}>EAN</th>
                      <th style={{padding:'4px 8px'}}>Désignation PDF</th>
                      <th style={{padding:'4px 8px', textAlign:'right'}}>Qté</th>
                      <th style={{padding:'4px 8px', textAlign:'right'}}>CA TTC</th>
                      <th style={{padding:'4px 8px'}}>Suggestion catalogue</th>
                    </tr></thead>
                    <tbody>
                      {unmatched.map((r, i) => (
                        <tr key={`unm-${r.ean}-${i}`} style={{borderTop:'1px solid #fecaca'}}>
                          <td style={{padding:'4px 8px', fontFamily:'var(--font-mono)'}}>{r.ean}</td>
                          <td style={{padding:'4px 8px'}}>{r.name}</td>
                          <td style={{padding:'4px 8px', textAlign:'right'}}>{r.qty}</td>
                          <td style={{padding:'4px 8px', textAlign:'right', fontWeight:600}}>{formatPrice(r.ca_ttc).full}</td>
                          <td style={{padding:'4px 8px'}}>
                            <UnmatchedSuggestion pdfName={r.name} pdfEan={r.ean} onLinked={() => setRows(rows.map(x => x.ean === r.ean ? { ...x, _linked: true } : x))} linked={r._linked} />
                          </td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                </div>
              )}
              {enrichStatus === 'searching' && (() => {
                const pct = enrichProgress.total > 0 ? Math.round((enrichProgress.done / enrichProgress.total) * 100) : 0;
                return (
                  <div style={{marginBottom:14, padding:'12px 14px', background:'var(--surface-1)', borderRadius:8, border:'1px solid var(--line-2)'}}>
                    <div style={{display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom:8, fontSize:13}}>
                      <div style={{display:'flex', alignItems:'center', gap:8, fontWeight:600}}>
                        <Icon name="clock" style={{width:14, height:14, color:'var(--vival-red)'}} />
                        Enrichissement des produits
                      </div>
                      <div style={{fontWeight:700, fontVariantNumeric:'tabular-nums'}}>
                        {enrichProgress.done} / {enrichProgress.total} <span style={{color:'var(--ink-3)'}}>· {pct}%</span>
                      </div>
                    </div>
                    <div style={{height:10, background:'var(--line-2, #e5e7eb)', borderRadius:5, overflow:'hidden'}}>
                      <div style={{
                        width: `${Math.max(2, pct)}%`,
                        height:'100%',
                        background: 'linear-gradient(90deg, var(--vival-red), #ef4444)',
                        transition: 'width .3s ease-out',
                      }} />
                    </div>
                    <div style={{fontSize:11, marginTop:6, display:'flex', gap:14, color:'var(--ink-3)'}}>
                      <span>🗄️ <strong style={{color:'var(--ink-2)'}}>{enrichProgress.dumpHits || 0}</strong> trouvés dans la base locale (instantané)</span>
                      {(enrichProgress.liveHits || 0) > 0 && <span>🌐 <strong style={{color:'var(--ink-2)'}}>{enrichProgress.liveHits}</strong> via API OFF (produits récents)</span>}
                    </div>
                  </div>
                );
              })()}
              {importStatus === 'importing' && (() => {
                const pct = importProgress.total > 0 ? Math.round((importProgress.done / importProgress.total) * 100) : 0;
                return (
                  <div style={{marginBottom:14, padding:'12px 14px', background:'#ecfdf5', borderRadius:8, border:'1px solid #6ee7b7'}}>
                    <div style={{display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom:8, fontSize:13}}>
                      <div style={{display:'flex', alignItems:'center', gap:8, fontWeight:600, color:'#065f46'}}>
                        <Icon name="plus" style={{width:14, height:14}} />
                        Ajout au catalogue en cours
                      </div>
                      <div style={{fontWeight:700, fontVariantNumeric:'tabular-nums', color:'#065f46'}}>
                        {importProgress.done} / {importProgress.total} <span style={{color:'#059669'}}>· {pct}%</span>
                      </div>
                    </div>
                    <div style={{height:10, background:'#a7f3d0', borderRadius:5, overflow:'hidden'}}>
                      <div style={{
                        width: `${Math.max(2, pct)}%`,
                        height:'100%',
                        background: 'linear-gradient(90deg, #10b981, #059669)',
                        transition: 'width .2s ease-out',
                      }} />
                    </div>
                  </div>
                );
              })()}
              {unmatchedEnriched.length > 0 && (
                <div style={{border:'1px solid var(--line-2)', borderRadius:8, overflow:'hidden'}}>
                  <div style={{padding:'6px 10px', background:'var(--surface-1)', fontSize:11, color:'var(--ink-3)', display:'flex', gap:10}}>
                    <label style={{display:'flex', alignItems:'center', gap:6, cursor:'pointer'}}>
                      <input type="checkbox"
                        checked={unmatchedEnriched.every(x => x.selected)}
                        onChange={e => setUnmatchedEnriched(prev => prev.map(x => ({...x, selected: e.target.checked})))} />
                      Tout
                    </label>
                    <span>|</span>
                    <button onClick={() => setUnmatchedEnriched(prev => prev.map(x => ({...x, selected: !!x.off})))}
                      style={{background:'none', border:0, color:'var(--vival-red)', cursor:'pointer', padding:0, fontSize:11, fontWeight:600}}>
                      Seulement les OFF trouvés ({unmatchedEnriched.filter(x => x.off).length})
                    </button>
                  </div>
                  <div style={{maxHeight:400, overflowY:'auto'}}>
                    <table style={{width:'100%', borderCollapse:'collapse', fontSize:12}}>
                      <thead style={{background:'var(--surface-2, #f9fafb)', position:'sticky', top:0}}>
                        <tr>
                          <th style={{padding:'8px', width:32}}></th>
                          <th style={{padding:'8px 6px', textAlign:'left'}}>Produit</th>
                          <th style={{padding:'8px 6px', textAlign:'left', width:120}}>Rayon</th>
                          <th style={{padding:'8px 6px', textAlign:'right', width:55}}>Qté</th>
                          <th style={{padding:'8px 6px', textAlign:'right', width:70}}>Prix TTC</th>
                          <th style={{padding:'8px 6px', textAlign:'right', width:70}}>CA</th>
                        </tr>
                      </thead>
                      <tbody>
                        {unmatchedEnriched.map((r, i) => {
                          const found = !!r.off;
                          return (
                            <tr key={`${r.ean}-${i}`} style={{borderTop:'1px solid var(--line-2)', background: r.selected ? '#f0fdf4' : 'transparent'}}>
                              <td style={{padding:'6px 4px', textAlign:'center'}}>
                                <input type="checkbox" checked={r.selected}
                                  onChange={e => setUnmatchedEnriched(prev => prev.map((x, j) => j === i ? { ...x, selected: e.target.checked } : x))} />
                              </td>
                              <td style={{padding:'6px'}}>
                                <div style={{display:'flex', gap:8, alignItems:'center'}}>
                                  {r.off?.image ? (
                                    <img src={r.off.image} alt="" style={{width:36, height:36, objectFit:'contain', background:'white', border:'1px solid var(--line-2)', borderRadius:4}} onError={e => e.target.style.display='none'} />
                                  ) : (
                                    <div style={{width:36, height:36, background:'var(--surface-2)', borderRadius:4, display:'flex', alignItems:'center', justifyContent:'center', color:'var(--ink-4)', fontSize:10}}>?</div>
                                  )}
                                  <div style={{flex:1, minWidth:0}}>
                                    <input type="text" value={r.final_name || ''}
                                      onChange={e => setUnmatchedEnriched(prev => prev.map((x, j) => j === i ? { ...x, final_name: e.target.value } : x))}
                                      style={{width:'100%', border:0, padding:'2px 4px', fontSize:12, fontWeight:600, background:'transparent'}} />
                                    <div style={{fontSize:10, color: found ? '#166534' : '#991b1b', marginTop:1}}>
                                      {found ? (
                                        <>OFF : {r.final_brand || '—'}{r.final_format ? ' · ' + r.final_format : ''}</>
                                      ) : (
                                        <>Non trouvé sur OFF · Libellé PDF : {r.pdf_name}</>
                                      )}
                                    </div>
                                    <div style={{fontSize:10, fontFamily:'var(--font-mono)', color:'var(--ink-3)', marginTop:1}}>EAN {r.ean}</div>
                                  </div>
                                </div>
                              </td>
                              <td style={{padding:'6px'}}>
                                <select value={r.final_rayon}
                                  onChange={e => setUnmatchedEnriched(prev => prev.map((x, j) => j === i ? { ...x, final_rayon: e.target.value } : x))}
                                  style={{width:'100%', padding:'3px 4px', fontSize:11, border:'1px solid var(--line-2)', borderRadius:4, background:'white'}}>
                                  {window.VivalData.RAYONS.map(ry => <option key={ry.id} value={ry.id}>{ry.name}</option>)}
                                </select>
                              </td>
                              <td style={{padding:'6px 6px', textAlign:'right'}}>{r.qty}</td>
                              <td style={{padding:'6px 6px', textAlign:'right', fontWeight:700}}>{formatPrice(r.pv_ttc_suggested).full}</td>
                              <td style={{padding:'6px 6px', textAlign:'right'}}>{formatPrice(r.ca_ttc).full}</td>
                            </tr>
                          );
                        })}
                      </tbody>
                    </table>
                  </div>
                </div>
              )}
            </div>
          )}

          {noPurchase.length > 0 && (
            <details>
              <summary style={{cursor:'pointer', fontSize:14, fontWeight:800, padding:'8px 0', color:'#9a3412'}}>
                Produits sans prix d'achat — marge non calculée ({noPurchase.length})
              </summary>
              <div style={{maxHeight:280, overflowY:'auto', border:'1px solid var(--line-2)', borderRadius:6, marginTop:8}}>
                <table style={{width:'100%', borderCollapse:'collapse', fontSize:12}}>
                  <tbody>
                    {noPurchase.sort((a,b) => b.ca_ttc - a.ca_ttc).map((r, i) => (
                      <tr key={i} style={{borderBottom:'1px solid var(--line-2)'}}>
                        <td style={{padding:'6px 8px'}}>{r.name}</td>
                        <td style={{padding:'6px 8px', textAlign:'right'}}>{r.qty}×</td>
                        <td style={{padding:'6px 8px', textAlign:'right', fontWeight:600}}>{formatPrice(r.ca_ttc).full}</td>
                      </tr>
                    ))}
                  </tbody>
                </table>
              </div>
            </details>
          )}
        </div>
      )}
    </Modal>
  );
}

// ========== Modal : éditer les prix d'achat manquants ==========
function MissingPurchasePriceModal({ onClose }) {
  const app = useApp();
  const [products, setProducts] = useState(null);
  const [edits, setEdits] = useState({}); // { id: newPA }
  const [savingId, setSavingId] = useState(null);
  const [saveAll, setSaveAll] = useState(false);
  const [search, setSearch] = useState('');

  const load = () => {
    const list = (window.VivalData?.PRODUCTS || [])
      .filter(p => !p.purchasePrice || p.purchasePrice === 0)
      .filter(p => p.isActive !== false)
      .sort((a, b) => (b.price || 0) - (a.price || 0)); // tri par prix vente DESC
    setProducts(list);
  };
  useEffect(() => { load(); }, []);

  const setEdit = (id, val) => setEdits(e => ({ ...e, [id]: val }));

  const saveOne = async (p) => {
    const val = edits[p.id];
    const pa = parseFloat(String(val ?? '').replace(',', '.'));
    if (isNaN(pa) || pa <= 0) { alert('Prix d\'achat invalide'); return; }
    setSavingId(p.id);
    try {
      await window.updateProduct(p.id, { purchasePrice: pa });
      // Retire localement de la liste et met à jour VivalData.PRODUCTS
      const idx = window.VivalData.PRODUCTS.findIndex(x => x.id === p.id);
      if (idx !== -1) window.VivalData.PRODUCTS[idx] = { ...window.VivalData.PRODUCTS[idx], purchasePrice: pa };
      setProducts(curr => (curr || []).filter(x => x.id !== p.id));
      setEdits(e => { const c = { ...e }; delete c[p.id]; return c; });
      app.showToast && app.showToast(`PA enregistré pour ${p.name}`);
    } catch (e) { alert('Erreur : ' + (e.message || e)); }
    finally { setSavingId(null); }
  };

  const saveAllPending = async () => {
    const todo = Object.entries(edits).filter(([id, v]) => {
      const pa = parseFloat(String(v ?? '').replace(',', '.'));
      return !isNaN(pa) && pa > 0;
    });
    if (todo.length === 0) { alert('Aucune saisie valide à enregistrer.'); return; }
    setSaveAll(true);
    let ok = 0, ko = 0;
    try {
      for (const [id, v] of todo) {
        const pa = parseFloat(String(v).replace(',', '.'));
        try {
          await window.updateProduct(id, { purchasePrice: pa });
          const idx = window.VivalData.PRODUCTS.findIndex(x => x.id === id);
          if (idx !== -1) window.VivalData.PRODUCTS[idx] = { ...window.VivalData.PRODUCTS[idx], purchasePrice: pa };
          ok++;
        } catch { ko++; }
      }
      app.showToast && app.showToast(`${ok} PA enregistré${ok > 1 ? 's' : ''}${ko > 0 ? ` · ${ko} erreurs` : ''}`);
      load();
      setEdits({});
    } finally { setSaveAll(false); }
  };

  const apply70pct = (p) => {
    if (!p.price) return;
    setEdit(p.id, (p.price / 1.055 * 0.70).toFixed(2));
  };

  const filtered = (products || []).filter(p => {
    if (!search) return true;
    const s = search.toLowerCase();
    return (p.name || '').toLowerCase().includes(s) || (p.barcode || '').includes(s);
  });

  const fmtEuro = (v) => Number(v || 0).toLocaleString('fr-FR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + ' €';
  const nbPending = Object.values(edits).filter(v => {
    const pa = parseFloat(String(v ?? '').replace(',', '.'));
    return !isNaN(pa) && pa > 0;
  }).length;

  return (
    <Modal open={true} onClose={onClose} size="xl" title={`Prix d'achat manquants (${products?.length || 0})`}
      footer={<>
        <span className="muted" style={{fontSize:12, flex:1}}>
          {nbPending > 0 ? `${nbPending} saisie${nbPending > 1 ? 's' : ''} en attente` : 'Saisis les PA pour calculer les marges'}
        </span>
        {nbPending > 0 && (
          <Button variant="primary" icon="check" onClick={saveAllPending} disabled={saveAll}>
            {saveAll ? 'Enregistrement…' : `Enregistrer tout (${nbPending})`}
          </Button>
        )}
        <Button variant="ghost" onClick={onClose}>Fermer</Button>
      </>}>
      <div style={{padding:16}}>
        <div style={{marginBottom:12, padding:'10px 14px', background:'#fffbeb', border:'1px solid #fbbf24', borderRadius:8, fontSize:13, color:'#92400e'}}>
          <strong>Ces produits ont été créés sans prix d'achat</strong> (typiquement lors d'un import caisse pour des EAN non présents dans l'inventaire).
          Renseigne leur PA HT ici : les rapports de marge se mettront à jour automatiquement, y compris pour les ventes déjà enregistrées.
        </div>

        <input type="search" placeholder="Rechercher par nom ou EAN"
          value={search} onChange={e => setSearch(e.target.value)}
          style={{width:'100%', padding:'10px 12px', border:'1px solid var(--line-1)', borderRadius:8, fontSize:14, marginBottom:12}} />

        {products === null && <div className="muted" style={{padding:24, textAlign:'center'}}>Chargement…</div>}
        {products && products.length === 0 && (
          <div style={{padding:32, textAlign:'center', background:'#dcfce7', borderRadius:10, color:'#166534', fontWeight:700}}>
            ✅ Tous les produits actifs ont un prix d'achat !
          </div>
        )}

        {products && products.length > 0 && (
          <div style={{maxHeight:'55vh', overflowY:'auto', border:'1px solid var(--line-1)', borderRadius:8}}>
            <table style={{width:'100%', fontSize:13, borderCollapse:'collapse'}}>
              <thead style={{position:'sticky', top:0, background:'var(--surface-1)', zIndex:1}}>
                <tr style={{textAlign:'left'}}>
                  <th style={{padding:'10px 12px'}}>Produit</th>
                  <th style={{padding:'10px 12px'}}>Rayon</th>
                  <th style={{padding:'10px 12px', textAlign:'right'}}>Prix vente TTC</th>
                  <th style={{padding:'10px 12px', textAlign:'right', width:200}}>Prix d'achat HT</th>
                  <th style={{padding:'10px 12px', width:110}}></th>
                </tr>
              </thead>
              <tbody>
                {filtered.map(p => {
                  const val = edits[p.id] !== undefined ? edits[p.id] : '';
                  const paNum = parseFloat(String(val).replace(',', '.'));
                  const valid = !isNaN(paNum) && paNum > 0;
                  const mEstim = valid && p.price ? ((p.price / (1 + (p.tvaRate || 0.055)) - paNum) / (p.price / (1 + (p.tvaRate || 0.055))) * 100) : null;
                  return (
                    <tr key={p.id} style={{borderTop:'1px solid var(--line-2)'}}>
                      <td style={{padding:'8px 12px'}}>
                        <div style={{display:'flex', gap:8, alignItems:'center'}}>
                          {p.image
                            ? <img src={p.image} alt="" style={{width:32, height:32, objectFit:'contain', background:'var(--surface-2)', borderRadius:4}} onError={e => e.target.style.display='none'} />
                            : <div style={{width:32, height:32, background:'var(--surface-2)', borderRadius:4}} />}
                          <div style={{flex:1, minWidth:0}}>
                            <div style={{fontWeight:600}}>{p.name}</div>
                            {p.barcode && <div className="muted" style={{fontSize:10, fontFamily:'var(--font-mono)'}}>EAN {p.barcode}</div>}
                          </div>
                        </div>
                      </td>
                      <td style={{padding:'8px 12px', fontSize:12, color:'var(--ink-3)'}}>{p.rayon}</td>
                      <td style={{padding:'8px 12px', textAlign:'right', fontWeight:600, fontVariantNumeric:'tabular-nums'}}>{fmtEuro(p.price)}</td>
                      <td style={{padding:'6px 12px', textAlign:'right'}}>
                        <div style={{display:'flex', gap:4, alignItems:'center', justifyContent:'flex-end'}}>
                          <input type="text" inputMode="decimal" value={val}
                            onChange={e => setEdit(p.id, e.target.value)}
                            onKeyDown={e => { if (e.key === 'Enter') saveOne(p); }}
                            placeholder="0,00"
                            style={{width:80, padding:'6px 8px', border: '1px solid ' + (valid ? '#16a34a' : 'var(--line-1)'), borderRadius:6, fontSize:13, textAlign:'right', fontVariantNumeric:'tabular-nums'}} />
                          <button type="button" onClick={() => apply70pct(p)} title="Appliquer 30% de marge (PA = 70% du prix HT)"
                            style={{padding:'4px 6px', fontSize:10, background:'var(--surface-1)', border:'1px solid var(--line-1)', borderRadius:4, cursor:'pointer', fontWeight:600, color:'var(--ink-2)'}}>
                            70%
                          </button>
                        </div>
                        {mEstim !== null && (
                          <div style={{fontSize:10, color: mEstim > 20 ? '#166534' : mEstim > 5 ? '#d97706' : '#dc2626', marginTop:2, fontWeight:600}}>
                            marge {mEstim.toFixed(1).replace('.', ',')}%
                          </div>
                        )}
                      </td>
                      <td style={{padding:'6px 12px', textAlign:'right'}}>
                        <Button variant="primary" size="sm" icon="check" onClick={() => saveOne(p)} disabled={savingId === p.id || !valid}>
                          {savingId === p.id ? '…' : 'OK'}
                        </Button>
                      </td>
                    </tr>
                  );
                })}
                {filtered.length === 0 && products.length > 0 && (
                  <tr><td colSpan={5} style={{padding:24, textAlign:'center'}} className="muted">Aucun produit ne correspond.</td></tr>
                )}
              </tbody>
            </table>
          </div>
        )}
      </div>
    </Modal>
  );
}

function DashboardScreen() {
  const { orders, setRoute } = useApp();
  const stockEnabled = useModule('stock');
  const [missingPaModalOpen, setMissingPaModalOpen] = useState(false);
  const today = orders.filter(o => {
    const d = new Date(o.createdAt);
    const n = new Date();
    return d.toDateString() === n.toDateString();
  });
  const ca = today.reduce((s, o) => s + o.lines.reduce((a, l) => {
    if (l.manual) return a + (Number(l.unitPrice ?? l.price) || 0) * l.qty;
    const p = PRODUCTS.find(x => x.id === l.pid);
    return a + (p ? p.price * l.qty : 0);
  }, 0), 0);
  const outOfStock = PRODUCTS.filter(p => !p.deleted && !p.byWeight && (p.stock || 0) === 0);
  const lowStock = PRODUCTS.filter(p => !p.deleted && !p.byWeight && p.stock > 0 && p.stock <= 3);

  // ===== Marges =====
  const [marginPeriod, setMarginPeriod] = useState('today');
  const [marginFrom, setMarginFrom] = useState(toDateInput(new Date(Date.now() - 6 * 86400000)));
  const [marginTo, setMarginTo] = useState(toDateInput(new Date()));

  const marginData = useMemo(() => {
    const { from, to } = getPeriodRange(marginPeriod, marginFrom, marginTo);
    const productsById = new Map(PRODUCTS.map(p => [p.id, p]));
    const relevant = orders.filter(o => {
      if (o.status === 'annulee') return false;
      const d = new Date(o.createdAt);
      return d >= from && d <= to;
    });
    let totalMargin = 0;
    let totalHt = 0;
    let totalTtc = 0;
    const missingPidSet = new Set();
    for (const o of relevant) {
      for (const l of o.lines) {
        const p = productsById.get(l.pid);
        if (!p) continue;
        const tva = Number(l.tvaRate ?? p.tvaRate) || 0;
        const unitTtc = Number(l.unitPrice ?? p.price) || 0;
        const unitHt = unitTtc / (1 + tva);
        const qty = Number(l.qty) || 0;
        const lineHt = unitHt * qty;
        const lineTtc = unitTtc * qty;
        totalHt += lineHt;
        totalTtc += lineTtc;
        if (!p.purchasePrice || p.purchasePrice <= 0) {
          missingPidSet.add(l.pid);
        } else {
          totalMargin += lineHt - (p.purchasePrice * qty);
        }
      }
    }
    const missingCount = PRODUCTS.filter(p => !p.deleted && (!p.purchasePrice || p.purchasePrice <= 0)).length;
    const marginRate = totalHt > 0 ? (totalMargin / totalHt) * 100 : 0;
    const avgPerOrder = relevant.length > 0 ? totalMargin / relevant.length : 0;
    return { totalMargin, totalHt, totalTtc, marginRate, avgPerOrder, orderCount: relevant.length, missingCount, missingInPeriod: missingPidSet.size, from, to };
  }, [orders, marginPeriod, marginFrom, marginTo]);

  const marginPeriods = [
    { id: 'today', label: "Aujourd'hui" },
    { id: 'week', label: '7 derniers jours' },
    { id: 'month', label: 'Ce mois-ci' },
    { id: 'custom', label: 'Personnalisé' },
  ];

  return (
    <>
      <div className="page-header">
        <div className="page-header-grow">
          <h1 className="page-title">Tableau de bord</h1>
          <p className="page-subtitle">Commandes faites dans l'app (téléphone, site, livraison) — pour la vue complète avec les ventes caisse, voir Rapports</p>
        </div>
        <Button variant="primary" size="lg" icon="plus" onClick={() => setRoute('catalog')}>Nouvelle commande</Button>
      </div>

      {stockEnabled && (outOfStock.length > 0 || lowStock.length > 0) && (
        <div className="dash-banner" style={{margin:'0 32px 16px', padding:'12px 16px', background:'var(--warning-soft, #fff4e5)', border:'1px solid var(--warning, #ea580c)', borderRadius:'var(--r-md)', display:'flex', alignItems:'center', gap:12, cursor:'pointer'}} onClick={() => setRoute('admin-catalog')}>
          <Icon name="alert" style={{color:'var(--warning, #ea580c)'}} />
          <div style={{flex:1}}>
            <div style={{fontWeight:700}}>
              {outOfStock.length > 0 && <span>{outOfStock.length} produit{outOfStock.length>1?'s':''} en rupture</span>}
              {outOfStock.length > 0 && lowStock.length > 0 && <span> · </span>}
              {lowStock.length > 0 && <span>{lowStock.length} en stock faible</span>}
            </div>
            <div className="muted" style={{fontSize:12, marginTop:2}}>{[...outOfStock, ...lowStock].slice(0, 4).map(p => p.name).join(' · ')}{(outOfStock.length + lowStock.length) > 4 ? '…' : ''}</div>
          </div>
          <Icon name="chevron-right" style={{color:'var(--ink-3)'}} />
        </div>
      )}

      <div className="kpi-grid">
        <div className="kpi">
          <div className="kpi-icon"><Icon name="euro" /></div>
          <div className="kpi-label">Chiffre du jour</div>
          <div className="kpi-value">{formatPrice(ca).euros}<span className="unit">,{formatPrice(ca).cents} €</span></div>
          <div className="kpi-delta">+12% vs. hier</div>
        </div>
        <div className="kpi">
          <div className="kpi-icon"><Icon name="list" /></div>
          <div className="kpi-label">Commandes</div>
          <div className="kpi-value">{today.length}<span className="unit"> aujourd'hui</span></div>
          <div className="kpi-delta">{today.filter(o => o.status === 'en-cours').length} en cours</div>
        </div>
        <div className="kpi">
          <div className="kpi-icon"><Icon name="truck" /></div>
          <div className="kpi-label">À livrer</div>
          <div className="kpi-value">{today.filter(o => o.status === 'prete').length}</div>
          <div className="kpi-delta">prêtes à partir</div>
        </div>
        <div className="kpi">
          <div className="kpi-icon"><Icon name="user" /></div>
          <div className="kpi-label">Clients servis</div>
          <div className="kpi-value">{new Set(today.map(o => o.customerId)).size}</div>
          <div className="kpi-delta">clients uniques</div>
        </div>
      </div>

      <div className="card" style={{margin:'0 32px 24px', padding:'20px 24px'}}>
        <div style={{display:'flex', alignItems:'center', gap:12, flexWrap:'wrap', marginBottom:4}}>
          <h2 style={{fontSize:18, fontWeight:800, margin:0, flex:1, minWidth:160, letterSpacing:'-0.01em'}}>
            <Icon name="tag" style={{width:18, height:18, verticalAlign:'-3px', marginRight:8, color:'var(--vival-red)'}} />
            Marges gagnées sur les commandes app
          </h2>
          <div style={{display:'flex', gap:6, flexWrap:'wrap'}}>
            {marginPeriods.map(p => (
              <button key={p.id} className="chip" aria-pressed={marginPeriod === p.id} onClick={() => setMarginPeriod(p.id)} style={{height:32}}>
                {p.label}
              </button>
            ))}
          </div>
        </div>
        <div style={{fontSize:12, color:'var(--ink-3)', marginBottom:16, display:'flex', gap:6, alignItems:'center', flexWrap:'wrap'}}>
          <Icon name="alert" style={{width:12, height:12}} />
          Ces chiffres ne concernent que les commandes passées via l'app (téléphone, livraison, site).
          <button onClick={() => setRoute('reports')} style={{background:'transparent', border:0, color:'var(--vival-red)', fontWeight:700, cursor:'pointer', textDecoration:'underline', padding:0, fontSize:12}}>
            Voir aussi les ventes caisse dans Rapports →
          </button>
        </div>

        {marginPeriod === 'custom' && (
          <div style={{display:'flex', gap:12, alignItems:'center', marginBottom:16, flexWrap:'wrap'}}>
            <label style={{fontSize:13, display:'flex', alignItems:'center', gap:8}}>
              Du
              <input type="date" value={marginFrom} max={marginTo} onChange={e => setMarginFrom(e.target.value)} style={{padding:'8px 10px', border:'1px solid var(--line-1)', borderRadius:8, fontSize:14}} />
            </label>
            <label style={{fontSize:13, display:'flex', alignItems:'center', gap:8}}>
              Au
              <input type="date" value={marginTo} min={marginFrom} max={toDateInput(new Date())} onChange={e => setMarginTo(e.target.value)} style={{padding:'8px 10px', border:'1px solid var(--line-1)', borderRadius:8, fontSize:14}} />
            </label>
          </div>
        )}

        <div style={{display:'grid', gridTemplateColumns:'repeat(auto-fit, minmax(200px, 1fr))', gap:16}}>
          <div>
            <div style={{fontSize:12, color:'var(--ink-3)', textTransform:'uppercase', letterSpacing:'0.04em', fontWeight:700}}>Marge totale</div>
            <div style={{fontSize:28, fontWeight:800, color: marginData.totalMargin < 0 ? 'var(--danger, #c02424)' : 'var(--ink-1)', marginTop:4, letterSpacing:'-0.02em'}}>
              {formatPrice(marginData.totalMargin).euros}<span style={{fontSize:16, color:'var(--ink-3)'}}>,{formatPrice(marginData.totalMargin).cents} €</span>
            </div>
            <div style={{fontSize:12, color:'var(--ink-3)', marginTop:4}}>sur {formatPrice(marginData.totalHt).full} HT</div>
          </div>
          <div>
            <div style={{fontSize:12, color:'var(--ink-3)', textTransform:'uppercase', letterSpacing:'0.04em', fontWeight:700}}>Taux de marge moyen</div>
            <div style={{fontSize:28, fontWeight:800, color:'var(--ink-1)', marginTop:4, letterSpacing:'-0.02em'}}>
              {marginData.marginRate.toFixed(1).replace('.', ',')}<span style={{fontSize:16, color:'var(--ink-3)'}}> %</span>
            </div>
            <div style={{fontSize:12, color:'var(--ink-3)', marginTop:4}}>marge HT / CA HT</div>
          </div>
          <div>
            <div style={{fontSize:12, color:'var(--ink-3)', textTransform:'uppercase', letterSpacing:'0.04em', fontWeight:700}}>Marge / commande</div>
            <div style={{fontSize:28, fontWeight:800, color:'var(--ink-1)', marginTop:4, letterSpacing:'-0.02em'}}>
              {formatPrice(marginData.avgPerOrder).euros}<span style={{fontSize:16, color:'var(--ink-3)'}}>,{formatPrice(marginData.avgPerOrder).cents} €</span>
            </div>
            <div style={{fontSize:12, color:'var(--ink-3)', marginTop:4}}>{marginData.orderCount} commande{marginData.orderCount > 1 ? 's' : ''}</div>
          </div>
        </div>

        {marginData.missingCount > 0 && (
          <div onClick={() => setMissingPaModalOpen(true)} style={{marginTop:16, padding:'10px 14px', background:'var(--warning-soft, #fff4e5)', border:'1px solid var(--warning, #ea580c)', borderRadius:'var(--r-md)', display:'flex', alignItems:'center', gap:10, cursor:'pointer', fontSize:13}}>
            <Icon name="alert" style={{color:'var(--warning, #ea580c)', width:16, height:16}} />
            <div style={{flex:1}}>
              <strong>{marginData.missingCount} produit{marginData.missingCount > 1 ? 's' : ''} sans prix d'achat</strong>
              {marginData.missingInPeriod > 0 && <span className="muted"> · {marginData.missingInPeriod} vendu{marginData.missingInPeriod > 1 ? 's' : ''} sur la période — marge sous-estimée</span>}
              <span className="muted" style={{marginLeft:6, fontSize:11}}>· Clique pour éditer</span>
            </div>
            <Icon name="chevron-right" style={{color:'var(--ink-3)', width:16, height:16}} />
          </div>
        )}
      </div>

      {missingPaModalOpen && <MissingPurchasePriceModal onClose={() => setMissingPaModalOpen(false)} />}

      <TopProducts orders={orders} />

      <OrderList orders={today} title="Commandes du jour" />
    </>
  );
}

// ========== Rapports (CA + marge multi-période) ==========

// Étend getPeriodRange avec yesterday et last-month
function getReportRange(period, customFrom, customTo) {
  const now = new Date();
  if (period === 'today') {
    const s = new Date(now); s.setHours(0,0,0,0);
    const e = new Date(now); e.setHours(23,59,59,999);
    return { from: s, to: e };
  }
  if (period === 'yesterday') {
    const s = new Date(now); s.setDate(s.getDate() - 1); s.setHours(0,0,0,0);
    const e = new Date(s); e.setHours(23,59,59,999);
    return { from: s, to: e };
  }
  if (period === 'week') {
    const s = new Date(now); s.setDate(s.getDate() - 6); s.setHours(0,0,0,0);
    const e = new Date(now); e.setHours(23,59,59,999);
    return { from: s, to: e };
  }
  if (period === 'this-month') {
    const s = new Date(now.getFullYear(), now.getMonth(), 1, 0, 0, 0, 0);
    const e = new Date(now); e.setHours(23,59,59,999);
    return { from: s, to: e };
  }
  if (period === 'last-month') {
    const s = new Date(now.getFullYear(), now.getMonth() - 1, 1, 0, 0, 0, 0);
    const e = new Date(now.getFullYear(), now.getMonth(), 0, 23, 59, 59, 999);
    return { from: s, to: e };
  }
  if (period === 'custom') {
    const s = new Date(customFrom); s.setHours(0,0,0,0);
    const e = new Date(customTo); e.setHours(23,59,59,999);
    return { from: s, to: e };
  }
  const s = new Date(now); s.setHours(0,0,0,0);
  const e = new Date(now); e.setHours(23,59,59,999);
  return { from: s, to: e };
}

// Retourne la période précédente de même durée (pour la comparaison)
function previousRange(from, to) {
  const ms = to.getTime() - from.getTime();
  const prevTo = new Date(from.getTime() - 1);
  const prevFrom = new Date(prevTo.getTime() - ms);
  return { from: prevFrom, to: prevTo };
}

// Retourne la MÊME période un an plus tôt (N-1), pour comparaison annuelle
function yearAgoRange(from, to) {
  const f = new Date(from); f.setFullYear(f.getFullYear() - 1);
  const t = new Date(to); t.setFullYear(t.getFullYear() - 1);
  return { from: f, to: t };
}

function formatPeriodLabel(period, from, to) {
  const fmt = (d) => d.toLocaleDateString('fr-FR', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' });
  const fmtShort = (d) => d.toLocaleDateString('fr-FR', { day: '2-digit', month: 'short', year: 'numeric' });
  const capitalize = (s) => s.charAt(0).toUpperCase() + s.slice(1);
  if (period === 'today') return `Aujourd'hui — ${capitalize(fmt(from))}`;
  if (period === 'yesterday') return `Hier — ${capitalize(fmt(from))}`;
  if (period === 'week') return `7 derniers jours — du ${fmtShort(from)} au ${fmtShort(to)}`;
  if (period === 'this-month') return `Ce mois-ci — du ${fmtShort(from)} au ${fmtShort(to)}`;
  if (period === 'last-month') return `Mois dernier — du ${fmtShort(from)} au ${fmtShort(to)}`;
  return `Du ${fmtShort(from)} au ${fmtShort(to)}`;
}

// Récupère les données de vente (orders + sales_import_lines) sur une période
async function fetchReportData(from, to, source) {
  const isoFrom = from.toISOString();
  const isoTo = to.toISOString();
  const lines = [];

  // Source 1 : ventes app (orders + order_lines)
  if (source === 'all' || source === 'app') {
    const { data: orders } = await window.sb
      .from('orders')
      .select('id, created_at, status')
      .gte('created_at', isoFrom)
      .lte('created_at', isoTo)
      .neq('status', 'annulee');
    const orderIds = (orders || []).map(o => o.id);
    const orderById = new Map((orders || []).map(o => [o.id, o]));
    if (orderIds.length > 0) {
      // Batch par 200 pour éviter URL trop longue
      for (let i = 0; i < orderIds.length; i += 200) {
        const slice = orderIds.slice(i, i + 200);
        const { data: rows } = await window.sb
          .from('order_lines')
          .select('order_id, product_id, product_name, qty, unit_price, tva_rate')
          .in('order_id', slice);
        for (const r of rows || []) {
          const o = orderById.get(r.order_id);
          if (!o) continue;
          lines.push({
            date: o.created_at,
            order_id: r.order_id,
            ean: r.product_id,
            name: r.product_name,
            qty: Number(r.qty) || 0,
            unit_price: Number(r.unit_price) || 0,
            ca_ttc: (Number(r.unit_price) || 0) * (Number(r.qty) || 0),
            tva_rate: Number(r.tva_rate) || 0.055,
            source: 'app',
          });
        }
      }
    }
  }

  // Source 2 : imports caisse (filtre sur la VRAIE date de vente, pas la date d'upload)
  // On inclut un import uniquement si sa période [sale_date_from, sale_date_to]
  // est ENTIÈREMENT contenue dans la période demandée [from, to].
  // Ainsi, un import d'une semaine ne fausse pas un filtre "Hier".
  if (source === 'all' || source === 'caisse') {
    // Format YYYY-MM-DD en heure LOCALE (pas UTC) pour éviter les décalages de fuseau.
    // Avec .toISOString() qui est en UTC, une date locale "20 avril 00:00 Paris" devient "2026-04-19T22:00" UTC → slice donnait '2026-04-19' (faux).
    const toYmd = (d) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
    const fromDate = toYmd(from);
    const toDate = toYmd(to);
    const { data: imports } = await window.sb
      .from('sales_imports')
      .select('id, created_at, sale_date_from, sale_date_to')
      .not('sale_date_from', 'is', null)
      .not('sale_date_to', 'is', null)
      .gte('sale_date_from', fromDate)
      .lte('sale_date_to', toDate);
    const importIds = (imports || []).map(i => i.id);
    const importById = new Map((imports || []).map(i => [i.id, i]));
    if (importIds.length > 0) {
      // Batch par 200 import_ids (URL trop longue sinon)
      for (let i = 0; i < importIds.length; i += 200) {
        const slice = importIds.slice(i, i + 200);
        // Pagination interne via .range(offset, offset+PAGE-1) - PostgREST plafonne a 1000 par defaut.
        // On boucle jusqu'a recevoir une page incomplete.
        const PAGE = 1000;
        let offset = 0;
        while (true) {
          const { data: rows } = await window.sb
            .from('sales_import_lines')
            .select('import_id, ean, product_id, rayon, sub, designation, qty, ca_ttc, ca_ht, tva_rate, purchase_price, marge_ht, matched')
            .in('import_id', slice)
            .range(offset, offset + PAGE - 1);
          const batch = rows || [];
          for (const r of batch) {
            const imp = importById.get(r.import_id);
            if (!imp) continue;
            lines.push({
              date: imp.sale_date_from,
              import_id: r.import_id,
              ean: r.ean,
              name: r.designation,
              rayon: r.rayon,
              sub: r.sub,
              qty: Number(r.qty) || 0,
              unit_price: Number(r.ca_ttc) / (Number(r.qty) || 1),
              ca_ttc: Number(r.ca_ttc) || 0,
              ca_ht_known: Number(r.ca_ht) || null,
              tva_rate: Number(r.tva_rate) || 0.055,
              purchase_price: Number(r.purchase_price) || 0,
              marge_ht_known: Number(r.marge_ht) || null,
              matched: !!r.matched,
              source: 'caisse',
            });
          }
          if (batch.length < PAGE) break;
          offset += PAGE;
        }
      }
    }
  }

  return lines;
}

// Enrichit avec rayon/sub et purchase_price depuis le catalogue local pour les lignes app
function enrichReportLines(lines) {
  const byId = new Map(window.VivalData.PRODUCTS.map(p => [p.id, p]));
  const byEan = new Map(window.VivalData.PRODUCTS.filter(p => p.barcode).map(p => [p.barcode, p]));
  return lines.map(l => {
    if (l.source === 'caisse') {
      if (!l.rayon && l.ean) {
        const p = byEan.get(l.ean);
        if (p) {
          l.rayon = p.rayon;
          l.sub = p.sub || 'Tout';
          l.sub_family = p.subFamily || null;
          l.purchase_price = p.purchasePrice || 0;
        }
      }
      if (!l.sub_family && l.ean) {
        const p = byEan.get(l.ean);
        if (p?.subFamily) l.sub_family = p.subFamily;
      }
      l.ca_ht = l.ca_ht_known != null ? l.ca_ht_known : l.ca_ttc / (1 + l.tva_rate);
      l.has_purchase = l.purchase_price > 0;
      l.marge_ht = l.has_purchase ? l.ca_ht - (l.purchase_price * l.qty) : 0;
      return l;
    }
    const p = byId.get(l.ean);
    if (p) {
      l.rayon = p.rayon;
      l.sub = p.sub || 'Tout';
      l.sub_family = p.subFamily || null;
      l.purchase_price = p.purchasePrice || 0;
    }
    l.ca_ht = l.ca_ttc / (1 + l.tva_rate);
    l.has_purchase = (l.purchase_price || 0) > 0;
    l.marge_ht = l.has_purchase ? l.ca_ht - (l.purchase_price * l.qty) : 0;
    return l;
  });
}

function computeKpi(lines) {
  const byOrder = new Set();
  let ca_ttc = 0, ca_ht = 0, marge_ht = 0, ca_ht_with_purchase = 0;
  // Compteurs séparés :
  // - nb_tickets_app : commandes uniques cré\u00e9es dans l'app (chaque order_id = 1 ticket)
  // - nb_imports_caisse : nombre d'imports PDF sur la période (pas de détail ticket dans le PDF)
  // Le PDF caisse agrège les ventes d'une journée : impossible de connaître le nb de tickets réels
  const ordersApp = new Set();
  const importsCaisse = new Set();
  for (const l of lines) {
    ca_ttc += l.ca_ttc;
    ca_ht += l.ca_ht;
    if (l.has_purchase) {
      marge_ht += l.marge_ht;
      ca_ht_with_purchase += l.ca_ht;
    }
    if (l.source === 'app' && l.order_id) ordersApp.add(l.order_id);
    if (l.source === 'caisse' && l.import_id) importsCaisse.add(l.import_id);
  }
  return {
    ca_ttc, ca_ht, marge_ht,
    marge_rate: ca_ht_with_purchase > 0 ? (marge_ht / ca_ht_with_purchase) * 100 : 0,
    nb_tickets_app: ordersApp.size,
    nb_imports_caisse: importsCaisse.size,
    nb_tickets: ordersApp.size,  // fallback compat
  };
}

function aggregateReportByRayon(lines) {
  const map = new Map();
  for (const l of lines) {
    const key = l.rayon || 'non-classe';
    if (!map.has(key)) map.set(key, { rayon: key, ca_ttc: 0, ca_ht: 0, marge_ht: 0, nb_lignes: 0, nb_plu_internal: 0, subs: new Map() });
    const a = map.get(key);
    a.ca_ttc += l.ca_ttc; a.ca_ht += l.ca_ht; a.nb_lignes++;
    if (l.is_plu_internal) a.nb_plu_internal++;
    if (l.has_purchase) a.marge_ht += l.marge_ht;
    const sk = l.sub || 'Tout';
    if (!a.subs.has(sk)) a.subs.set(sk, { sub: sk, ca_ttc: 0, ca_ht: 0, marge_ht: 0, nb_lignes: 0, subFamilies: new Map() });
    const s = a.subs.get(sk);
    s.ca_ttc += l.ca_ttc; s.ca_ht += l.ca_ht; s.nb_lignes++;
    if (l.has_purchase) s.marge_ht += l.marge_ht;
    // Niveau 3 : sous-famille
    const sfk = l.sub_family || 'Non classé';
    if (!s.subFamilies.has(sfk)) s.subFamilies.set(sfk, { sub_family: sfk, ca_ttc: 0, ca_ht: 0, marge_ht: 0, nb_lignes: 0 });
    const sf = s.subFamilies.get(sfk);
    sf.ca_ttc += l.ca_ttc; sf.ca_ht += l.ca_ht; sf.nb_lignes++;
    if (l.has_purchase) sf.marge_ht += l.marge_ht;
  }
  return [...map.values()].sort((a, b) => b.ca_ttc - a.ca_ttc);
}

function aggregateTopProducts(lines, limit = 20) {
  const map = new Map();
  for (const l of lines) {
    const key = l.ean || l.name;
    if (!map.has(key)) map.set(key, { ean: l.ean, name: l.name, qty: 0, ca_ttc: 0, marge_ht: 0 });
    const a = map.get(key);
    a.qty += l.qty;
    a.ca_ttc += l.ca_ttc;
    if (l.has_purchase) a.marge_ht += l.marge_ht;
  }
  return [...map.values()].sort((a, b) => b.ca_ttc - a.ca_ttc).slice(0, limit);
}

function deltaPct(current, previous) {
  if (!previous || previous === 0) return current > 0 ? null : 0; // nouveau vs rien → pas de %
  return ((current - previous) / previous) * 100;
}

function DeltaBadge({ value }) {
  if (value == null) return null;
  const pos = value >= 0;
  const color = pos ? '#15803d' : '#c02424';
  const bg = pos ? '#dcfce7' : '#fee2e2';
  const sign = pos ? '+' : '';
  return (
    <span style={{display:'inline-block', padding:'2px 8px', borderRadius:10, fontSize:11, fontWeight:700, background:bg, color, marginLeft:6}}>
      {sign}{value.toFixed(1).replace('.', ',')} %
    </span>
  );
}

// ========== Modal Import inventaire PDF ==========
function InventoryImportModal({ onClose, onDone }) {
  const app = useApp();
  const [status, setStatus] = useState('idle'); // idle | parsing | ready | enriching | importing | done | error
  const [error, setError] = useState(null);
  const [filename, setFilename] = useState('');
  const [inventoryDate, setInventoryDate] = useState(toDateInput(new Date()));
  const [rows, setRows] = useState([]); // [{ean, name, family_code, family_label, price, purchase_price, tva_rate, existing_product, off, action}]
  const [familyMapping, setFamilyMapping] = useState(new Map()); // code -> {rayon_id, is_confirmed}
  const [newFamilies, setNewFamilies] = useState([]); // familles non encore mappées
  const [enrichProgress, setEnrichProgress] = useState({ done: 0, total: 0 });
  const [saveProgress, setSaveProgress] = useState({ done: 0, total: 0, created: 0, updated: 0, skipped: 0 });
  const [recalcProgress, setRecalcProgress] = useState({ done: 0, total: 0 });
  const [cleanupStatus, setCleanupStatus] = useState('idle'); // idle | running | done
  const [cleanupResult, setCleanupResult] = useState(null);

  const handleFile = async (f) => {
    if (!f) return;
    setFilename(f.name);
    setStatus('parsing');
    setError(null);
    try {
      const { parsed, linesStrs } = await parseInventoryPdfWithReflow(f);
      // Extrait la date d'inventaire du header
      for (const l of linesStrs.slice(0, 20)) {
        const m = l.match(/(\d{1,2}\/\d{1,2}\/\d{2,4})/);
        if (m) { const d = parseFrDate(m[1]); if (d) { setInventoryDate(d); break; } }
      }
      const familiesSet = new Map();
      for (const r of parsed) {
        if (r.family_code && !familiesSet.has(r.family_code)) {
          familiesSet.set(r.family_code, r.family_label);
        }
      }
      if (parsed.length === 0) {
        // Détecte si c'est un PDF d'export caisse (PLU) uploadé par erreur
        const header = linesStrs.slice(0, 25).join(' ');
        if (/\(PLU\)|R[eé]sultats de vente|Ventes par article/i.test(header)) {
          throw new Error(
            "❌ Ce PDF est un export caisse journalier (ventes), pas un inventaire.\n\n" +
            "➡️ Ferme cette fenêtre et utilise le bouton « Importer export caisse » à gauche.\n\n" +
            "Le bouton « Importer inventaire » attend le PDF d'inventaire magasin (nom commençant par psinv21p_…_PROFRE_…)."
          );
        }
        throw new Error(
          "❌ Aucune ligne d'inventaire reconnue dans ce PDF.\n\n" +
          "Vérifie qu'il s'agit bien du PDF d'inventaire magasin Casino (PROFRE), avec les colonnes :\n" +
          "Code article · Libellé · Famille · Qté · TVA · Prix vente · Prix d'achat · Stock."
        );
      }

      // Charge le mapping existant
      const mapping = await loadCasinoMapping();
      // Upsert les nouvelles familles avec rayon deviné
      const created = await upsertCasinoFamilies(familiesSet, mapping);
      // Recharge la table après upsert
      const freshMapping = await loadCasinoMapping();
      setFamilyMapping(freshMapping);
      setNewFamilies(created);

      // Indexe les produits existants par EAN (base locale)
      const byEan = new Map();
      const byPrefix12 = new Map();
      for (const p of window.VivalData.PRODUCTS) {
        if (!p.barcode) continue;
        byEan.set(p.barcode, p);
        if (p.barcode.length === 13) byPrefix12.set(p.barcode.slice(0, 12), p);
      }

      const enriched = parsed.map(r => {
        let existing = byEan.get(r.ean);
        if (!existing && r.ean.length === 12) existing = byPrefix12.get(r.ean);
        if (!existing && r.ean.length === 12) {
          const cs = ean13Checksum(r.ean);
          if (cs !== null) existing = byEan.get(r.ean + cs);
        }
        const mapRow = r.family_code ? freshMapping.get(r.family_code) : null;
        return {
          ...r,
          existing_product: existing || null,
          rayon: mapRow?.rayon_id || 'epicerie-salee',
          sub: r.family_label ? (r.family_label.charAt(0) + r.family_label.slice(1).toLowerCase()) : 'Tout',
          action: existing ? 'update' : 'create',
          off: null,
        };
      });
      setRows(enriched);
      setStatus('ready');

      // Auto-enrichissement OFF pour les produits à créer
      const toEnrich = enriched.filter(r => r.action === 'create');
      if (toEnrich.length > 0) runOffEnrichment(toEnrich, enriched);
    } catch (e) {
      console.error('parseInventory', e);
      setError(e.message);
      setStatus('error');
    }
  };

  const runOffEnrichment = async (toEnrich, allRows) => {
    setEnrichProgress({ done: 0, total: toEnrich.length });
    const BATCH = 20;
    const offByEan = new Map();
    for (let i = 0; i < toEnrich.length; i += BATCH) {
      const slice = toEnrich.slice(i, i + BATCH);
      try {
        const res = await window.sb.functions.invoke('ean-lookup', {
          body: { eans: slice.map(r => r.ean) },
        });
        const results = res?.data?.results || {};
        for (const r of slice) {
          const entry = results[r.ean];
          if (entry?.found) offByEan.set(r.ean, entry);
        }
      } catch (e) { console.error('ean-lookup batch', e); }
      setEnrichProgress({ done: Math.min(i + BATCH, toEnrich.length), total: toEnrich.length });
    }
    // Merge OFF dans les rows
    setRows(allRows.map(r => r.action === 'create' && offByEan.has(r.ean) ? { ...r, off: offByEan.get(r.ean) } : r));
  };

  const runImport = async () => {
    if (!inventoryDate) { alert('Vérifie la date de l\'inventaire.'); return; }
    setStatus('importing');
    setSaveProgress({ done: 0, total: rows.length, created: 0, updated: 0, skipped: 0 });
    let created = 0, updated = 0, skipped = 0;
    // 1) Update des existants + Insert des nouveaux, par batch séparés
    const BATCH = 100;
    const updateRows = rows.filter(r => r.action === 'update' && r.existing_product);
    const createRows = rows.filter(r => r.action === 'create');

    // --- Updates en parallèle par batch (20 simultanés pour ne pas saturer)
    const PARALLEL = 20;
    for (let i = 0; i < updateRows.length; i += PARALLEL) {
      const slice = updateRows.slice(i, i + PARALLEL);
      const tasks = slice.map(async (r) => {
        const mapRow = r.family_code ? familyMapping.get(r.family_code) : null;
        const rayon = mapRow?.rayon_id || 'epicerie-salee';
        const sub = r.family_label || 'Tout';
        try {
          const { error } = await window.sb.from('products').update({
            name: r.name,
            price: r.price,
            purchase_price: r.purchase_price,
            tva_rate: r.tva_rate,
            rayon, sub,
            casino_family_code: r.family_code || null,
            casino_family_label: r.family_label || null,
            source_of_truth: 'inventory',
            is_active: true,
          }).eq('id', r.existing_product.id);
          if (error) throw error;
          return 'ok';
        } catch (e) {
          console.error('update product', e);
          return 'ko';
        }
      });
      const results = await Promise.all(tasks);
      updated += results.filter(r => r === 'ok').length;
      skipped += results.filter(r => r === 'ko').length;
      setSaveProgress({ done: Math.min(i + PARALLEL, updateRows.length), total: rows.length, created, updated, skipped });
    }

    // --- Inserts (par batch)
    const storeId = window.VivalData?._storeId || null;
    for (let i = 0; i < createRows.length; i += BATCH) {
      const slice = createRows.slice(i, i + BATCH);
      const payload = slice.map(r => {
        const mapRow = r.family_code ? familyMapping.get(r.family_code) : null;
        const rayon = mapRow?.rayon_id || 'epicerie-salee';
        const sub = r.family_label || 'Tout';
        return {
          id: 'inventory-' + r.ean,
          ean: r.ean,
          name: (r.off?.name || r.name).slice(0, 200),
          brand: (r.off?.brand || '').slice(0, 100) || null,
          format: (r.off?.format || '').slice(0, 50) || null,
          image: r.off?.image || null,
          price: r.price,
          purchase_price: r.purchase_price,
          tva_rate: r.tva_rate,
          rayon, sub,
          casino_family_code: r.family_code || null,
          casino_family_label: r.family_label || null,
          source_of_truth: 'inventory',
          is_active: true,
          stock: 0,
          by_weight: false,
          source: 'inventory',
          store_id: storeId,
        };
      });
      try {
        const { error } = await window.sb.from('products').insert(payload);
        if (error) throw error;
        created += slice.length;
      } catch (e) {
        console.error('insert products batch', e);
        skipped += slice.length;
      }
      setSaveProgress({ done: updateRows.length + Math.min(i + BATCH, createRows.length), total: rows.length, created, updated, skipped });
    }

    // 2) Enregistre le log inventory_imports
    const unknownFamilies = [...familyMapping.values()].filter(f => !f.rayon_id || !f.is_confirmed).map(f => ({ code: f.code, label: f.label, rayon_id: f.rayon_id }));
    const cashier = (() => { try { return JSON.parse(sessionStorage.getItem('vival_cashier') || 'null'); } catch { return null; } })();
    try {
      await window.sb.from('inventory_imports').insert({
        uploaded_by: cashier?.name || cashier?.email || 'admin',
        filename,
        inventory_date: inventoryDate,
        nb_lines_total: rows.length,
        nb_lines_updated: updated,
        nb_lines_created: created,
        nb_lines_skipped: skipped,
        unknown_families: unknownFamilies.length > 0 ? unknownFamilies : null,
      });
    } catch (e) { console.error('log inventory_imports', e); }

    // 3) Recalcul rétroactif des sales_import_lines
    await recalcPastImports();

    // 4) Refresh produits en mémoire (recharge complète la prochaine fois)
    // La page Rapports se rafraichira via reloadKey. Le catalogue sera à jour au prochain F5.
    setStatus('done');
    app.showToast && app.showToast(`Inventaire importé : ${created} créés, ${updated} mis à jour`);
    onDone?.();
  };

  const recalcPastImports = async () => {
    // Relit tous les sales_import_lines (pagine pour contourner la limite 1000 PostgREST)
    const lines = [];
    const PAGE = 1000;
    let off = 0;
    while (true) {
      const { data: batch } = await window.sb
        .from('sales_import_lines')
        .select('id, ean, qty, ca_ttc, tva_rate, product_id')
        .range(off, off + PAGE - 1);
      const arr = batch || [];
      lines.push(...arr);
      if (arr.length < PAGE) break;
      off += PAGE;
    }
    if (!lines.length) return;
    setRecalcProgress({ done: 0, total: lines.length });
    // Indexe par EAN les produits fraichement mis à jour
    const paByEan = new Map();
    const { data: prods } = await window.sb
      .from('products')
      .select('ean, id, purchase_price, tva_rate, rayon, sub')
      .not('purchase_price', 'is', null);
    for (const p of prods || []) if (p.ean) paByEan.set(p.ean, p);

    const updates = [];
    for (const l of lines) {
      const p = l.ean ? paByEan.get(l.ean) : null;
      if (!p || !p.purchase_price) continue;
      const tva = Number(l.tva_rate) || Number(p.tva_rate) || 0.055;
      const caHt = Number(l.ca_ttc) / (1 + tva);
      const qty = Number(l.qty) || 0;
      const paTotal = Number(p.purchase_price) * qty;
      const margeHt = caHt - paTotal;
      const margeRate = caHt > 0 ? (margeHt / caHt) * 100 : 0;
      updates.push({
        id: l.id,
        product_id: p.id,
        rayon: p.rayon,
        sub: p.sub,
        ca_ht: caHt,
        tva_rate: tva,
        purchase_price: p.purchase_price,
        marge_ht: margeHt,
        marge_rate: margeRate,
        matched: true,
      });
    }
    // Batch update 200 par 200 via upsert
    for (let i = 0; i < updates.length; i += 200) {
      const slice = updates.slice(i, i + 200);
      try { await window.sb.from('sales_import_lines').upsert(slice, { onConflict: 'id' }); }
      catch (e) { console.error('recalc batch', e); }
      setRecalcProgress({ done: Math.min(i + 200, updates.length), total: updates.length });
    }
    // Recalcule les totaux sales_imports impactés
    const { data: imports } = await window.sb.from('sales_imports').select('id');
    for (const imp of imports || []) {
      const { data: lns } = await window.sb
        .from('sales_import_lines')
        .select('ca_ttc, ca_ht, marge_ht')
        .eq('import_id', imp.id);
      const tot = (lns || []).reduce((a, l) => ({
        ttc: a.ttc + (Number(l.ca_ttc) || 0),
        ht: a.ht + (Number(l.ca_ht) || 0),
        marge: a.marge + (Number(l.marge_ht) || 0),
      }), { ttc: 0, ht: 0, marge: 0 });
      await window.sb.from('sales_imports').update({
        total_ca_ttc: tot.ttc,
        total_ca_ht: tot.ht,
        total_marge_ht: tot.marge,
      }).eq('id', imp.id);
    }
  };

  const cleanupOrphans = async () => {
    if (!confirm("Supprimer tous les produits qui ne sont PAS dans cet inventaire ?\n\nLes images et données scrappées de l'autre magasin seront perdues.\nCette action est irréversible.")) return;
    setCleanupStatus('running');
    try {
      // Compte d'abord
      const { count: before } = await window.sb.from('products').select('*', { count: 'exact', head: true });
      // Supprime les produits non touchés par cet inventaire
      const { error, count } = await window.sb.from('products')
        .delete({ count: 'exact' })
        .or('source_of_truth.is.null,source_of_truth.neq.inventory');
      if (error) throw error;
      const { count: after } = await window.sb.from('products').select('*', { count: 'exact', head: true });
      setCleanupResult({ before, after, deleted: count ?? ((before || 0) - (after || 0)) });
      setCleanupStatus('done');
      app.showToast && app.showToast(`${count ?? 0} produits supprimés`);
    } catch (e) {
      console.error('cleanupOrphans', e);
      alert('Erreur : ' + (e.message || e));
      setCleanupStatus('idle');
    }
  };

  const toCreate = rows.filter(r => r.action === 'create');
  const toUpdate = rows.filter(r => r.action === 'update');
  const withOff = toCreate.filter(r => r.off).length;

  return (
    <Modal open={true} onClose={onClose} size="xl" title="Importer l'inventaire magasin"
      footer={<>
        {status === 'ready' && (
          <Button variant="primary" icon="check-circle" onClick={runImport} disabled={!inventoryDate}>
            Importer ({rows.length} produits)
          </Button>
        )}
        {status === 'done' && cleanupStatus === 'idle' && (
          <Button variant="primary" icon="trash" onClick={cleanupOrphans}>
            Supprimer les produits hors inventaire
          </Button>
        )}
        {status === 'done' && cleanupStatus === 'running' && (
          <span style={{fontSize:13, fontWeight:700}}>Suppression…</span>
        )}
        {cleanupStatus === 'done' && (
          <span style={{fontSize:13, color:'#166534', fontWeight:700, display:'flex', alignItems:'center', gap:6}}>
            <Icon name="check-circle" style={{width:16, height:16}} /> {cleanupResult?.deleted ?? 0} produits supprimés · reste {cleanupResult?.after ?? '?'} produits
          </span>
        )}
        <Button variant="ghost" onClick={onClose}>Fermer</Button>
      </>}
    >
      {status === 'idle' && (
        <div style={{padding:20, textAlign:'center'}}>
          <div style={{marginBottom:16, fontSize:14, color:'var(--ink-2)'}}>
            Upload le PDF d'inventaire exporté par la caisse (fichier <code>psinv21p_..._PROFRE_...pdf</code>).
            <br/>
            Les <strong>prix de vente, prix d'achat, TVA et familles Casino</strong> seront synchronisés dans le catalogue.
            <br/>
            Les produits inconnus seront créés et enrichis (image, marque) via OpenFoodFacts.
            <br/>
            Les <strong>rapports de marge passés seront recalculés</strong> avec les nouveaux prix d'achat.
          </div>
          <label style={{display:'inline-block', padding:'12px 20px', background:'var(--vival-red)', color:'white', borderRadius:8, fontWeight:700, cursor:'pointer'}}>
            <input type="file" accept=".pdf,application/pdf" style={{display:'none'}} onChange={e => handleFile(e.target.files?.[0])} />
            Choisir le PDF d'inventaire
          </label>
        </div>
      )}

      {status === 'parsing' && <div style={{padding:40, textAlign:'center'}}>Parsing du PDF…</div>}
      {status === 'error' && <div style={{padding:20, color:'#b91c1c'}}>Erreur : {error}</div>}

      {(status === 'ready' || status === 'importing' || status === 'done') && (
        <div style={{padding:16}}>
          <div style={{display:'flex', gap:16, marginBottom:16, alignItems:'center', flexWrap:'wrap'}}>
            <label style={{fontSize:13, fontWeight:600}}>
              Date de l'inventaire :
              <input type="date" value={inventoryDate} onChange={e => setInventoryDate(e.target.value)}
                style={{marginLeft:8, padding:'6px 10px', border:'1px solid var(--line-1)', borderRadius:6, fontSize:13}} />
            </label>
            <span className="muted" style={{fontSize:13}}>{filename}</span>
          </div>

          <div style={{display:'grid', gridTemplateColumns:'repeat(4, 1fr)', gap:12, marginBottom:16}}>
            <div style={{background:'var(--surface-1)', borderRadius:8, padding:12}}>
              <div className="muted" style={{fontSize:12}}>Total</div>
              <div style={{fontSize:22, fontWeight:800}}>{rows.length}</div>
            </div>
            <div style={{background:'#ecfdf5', borderRadius:8, padding:12}}>
              <div className="muted" style={{fontSize:12}}>À créer</div>
              <div style={{fontSize:22, fontWeight:800, color:'#166534'}}>{toCreate.length}</div>
              <div className="muted" style={{fontSize:11}}>{withOff}/{toCreate.length} enrichis OFF</div>
            </div>
            <div style={{background:'#eff6ff', borderRadius:8, padding:12}}>
              <div className="muted" style={{fontSize:12}}>À mettre à jour</div>
              <div style={{fontSize:22, fontWeight:800, color:'#1e40af'}}>{toUpdate.length}</div>
            </div>
            <div style={{background:'#fef3c7', borderRadius:8, padding:12}}>
              <div className="muted" style={{fontSize:12}}>Familles Casino</div>
              <div style={{fontSize:22, fontWeight:800, color:'#92400e'}}>{familyMapping.size}</div>
              <div className="muted" style={{fontSize:11}}>{newFamilies.length} nouvelles</div>
            </div>
          </div>

          {enrichProgress.total > 0 && enrichProgress.done < enrichProgress.total && (
            <div style={{marginBottom:12, fontSize:13}}>
              Enrichissement OFF : {enrichProgress.done}/{enrichProgress.total}
              <div style={{height:6, background:'var(--line-1)', borderRadius:3, marginTop:4, overflow:'hidden'}}>
                <div style={{height:'100%', width:`${(enrichProgress.done / enrichProgress.total) * 100}%`, background:'var(--vival-red)'}} />
              </div>
            </div>
          )}

          {status === 'importing' && (
            <div style={{marginBottom:12, fontSize:13}}>
              Enregistrement : {saveProgress.done}/{saveProgress.total} ({saveProgress.created} créés, {saveProgress.updated} MAJ)
              <div style={{height:6, background:'var(--line-1)', borderRadius:3, marginTop:4, overflow:'hidden'}}>
                <div style={{height:'100%', width:`${saveProgress.total>0?(saveProgress.done / saveProgress.total) * 100:0}%`, background:'var(--vival-red)'}} />
              </div>
              {recalcProgress.total > 0 && (
                <div style={{marginTop:8, fontSize:12, color:'var(--ink-3)'}}>
                  Recalcul marges passées : {recalcProgress.done}/{recalcProgress.total}
                </div>
              )}
            </div>
          )}

          {newFamilies.length > 0 && (
            <details style={{marginBottom:16}}>
              <summary style={{cursor:'pointer', fontSize:13, fontWeight:700, padding:'8px 0'}}>
                {newFamilies.length} nouvelles familles Casino détectées (rayon deviné auto)
              </summary>
              <div style={{maxHeight:160, overflowY:'auto', fontSize:12, background:'var(--surface-1)', borderRadius:6, padding:8}}>
                {newFamilies.map(f => (
                  <div key={f.code} style={{display:'flex', justifyContent:'space-between', padding:'3px 0'}}>
                    <span><strong>{f.code}</strong> — {f.label}</span>
                    <span style={{color: f.rayon_id ? 'var(--ink-2)' : '#b91c1c'}}>→ {f.rayon_id || '⚠ à affecter'}</span>
                  </div>
                ))}
              </div>
            </details>
          )}

          <div style={{maxHeight:360, overflowY:'auto', border:'1px solid var(--line-1)', borderRadius:8}}>
            <table style={{width:'100%', borderCollapse:'collapse', fontSize:12}}>
              <thead style={{position:'sticky', top:0, background:'var(--surface-1)'}}>
                <tr>
                  <th style={{padding:'8px', textAlign:'left'}}>EAN</th>
                  <th style={{padding:'8px', textAlign:'left'}}>Désignation</th>
                  <th style={{padding:'8px', textAlign:'left'}}>Famille</th>
                  <th style={{padding:'8px', textAlign:'right'}}>PV TTC</th>
                  <th style={{padding:'8px', textAlign:'right'}}>PA HT</th>
                  <th style={{padding:'8px', textAlign:'center'}}>Action</th>
                </tr>
              </thead>
              <tbody>
                {rows.slice(0, 200).map((r, i) => (
                  <tr key={i} style={{borderTop:'1px solid var(--line-1)'}}>
                    <td style={{padding:'6px 8px', fontFamily:'monospace'}}>{r.ean}</td>
                    <td style={{padding:'6px 8px'}}>{r.name}</td>
                    <td style={{padding:'6px 8px', fontSize:11, color:'var(--ink-3)'}}>{r.family_label || '—'}</td>
                    <td style={{padding:'6px 8px', textAlign:'right'}}>{formatPrice(r.price).full}</td>
                    <td style={{padding:'6px 8px', textAlign:'right'}}>{formatPrice(r.purchase_price).full}</td>
                    <td style={{padding:'6px 8px', textAlign:'center'}}>
                      {r.action === 'create'
                        ? <span style={{padding:'2px 6px', borderRadius:4, background:'#d1fae5', color:'#065f46', fontSize:11, fontWeight:700}}>CRÉER{r.off ? ' +' : ''}</span>
                        : <span style={{padding:'2px 6px', borderRadius:4, background:'#dbeafe', color:'#1e40af', fontSize:11, fontWeight:700}}>MAJ</span>}
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
            {rows.length > 200 && (
              <div style={{padding:8, textAlign:'center', fontSize:12, color:'var(--ink-3)', background:'var(--surface-1)'}}>
                … {rows.length - 200} lignes supplémentaires non affichées (toutes seront importées)
              </div>
            )}
          </div>
        </div>
      )}
    </Modal>
  );
}

// ========== Éditeur des familles Casino ==========
function CasinoFamiliesEditor() {
  const app = useApp();
  const [families, setFamilies] = useState(null);
  const [edits, setEdits] = useState({}); // { code: { label?, rayon_id? } }
  const [saving, setSaving] = useState({});
  const [filter, setFilter] = useState(''); // '' | 'pending'
  const [search, setSearch] = useState('');
  const [visibleCount, setVisibleCount] = useState(30);
  useEffect(() => { setVisibleCount(30); }, [filter, search]);
  const [guessing, setGuessing] = useState(false);

  const load = async () => {
    const { data } = await window.sb
      .from('casino_family_mapping')
      .select('*')
      .order('code');
    // Ajoute le compte de produits par famille
    let counts = null;
    try { const r = await window.sb.rpc('count_products_by_casino_family'); counts = r.data; }
    catch (e) { console.warn('count_products_by_casino_family failed', e); }
    const byCode = new Map();
    for (const c of counts || []) byCode.set(c.code, c.nb);
    // Compte les sous-familles par famille
    const { data: subs } = await window.sb.from('casino_subfamilies').select('family_code');
    const subCountByCode = new Map();
    for (const s of subs || []) subCountByCode.set(s.family_code, (subCountByCode.get(s.family_code) || 0) + 1);
    setFamilies((data || []).map(f => ({
      ...f,
      nb_products: byCode.get(f.code) || 0,
      nb_subfamilies: subCountByCode.get(f.code) || 0,
    })));
  };
  useEffect(() => { load(); }, []);

  const setEdit = (code, patch) => setEdits(e => ({ ...e, [code]: { ...e[code], ...patch } }));

  // Détecte un libellé tronqué : finit par un mot orphelin
  const TRUNC_ENDINGS = ['EN','DE','ET','AU','LA','DU','AUX','POUR','DES','LE','LES','SANS','AVEC','SUR','PAR',':'];
  const isTruncated = (label) => {
    if (!label) return false;
    const last = label.trim().split(/\s+/).pop() || '';
    return TRUNC_ENDINGS.includes(last.toUpperCase().replace(/[^A-Z:]/g, ''));
  };

  const guessMissing = async () => {
    setGuessing(true);
    try {
      const { data, error } = await window.sb.rpc('suggest_casino_family_labels', { only_unconfirmed: true });
      if (error) throw error;
      // On ne remplit QUE les libellés tronqués
      const newEdits = { ...edits };
      let touched = 0;
      for (const row of data || []) {
        if (!isTruncated(row.current_label)) continue;
        if (!row.suggested_label || row.suggested_label === row.current_label) continue;
        if (row.nb_products === 0) continue;
        newEdits[row.code] = { ...newEdits[row.code], label: row.suggested_label };
        touched++;
      }
      setEdits(newEdits);
      app.showToast && app.showToast(
        touched > 0
          ? `${touched} libellés tronqués ont une proposition — vérifie et enregistre`
          : 'Aucun libellé tronqué trouvé (les labels semblent complets).'
      );
    } catch (e) {
      alert('Erreur : ' + (e.message || e));
    } finally {
      setGuessing(false);
    }
  };

  // Valide en bloc toutes les familles dont le libellé est complet ET le rayon défini
  const [bulkValidating, setBulkValidating] = useState(false);
  const [subfamilyOpenFor, setSubfamilyOpenFor] = useState(null); // family object
  const bulkValidateComplete = async () => {
    const toValidate = (families || []).filter(f =>
      !f.is_confirmed && f.label && !isTruncated(f.label) && f.rayon_id
    );
    if (toValidate.length === 0) {
      app.showToast && app.showToast('Aucune famille éligible à la validation auto.');
      return;
    }
    if (!confirm(`Valider automatiquement ${toValidate.length} familles dont le libellé est complet ET le rayon défini ?`)) return;
    setBulkValidating(true);
    try {
      const codes = toValidate.map(f => f.code);
      const { error } = await window.sb.from('casino_family_mapping')
        .update({ is_confirmed: true, updated_at: new Date().toISOString() })
        .in('code', codes);
      if (error) throw error;
      app.showToast && app.showToast(`${toValidate.length} familles validées`);
      await load();
    } catch (e) {
      alert('Erreur : ' + (e.message || e));
    } finally {
      setBulkValidating(false);
    }
  };

  const saveOne = async (fam) => {
    const patch = edits[fam.code] || {};
    const newLabel = (patch.label ?? fam.label) || '';
    const newRayon = (patch.rayon_id ?? fam.rayon_id) || null;
    setSaving(s => ({ ...s, [fam.code]: true }));
    try {
      const { error } = await window.sb.from('casino_family_mapping')
        .update({ label: newLabel, rayon_id: newRayon, is_confirmed: true, updated_at: new Date().toISOString() })
        .eq('code', fam.code);
      if (error) throw error;
      // Propage aux produits : tous les produits de cette famille reçoivent le nouveau rayon + le nouveau label en sub
      const { error: e2 } = await window.sb.from('products')
        .update({ rayon: newRayon, sub: newLabel, casino_family_label: newLabel })
        .eq('casino_family_code', fam.code);
      if (e2) throw e2;
      // Recalcule les sales_import_lines liées
      const { data: prods } = await window.sb.from('products').select('id').eq('casino_family_code', fam.code);
      const ids = (prods || []).map(p => p.id);
      if (ids.length > 0) {
        await window.sb.from('sales_import_lines')
          .update({ rayon: newRayon, sub: newLabel })
          .in('product_id', ids);
      }
      app.showToast && app.showToast(`Famille ${fam.code} mise à jour`);
      setEdits(e => { const c = { ...e }; delete c[fam.code]; return c; });
      await load();
    } catch (e) {
      alert('Erreur : ' + (e.message || e));
    } finally {
      setSaving(s => { const c = { ...s }; delete c[fam.code]; return c; });
    }
  };

  if (families === null) return <div className="muted" style={{padding:'40px 32px', textAlign:'center'}}>Chargement…</div>;
  if (families.length === 0) return (
    <div className="empty-state" style={{margin:'40px 32px'}}>
      <Icon name="list" />
      <div>Aucune famille Casino enregistrée</div>
      <div className="muted" style={{fontSize:13, marginTop:8}}>
        Importe un inventaire pour peupler cette liste.
      </div>
    </div>
  );

  const rayonsOpts = window.VivalData.RAYONS.map(r => ({ id: r.id, name: r.name }));

  const filtered = families.filter(f => {
    if (filter === 'pending' && f.is_confirmed) return false;
    if (search) {
      const s = search.toLowerCase();
      if (!f.code.includes(s) && !(f.label || '').toLowerCase().includes(s)) return false;
    }
    return true;
  });

  const pendingCount = families.filter(f => !f.is_confirmed).length;

  return (
    <div style={{padding:'0 32px 32px'}}>
      <div style={{display:'flex', gap:12, marginBottom:16, alignItems:'center', flexWrap:'wrap'}}>
        <input
          type="search" placeholder="Rechercher par code ou libellé"
          value={search} onChange={e => setSearch(e.target.value)}
          style={{padding:'8px 12px', border:'1px solid var(--line-1)', borderRadius:8, fontSize:13, minWidth:260}}
        />
        <button className="chip" aria-pressed={filter === ''} onClick={() => setFilter('')}>
          Toutes ({families.length})
        </button>
        <button className="chip" aria-pressed={filter === 'pending'} onClick={() => setFilter('pending')}>
          À valider ({pendingCount})
        </button>
        <Button variant="outline" icon="search" onClick={guessMissing} disabled={guessing}>
          {guessing ? 'Analyse…' : 'Deviner les libellés tronqués'}
        </Button>
        <Button variant="outline" icon="check" onClick={bulkValidateComplete} disabled={bulkValidating}>
          {bulkValidating ? 'Validation…' : 'Valider auto les libellés complets'}
        </Button>
        <div className="muted" style={{fontSize:13, marginLeft:'auto'}}>
          Modifier met à jour <strong>tous les produits</strong> + les rapports de marge.
        </div>
      </div>

      <table style={{width:'100%', borderCollapse:'collapse', fontSize:13, background:'#fff', borderRadius:10, overflow:'hidden'}}>
        <thead>
          <tr style={{background:'var(--surface-1)', textAlign:'left'}}>
            <th style={{padding:'10px 12px', width:70}}>Code</th>
            <th style={{padding:'10px 12px'}}>Libellé (modifiable)</th>
            <th style={{padding:'10px 12px', width:220}}>Rayon</th>
            <th style={{padding:'10px 12px', width:90, textAlign:'right'}}>Produits</th>
            <th style={{padding:'10px 12px', width:140, textAlign:'center'}}>Sous-familles</th>
            <th style={{padding:'10px 12px', width:90, textAlign:'center'}}>Statut</th>
            <th style={{padding:'10px 12px', width:110, textAlign:'right'}}></th>
          </tr>
        </thead>
        <tbody>
          {filtered.slice(0, visibleCount).map(f => {
            const patch = edits[f.code] || {};
            const dirty = patch.label !== undefined || patch.rayon_id !== undefined;
            const label = patch.label !== undefined ? patch.label : (f.label || '');
            const rayon = patch.rayon_id !== undefined ? patch.rayon_id : (f.rayon_id || '');
            return (
              <tr key={f.code} style={{borderTop:'1px solid var(--line-1)'}}>
                <td style={{padding:'8px 12px', fontFamily:'monospace', fontWeight:700}}>{f.code}</td>
                <td style={{padding:'6px 12px'}}>
                  <input
                    value={label}
                    onChange={e => setEdit(f.code, { label: e.target.value })}
                    style={{
                      width:'100%', padding:'6px 8px',
                      border: '1px solid ' + (dirty ? '#f59e0b' : 'var(--line-1)'),
                      borderRadius:6, fontSize:13, background: dirty ? '#fffbeb' : 'white',
                    }}
                  />
                </td>
                <td style={{padding:'6px 12px'}}>
                  <select
                    value={rayon || ''}
                    onChange={e => setEdit(f.code, { rayon_id: e.target.value || null })}
                    style={{
                      width:'100%', padding:'6px 8px',
                      border: '1px solid ' + (dirty ? '#f59e0b' : 'var(--line-1)'),
                      borderRadius:6, fontSize:13, background: dirty ? '#fffbeb' : 'white',
                    }}>
                    <option value="">— Aucun —</option>
                    {rayonsOpts.map(r => <option key={r.id} value={r.id}>{r.name}</option>)}
                  </select>
                </td>
                <td style={{padding:'8px 12px', textAlign:'right', fontFamily:'monospace'}}>{f.nb_products}</td>
                <td style={{padding:'6px 12px', textAlign:'center'}}>
                  <button
                    onClick={() => setSubfamilyOpenFor(f)}
                    disabled={f.nb_products === 0}
                    style={{
                      padding:'4px 10px', border:'1px solid var(--line-1)', borderRadius:6,
                      background:'white', fontSize:12, cursor: f.nb_products > 0 ? 'pointer' : 'not-allowed',
                      color: f.nb_products > 0 ? 'var(--ink-1)' : 'var(--ink-3)',
                    }}>
                    {f.nb_subfamilies > 0 ? `${f.nb_subfamilies} · Gérer` : 'Gérer'}
                  </button>
                </td>
                <td style={{padding:'8px 12px', textAlign:'center'}}>
                  {f.is_confirmed
                    ? <span style={{padding:'2px 8px', borderRadius:4, background:'#d1fae5', color:'#065f46', fontSize:11, fontWeight:700}}>VALIDÉ</span>
                    : <span style={{padding:'2px 8px', borderRadius:4, background:'#fef3c7', color:'#92400e', fontSize:11, fontWeight:700}}>À VALIDER</span>}
                </td>
                <td style={{padding:'6px 12px', textAlign:'right'}}>
                  <Button
                    variant={dirty || !f.is_confirmed ? 'primary' : 'ghost'}
                    size="sm"
                    onClick={() => saveOne(f)}
                    disabled={saving[f.code]}>
                    {saving[f.code] ? '…' : (dirty ? 'Enregistrer' : (f.is_confirmed ? '✓' : 'Valider'))}
                  </Button>
                </td>
              </tr>
            );
          })}
        </tbody>
      </table>
      {filtered.length === 0 && (
        <div className="muted" style={{padding:24, textAlign:'center'}}>Aucune famille ne correspond.</div>
      )}

      {/* Bouton Voir plus si tout n'est pas affiché */}
      {filtered.length > visibleCount && (
        <div style={{padding:16, textAlign:'center', background:'var(--surface-1)', borderTop:'1px solid var(--line-1)', borderBottomLeftRadius:10, borderBottomRightRadius:10}}>
          <div className="muted" style={{fontSize:12, marginBottom:8}}>
            Affichage de <strong>{visibleCount}</strong> sur <strong>{filtered.length}</strong> familles
          </div>
          <div style={{display:'flex', gap:8, justifyContent:'center'}}>
            <Button variant="outline" icon="chevron-down" onClick={() => setVisibleCount(c => c + 30)}>
              Voir 30 de plus
            </Button>
            <Button variant="ghost" onClick={() => setVisibleCount(filtered.length)}>
              Tout afficher ({filtered.length})
            </Button>
          </div>
        </div>
      )}
      {filtered.length > 30 && visibleCount >= filtered.length && (
        <div style={{padding:12, textAlign:'center'}}>
          <Button variant="ghost" onClick={() => setVisibleCount(30)}>Réduire</Button>
        </div>
      )}

      {subfamilyOpenFor && (
        <SubfamiliesModal
          family={subfamilyOpenFor}
          onClose={() => { setSubfamilyOpenFor(null); load(); }}
        />
      )}
    </div>
  );
}

// ========== Modal : gérer les sous-familles d'une famille Casino ==========
function SubfamiliesModal({ family, onClose }) {
  const app = useApp();
  const [subfamilies, setSubfamilies] = useState(null);
  const [assigning, setAssigning] = useState(false);
  const [assignResult, setAssignResult] = useState(null);
  const [newLabel, setNewLabel] = useState('');

  const load = async () => {
    const { data } = await window.sb.from('casino_subfamilies')
      .select('*').eq('family_code', family.code).order('label');
    setSubfamilies(data || []);
  };
  useEffect(() => { load(); }, [family.code]);

  const addManual = async () => {
    const label = newLabel.trim().toUpperCase();
    if (!label) return;
    try {
      const { error } = await window.sb.from('casino_subfamilies').insert({
        family_code: family.code, label, is_confirmed: true,
      });
      if (error) throw error;
      setNewLabel('');
      await load();
    } catch (e) { alert('Erreur : ' + (e.message || e)); }
  };

  const deleteSub = async (id) => {
    if (!confirm('Supprimer cette sous-famille ? Les produits déjà classés seront déclassés.')) return;
    try {
      await window.sb.from('products').update({ sub_family_id: null, sub_family_label: null }).eq('sub_family_id', id);
      const { error } = await window.sb.from('casino_subfamilies').delete().eq('id', id);
      if (error) throw error;
      await load();
    } catch (e) { alert('Erreur : ' + (e.message || e)); }
  };

  const runAutoAssign = async () => {
    setAssigning(true);
    try {
      const { data, error } = await window.sb.rpc('auto_assign_subfamilies', { p_family_code: family.code });
      if (error) throw error;
      const r = (data || [])[0] || {};
      setAssignResult(r);
      app.showToast && app.showToast(`${r.assigned || 0} produits classés / ${r.total || 0}`);
      await load();
    } catch (e) { alert('Erreur : ' + (e.message || e)); }
    finally { setAssigning(false); }
  };

  return (
    <Modal open={true} onClose={onClose} size="lg" title={`Sous-familles de « ${family.label || family.code} »`}
      footer={<Button variant="ghost" onClick={onClose}>Fermer</Button>}
    >
      <div style={{padding:16}}>
        <div style={{fontSize:13, marginBottom:12, color:'var(--ink-2)'}}>
          Famille Casino <strong>{family.code}</strong> · {family.nb_products} produits.
          Définis les sous-familles pour permettre un drill-down analytique fin dans les rapports.
        </div>

        {/* Liste des sous-familles existantes */}
        <h3 style={{fontSize:14, fontWeight:700, marginTop:8, marginBottom:8}}>Sous-familles actuelles ({(subfamilies || []).length})</h3>
        {subfamilies === null && <div className="muted" style={{fontSize:13}}>Chargement…</div>}
        {subfamilies && subfamilies.length === 0 && (
          <div className="muted" style={{fontSize:13, padding:12, background:'var(--surface-1)', borderRadius:8}}>
            Aucune sous-famille. Ajoute-les ci-dessous.
          </div>
        )}
        {subfamilies && subfamilies.length > 0 && (
          <table style={{width:'100%', fontSize:13, borderCollapse:'collapse', marginBottom:16}}>
            <thead>
              <tr style={{background:'var(--surface-1)', textAlign:'left'}}>
                <th style={{padding:'8px 10px'}}>Libellé</th>
                <th style={{padding:'8px 10px', textAlign:'right'}}>Produits</th>
                <th style={{padding:'8px 10px', width:80}}></th>
              </tr>
            </thead>
            <tbody>
              {subfamilies.map(sf => (
                <tr key={sf.id} style={{borderTop:'1px solid var(--line-1)'}}>
                  <td style={{padding:'8px 10px'}}>
                    <strong>{sf.label}</strong>
                    {sf.off_source_patterns && sf.off_source_patterns.length > 0 && (
                      <span style={{marginLeft:8, fontSize:11, color:'var(--ink-3)'}}>
                        via OFF : {sf.off_source_patterns.slice(0, 2).join(', ')}
                      </span>
                    )}
                  </td>
                  <td style={{padding:'8px 10px', textAlign:'right', fontFamily:'monospace'}}>{sf.nb_products || 0}</td>
                  <td style={{padding:'4px 10px', textAlign:'right'}}>
                    <button onClick={() => deleteSub(sf.id)}
                      style={{padding:'4px 8px', border:'1px solid var(--line-1)', borderRadius:6, background:'white', fontSize:11, cursor:'pointer', color:'#b91c1c'}}>
                      Supprimer
                    </button>
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        )}

        {/* Ajout manuel */}
        <div style={{display:'flex', gap:8, marginBottom:16}}>
          <input
            value={newLabel} onChange={e => setNewLabel(e.target.value)}
            placeholder="Ajouter manuellement (ex. BIERES BLONDES)"
            style={{flex:1, padding:'8px 10px', border:'1px solid var(--line-1)', borderRadius:6, fontSize:13}}
            onKeyDown={e => { if (e.key === 'Enter') addManual(); }}
          />
          <Button variant="outline" icon="plus" onClick={addManual} disabled={!newLabel.trim()}>
            Ajouter
          </Button>
        </div>

        {/* Auto-assign */}
        {subfamilies && subfamilies.length > 0 && (
          <div style={{borderTop:'1px solid var(--line-1)', paddingTop:16, marginTop:16}}>
            <Button variant="primary" icon="check" onClick={runAutoAssign} disabled={assigning}>
              {assigning ? 'Assignation…' : 'Classer les produits dans ces sous-familles'}
            </Button>
            {assignResult && (
              <div style={{marginTop:8, fontSize:13, color:'var(--ink-2)'}}>
                → {assignResult.assigned} produits classés · {assignResult.skipped} non classés (catégorie OFF inconnue) · {assignResult.total} total
              </div>
            )}
          </div>
        )}
      </div>
    </Modal>
  );
}

// ========== Écran Suivi marge par rayon (F&L, Boulangerie, Boucherie, Charcuterie, Fromage coupe) ==========
// Calcul EXACT : Marge brute = CA_HT - (stock_début + achats - stock_fin) - casse
// Utilisé pour les rayons où les produits n'ont pas d'EAN individuel (vrac, coupe, pain du jour).
const MARGIN_RAYONS = [
  { id: 'fruits-et-legumes', label: 'Fruits & Légumes' },
  { id: 'boulangerie-et-patisserie', label: 'Boulangerie' },
  { id: 'viandes-et-poissons', label: 'Boucherie / Charcuterie' },
  { id: 'produits-frais', label: 'Fromage à la coupe' },
];

// ========== Écran Budget ==========
function BudgetScreen() {
  const app = useApp();
  const today = new Date();
  const [yearMonth, setYearMonth] = useState(`${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}`);
  const [rayonId, setRayonId] = useState(''); // '' = global
  const [status, setStatus] = useState(null);
  const [targetCa, setTargetCa] = useState('');
  const [targetRate, setTargetRate] = useState('');
  const [saving, setSaving] = useState(false);
  const storeId = window.VivalData?._storeId;

  const RAYONS_FOR_BUDGET = [
    { id: '', label: 'Magasin global' },
    ...(window.VivalData?.RAYONS || []).map(r => ({ id: r.id, label: r.name })),
  ];

  const load = async () => {
    if (!storeId) return;
    const { data } = await window.sb.rpc('budget_status', {
      p_store_id: storeId,
      p_year_month: yearMonth,
      p_rayon_id: rayonId || null,
    });
    const s = (data || [])[0];
    setStatus(s || null);
    setTargetCa(s && Number(s.target_ca_ht) > 0 ? String(s.target_ca_ht) : '');
    setTargetRate(s && s.target_margin_rate ? String(s.target_margin_rate) : '');
  };
  useEffect(() => { load(); }, [yearMonth, rayonId]);

  const saveTarget = async () => {
    const ca = parseFloat(String(targetCa).replace(',', '.'));
    const rate = targetRate ? parseFloat(String(targetRate).replace(',', '.')) : null;
    if (isNaN(ca) || ca < 0) { alert('Objectif CA invalide.'); return; }
    setSaving(true);
    try {
      // Supabase upsert avec coalesce en clé composite : on fait delete + insert
      await window.sb.from('budgets').delete()
        .eq('store_id', storeId).eq('year_month', yearMonth)
        .is('rayon_id', rayonId ? undefined : null);
      if (rayonId) {
        await window.sb.from('budgets').delete()
          .eq('store_id', storeId).eq('year_month', yearMonth).eq('rayon_id', rayonId);
      }
      const { error } = await window.sb.from('budgets').insert({
        store_id: storeId, year_month: yearMonth, rayon_id: rayonId || null,
        target_ca_ht: ca, target_margin_rate: rate,
        updated_at: new Date().toISOString(),
      });
      if (error) throw error;
      await load();
      app.showToast && app.showToast('Objectif enregistré');
    } catch (e) { alert('Erreur : ' + (e.message || e)); }
    finally { setSaving(false); }
  };

  const deleteTarget = async () => {
    const perim = RAYONS_FOR_BUDGET.find(r => r.id === rayonId)?.label || '—';
    const mois = monthOptions.find(o => o.id === yearMonth)?.label || yearMonth;
    if (!confirm(`Supprimer l'objectif pour « ${perim} » en ${mois} ?`)) return;
    setSaving(true);
    try {
      let q = window.sb.from('budgets').delete()
        .eq('store_id', storeId).eq('year_month', yearMonth);
      q = rayonId ? q.eq('rayon_id', rayonId) : q.is('rayon_id', null);
      const { error } = await q;
      if (error) throw error;
      setTargetCa('');
      setTargetRate('');
      await load();
      app.showToast && app.showToast('Objectif supprimé');
    } catch (e) { alert('Erreur : ' + (e.message || e)); }
    finally { setSaving(false); }
  };

  const fmt = (v) => {
    if (v === null || v === undefined) return '—';
    return Number(v).toLocaleString('fr-FR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + ' €';
  };

  const monthOptions = (() => {
    const opts = [];
    const d = new Date();
    for (let i = 0; i < 13; i++) {
      const ym = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`;
      const lbl = d.toLocaleDateString('fr-FR', { month: 'long', year: 'numeric' });
      opts.push({ id: ym, label: lbl.charAt(0).toUpperCase() + lbl.slice(1) });
      d.setMonth(d.getMonth() - 1);
    }
    return opts;
  })();

  const achievement = status ? Number(status.achievement_pct) : 0;
  const projected = status ? Number(status.projected_achievement_pct) : 0;
  // Couleur de l'indicateur selon l'avancement vs projection
  const achColor = projected >= 100 ? '#16a34a' : projected >= 85 ? '#d97706' : '#dc2626';

  return (
    <div style={{padding:'0 32px 32px'}}>
      <div style={{display:'flex', gap:12, alignItems:'center', marginBottom:20, flexWrap:'wrap'}}>
        <label style={{fontSize:13, fontWeight:600}}>Mois :
          <select value={yearMonth} onChange={e => setYearMonth(e.target.value)}
            style={{marginLeft:8, padding:'6px 10px', border:'1px solid var(--line-1)', borderRadius:6, fontSize:14}}>
            {monthOptions.map(o => <option key={o.id} value={o.id}>{o.label}</option>)}
          </select>
        </label>
        <label style={{fontSize:13, fontWeight:600}}>Périmètre :
          <select value={rayonId} onChange={e => setRayonId(e.target.value)}
            style={{marginLeft:8, padding:'6px 10px', border:'1px solid var(--line-1)', borderRadius:6, fontSize:14}}>
            {RAYONS_FOR_BUDGET.map(r => <option key={r.id} value={r.id}>{r.label}</option>)}
          </select>
        </label>
      </div>

      {/* Saisie objectif */}
      <div className="card" style={{padding:18, marginBottom:20}}>
        <h3 style={{margin:'0 0 12px', fontSize:14, fontWeight:700}}>
          Objectif — {RAYONS_FOR_BUDGET.find(r => r.id === rayonId)?.label || '—'} — {monthOptions.find(o => o.id === yearMonth)?.label}
        </h3>
        <div style={{display:'grid', gridTemplateColumns:'repeat(auto-fit, minmax(180px, 1fr))', gap:12, alignItems:'end'}}>
          <label style={{display:'flex', flexDirection:'column', gap:4, fontSize:12, fontWeight:600}}>CA HT objectif (€)
            <input type="text" inputMode="decimal" value={targetCa}
              onChange={e => setTargetCa(e.target.value)}
              placeholder="Ex. 50000"
              style={{padding:'10px 12px', border:'1px solid var(--line-1)', borderRadius:8, fontSize:14}} />
          </label>
          <label style={{display:'flex', flexDirection:'column', gap:4, fontSize:12, fontWeight:600}}>Taux de marge cible (%)
            <input type="text" inputMode="decimal" value={targetRate}
              onChange={e => setTargetRate(e.target.value)}
              placeholder="Ex. 25"
              style={{padding:'10px 12px', border:'1px solid var(--line-1)', borderRadius:8, fontSize:14}} />
          </label>
          <div style={{display:'flex', gap:8, flexDirection:'column'}}>
            <Button variant="primary" icon="check" onClick={saveTarget} disabled={saving}>
              {saving ? 'Enregistrement…' : 'Enregistrer objectif'}
            </Button>
            {status && Number(status.target_ca_ht) > 0 && (
              <button type="button" onClick={deleteTarget} disabled={saving}
                style={{padding:'8px 12px', background:'transparent', color:'var(--danger)', border:'1px solid var(--line-1)', borderRadius:8, fontSize:12, fontWeight:600, cursor:'pointer'}}>
                🗑 Supprimer l'objectif
              </button>
            )}
          </div>
        </div>
      </div>

      {/* KPI */}
      {status && Number(status.target_ca_ht) > 0 && (
        <>
          <div className="kpi-grid">
            <div className="kpi">
              <div className="kpi-icon"><Icon name="tag" /></div>
              <div className="kpi-label">Objectif CA HT</div>
              <div className="kpi-value">{fmt(status.target_ca_ht)}</div>
              <div className="kpi-delta">{status.target_margin_rate ? `Marge cible : ${Number(status.target_margin_rate).toFixed(1).replace('.', ',')} %` : ''}</div>
            </div>
            <div className="kpi">
              <div className="kpi-icon"><Icon name="euro" /></div>
              <div className="kpi-label">Réalisé</div>
              <div className="kpi-value">{fmt(status.realized_ca_ht)}</div>
              <div className="kpi-delta">Marge : {fmt(status.realized_margin_ht)} ({Number(status.realized_margin_rate).toFixed(1).replace('.', ',')} %)</div>
            </div>
            <div className="kpi" style={{border: '2px solid ' + achColor}}>
              <div className="kpi-icon"><Icon name="list" /></div>
              <div className="kpi-label">Avancement</div>
              <div className="kpi-value" style={{color: achColor}}>{achievement.toFixed(1).replace('.', ',')} %</div>
              <div className="kpi-delta">{status.days_elapsed} / {status.days_total} jours</div>
            </div>
            <div className="kpi">
              <div className="kpi-icon"><Icon name="clock" /></div>
              <div className="kpi-label">Projection fin de mois</div>
              <div className="kpi-value">{fmt(status.projected_ca_ht)}</div>
              <div className="kpi-delta" style={{color: achColor, fontWeight:700}}>
                {projected.toFixed(1).replace('.', ',')} % de l'objectif
              </div>
            </div>
          </div>

          {/* Barre de progression */}
          <div style={{margin:'0 0 20px'}}>
            <div style={{display:'flex', justifyContent:'space-between', marginBottom:6, fontSize:12, color:'var(--ink-2)'}}>
              <span>Progression CA</span>
              <span>{achievement.toFixed(1).replace('.', ',')} % de {fmt(status.target_ca_ht)}</span>
            </div>
            <div style={{height:16, background:'var(--surface-1)', borderRadius:8, overflow:'hidden', position:'relative'}}>
              <div style={{
                width: `${Math.min(100, achievement)}%`, height:'100%',
                background: achColor, transition:'width 0.3s'
              }} />
              {/* Marqueur avancement jour */}
              <div style={{
                position:'absolute', left: `${Number(status.progress_pct)}%`, top:0, bottom:0,
                width:2, background:'#1e293b', opacity:0.6
              }} title={`Jour ${status.days_elapsed}/${status.days_total}`} />
            </div>
            <div style={{fontSize:11, color:'var(--ink-3)', marginTop:4}}>
              Repère noir = avancement jour ({Number(status.progress_pct).toFixed(0)} % du mois écoulé). Si la barre colorée est à gauche du repère, tu es en retard.
            </div>
          </div>
        </>
      )}

      {status && Number(status.target_ca_ht) === 0 && (
        <div style={{padding:40, textAlign:'center', background:'var(--surface-1)', borderRadius:10}}>
          <div className="muted" style={{fontSize:14}}>Aucun objectif défini pour cette période. Saisis-le ci-dessus.</div>
        </div>
      )}
    </div>
  );
}

// ========== Écran Achats Casino (ERP niveau 3) ==========
// =========== Ecart BL vs Facture (detection erreurs fournisseur) ===========
function EcartsFournisseurScreen() {
  const [rows, setRows] = useState(null);
  const [filterMois, setFilterMois] = useState('all');
  const [onglet, setOnglet] = useState('ecarts'); // ecarts | facture_sans_bl | bl_sans_facture
  const [sortKey, setSortKey] = useState('ecart'); // libelle | bl | facture | qty_bl | qty_fact | prix_bl | prix_fact | ecart
  const [sortDir, setSortDir] = useState('desc'); // asc | desc
  const toggleSort = (key) => {
    if (sortKey === key) setSortDir(d => d === 'asc' ? 'desc' : 'asc');
    else { setSortKey(key); setSortDir(key === 'ecart' ? 'desc' : 'asc'); }
  };

  useEffect(() => {
    (async () => {
      const sid = window.VivalData?._storeId;
      if (!sid) return;
      // Vue precise ligne-a-ligne (jointure par bl_number + code_article)
      const { data } = await window.sb.from('v_ecart_bl_facture_precis')
        .select('*').eq('store_id', sid)
        .neq('statut', 'ok')
        .limit(10000);
      setRows(data || []);
    })();
  }, []);

  if (rows === null) return <div style={{padding:40, textAlign:'center', color:'var(--ink-3)'}}>Chargement…</div>;

  // Helper: mois YYYY-MM depuis la date la plus pertinente (invoice > delivery)
  const getMois = (r) => {
    const d = r.invoice_date || r.delivery_date;
    return d ? d.slice(0, 7) : null;
  };

  // Groupes
  const ecartsReels = rows.filter(r => r.statut === 'qty_differente' || r.statut === 'prix_different');
  const factureSansBl = rows.filter(r => r.statut === 'facture_sans_bl');
  const blSansFacture = rows.filter(r => r.statut === 'bl_sans_facture');
  const totalEcartReel = ecartsReels.reduce((s, r) => s + Number(r.ecart_ht || 0), 0);
  // Compteurs de DOCUMENTS distincts (pour ne pas confondre lignes produits et factures)
  const nbFacturesSansBl = new Set(factureSansBl.map(r => r.invoice_number).filter(Boolean)).size;
  const nbBlSansFacture = new Set(blSansFacture.map(r => r.bl_number).filter(Boolean)).size;
  // Les montants pour facture_sans_bl / bl_sans_facture viennent directement de montant_facture_ht / montant_bl_ht
  const totalFactureSansBl = factureSansBl.reduce((s, r) => s + Number(r.montant_facture_ht || 0), 0);
  const totalBlSansFacture = blSansFacture.reduce((s, r) => s + Number(r.montant_bl_ht || 0), 0);

  const moisDisponibles = [...new Set(rows.map(getMois).filter(Boolean))].sort().reverse();

  const filterByMois = (arr) => filterMois === 'all' ? arr : arr.filter(r => getMois(r) === filterMois);
  const displayed = onglet === 'ecarts' ? filterByMois(ecartsReels)
    : onglet === 'facture_sans_bl' ? filterByMois(factureSansBl)
    : filterByMois(blSansFacture);
  // Tri dynamique selon la colonne cliquee
  const getSortValue = (r) => {
    switch (sortKey) {
      case 'libelle': return (r.libelle || '').toLowerCase();
      case 'bl': return r.delivery_date || '';
      case 'facture': return r.invoice_date || '';
      case 'qty_bl': return Number(r.qty_bl || 0);
      case 'qty_fact': return Number(r.qty_facture || 0);
      case 'prix_bl': return Number(r.prix_bl || 0);
      case 'prix_fact': return Number(r.prix_facture || 0);
      case 'ecart': return Math.abs(Number(r.ecart_ht || 0));
      default: return 0;
    }
  };
  const sorted = [...displayed].sort((a, b) => {
    const va = getSortValue(a), vb = getSortValue(b);
    if (va < vb) return sortDir === 'asc' ? -1 : 1;
    if (va > vb) return sortDir === 'asc' ? 1 : -1;
    return 0;
  });
  const sortIcon = (key) => sortKey !== key ? ' ⇅' : sortDir === 'asc' ? ' ↑' : ' ↓';
  const thStyle = (key, extra={}) => ({
    padding:'10px', cursor:'pointer', userSelect:'none',
    color: sortKey === key ? 'var(--ink-1)' : 'var(--ink-3)',
    fontWeight: sortKey === key ? 700 : 600,
    ...extra
  });

  return (
    <div style={{padding:'0 0 32px'}}>
      {/* Bandeau explication */}
      <div style={{padding:'14px 16px', background:'#fef3c7', border:'1px solid #fcd34d', borderRadius:12, marginBottom:16, fontSize:13, color:'#78350f', lineHeight:1.5}}>
        <div style={{fontWeight:700, marginBottom:6, fontSize:14}}>⚠ Écarts fournisseur — rapprochement ligne à ligne</div>
        Comparaison <strong>chaque ligne de BL avec sa ligne de facture Casino</strong> (via numéro de BL + code article).
        <ul style={{margin:'6px 0 0 18px', padding:0}}>
          <li><strong>Écarts réels</strong> : même produit, quantités ou prix différents → réclamer à ton commercial Casino</li>
          <li><strong>Factures sans BL</strong> : livraisons CORBAS (surgelés/épicerie DHP) dont Casino ne fournit pas le PDF BL</li>
          <li><strong>BL sans facture</strong> : tu as reçu mais la facture n'est pas encore arrivée (normal sous 5 jours)</li>
        </ul>
      </div>

      {/* KPIs */}
      <div style={{display:'grid', gridTemplateColumns:'repeat(auto-fit, minmax(200px, 1fr))', gap:12, marginBottom:16}}>
        <div className="card" style={{padding:14, borderLeft:'4px solid #dc2626'}}>
          <div className="kpi-label">⚠ Écarts réels</div>
          <div style={{fontSize:22, fontWeight:800, marginTop:4, color:'#dc2626'}}>{ecartsReels.length}</div>
          <div style={{fontSize:11, color:'var(--ink-3)', marginTop:2}}>produits avec quantité/prix différent</div>
        </div>
        <div className="card" style={{padding:14, borderLeft:`4px solid ${totalEcartReel > 0 ? '#dc2626' : totalEcartReel < 0 ? '#059669' : '#6b7280'}`}}>
          <div className="kpi-label">Total écart €</div>
          <div style={{fontSize:22, fontWeight:800, marginTop:4, color: totalEcartReel > 0 ? '#dc2626' : totalEcartReel < 0 ? '#059669' : 'inherit'}}>
            {totalEcartReel >= 0 ? '+' : ''}{totalEcartReel.toFixed(2)} €
          </div>
          <div style={{fontSize:11, color:'var(--ink-3)', marginTop:2}}>
            {totalEcartReel > 0 ? 'Casino t\'a surfacturé' : totalEcartReel < 0 ? 'Casino t\'a sous-facturé' : 'Équilibré'}
          </div>
        </div>
        <div className="card" style={{padding:14, borderLeft:'4px solid #0891b2'}}>
          <div className="kpi-label">Factures avec lignes CORBAS</div>
          <div style={{fontSize:22, fontWeight:800, marginTop:4, color:'#0891b2'}}>{nbFacturesSansBl}</div>
          <div style={{fontSize:11, color:'var(--ink-3)', marginTop:2}}>
            {factureSansBl.length} lignes · +{totalFactureSansBl.toFixed(0)} € (BL PDF non fourni par Casino)
          </div>
        </div>
        <div className="card" style={{padding:14, borderLeft:'4px solid #d97706'}}>
          <div className="kpi-label">BL en attente facture</div>
          <div style={{fontSize:22, fontWeight:800, marginTop:4, color:'#d97706'}}>{nbBlSansFacture}</div>
          <div style={{fontSize:11, color:'var(--ink-3)', marginTop:2}}>
            {blSansFacture.length} lignes · {totalBlSansFacture.toFixed(0)} € (facture pas encore reçue)
          </div>
        </div>
      </div>

      {/* Onglets */}
      <div style={{display:'flex', gap:6, borderBottom:'1px solid var(--line-1)', marginBottom:12, flexWrap:'wrap'}}>
        <button onClick={() => setOnglet('ecarts')} style={{padding:'8px 14px', border:0, background:'transparent', cursor:'pointer', fontSize:13, fontWeight:700, borderBottom: onglet === 'ecarts' ? '3px solid #dc2626' : '3px solid transparent', color: onglet === 'ecarts' ? 'var(--ink-1)' : 'var(--ink-3)', marginBottom:-1}}>
          ⚠ Écarts réels ({ecartsReels.length})
        </button>
        <button onClick={() => setOnglet('facture_sans_bl')} style={{padding:'8px 14px', border:0, background:'transparent', cursor:'pointer', fontSize:13, fontWeight:700, borderBottom: onglet === 'facture_sans_bl' ? '3px solid #0891b2' : '3px solid transparent', color: onglet === 'facture_sans_bl' ? 'var(--ink-1)' : 'var(--ink-3)', marginBottom:-1}}>
          📄 Lignes CORBAS ({factureSansBl.length})
        </button>
        <button onClick={() => setOnglet('bl_sans_facture')} style={{padding:'8px 14px', border:0, background:'transparent', cursor:'pointer', fontSize:13, fontWeight:700, borderBottom: onglet === 'bl_sans_facture' ? '3px solid #d97706' : '3px solid transparent', color: onglet === 'bl_sans_facture' ? 'var(--ink-1)' : 'var(--ink-3)', marginBottom:-1}}>
          📦 Lignes en attente facture ({blSansFacture.length})
        </button>
      </div>

      {/* Filtre mois */}
      <div style={{display:'flex', gap:8, alignItems:'center', marginBottom:12, flexWrap:'wrap'}}>
        <span style={{fontSize:12, color:'var(--ink-3)'}}>Mois :</span>
        <button className="chip" aria-pressed={filterMois === 'all'} onClick={() => setFilterMois('all')}>Tous</button>
        {moisDisponibles.slice(0, 6).map(m => (
          <button key={m} className="chip" aria-pressed={filterMois === m} onClick={() => setFilterMois(m)}>{m}</button>
        ))}
      </div>

      {/* Tableau */}
      <div className="card" style={{padding:0, overflow:'hidden'}}>
        <div style={{overflowX:'auto', maxHeight:600, overflowY:'auto'}}>
          <table style={{width:'100%', fontSize:12, borderCollapse:'collapse'}}>
            <thead style={{position:'sticky', top:0, background:'var(--surface-1)', zIndex:1}}>
              <tr style={{textAlign:'left'}}>
                <th onClick={() => toggleSort('libelle')} style={thStyle('libelle')}>Produit{sortIcon('libelle')}</th>
                <th onClick={() => toggleSort('bl')} style={thStyle('bl')}>BL{sortIcon('bl')}</th>
                <th onClick={() => toggleSort('facture')} style={thStyle('facture')}>Facture{sortIcon('facture')}</th>
                <th onClick={() => toggleSort('qty_bl')} style={thStyle('qty_bl', {textAlign:'right'})}>Qté BL{sortIcon('qty_bl')}</th>
                <th onClick={() => toggleSort('qty_fact')} style={thStyle('qty_fact', {textAlign:'right'})}>Qté fact.{sortIcon('qty_fact')}</th>
                <th onClick={() => toggleSort('prix_bl')} style={thStyle('prix_bl', {textAlign:'right'})}>Prix BL{sortIcon('prix_bl')}</th>
                <th onClick={() => toggleSort('prix_fact')} style={thStyle('prix_fact', {textAlign:'right'})}>Prix fact.{sortIcon('prix_fact')}</th>
                <th onClick={() => toggleSort('ecart')} style={thStyle('ecart', {textAlign:'right'})}>Écart €{sortIcon('ecart')}</th>
              </tr>
            </thead>
            <tbody>
              {sorted.slice(0, 200).map((r, i) => {
                const ecart = r.ecart_ht != null ? Number(r.ecart_ht) : null;
                const qtyDiff = r.qty_bl != null && r.qty_facture != null && Math.abs(Number(r.qty_bl) - Number(r.qty_facture)) > 0.01;
                const prixDiff = r.prix_bl != null && r.prix_facture != null && Math.abs(Number(r.prix_bl) - Number(r.prix_facture)) > 0.01;
                return (
                  <tr key={i} style={{borderTop:'1px solid var(--line-2)', background: Math.abs(ecart) > 50 ? '#fef2f2' : Math.abs(ecart) > 10 ? '#fffbeb' : 'transparent'}}>
                    <td style={{padding:'7px 10px', maxWidth:280}}>
                      <div style={{fontWeight:600}}>{(r.libelle || '').slice(0, 50)}</div>
                      <div style={{fontSize:10, color:'var(--ink-3)', fontFamily:'var(--font-mono)'}}>
                        {r.code_article ? `Code ${r.code_article}` : ''}
                        {r.ean ? ` · EAN ${r.ean}` : ''}
                      </div>
                    </td>
                    <td style={{padding:'7px 10px', fontSize:11}}>
                      {r.bl_number ? <div style={{fontWeight:600, fontFamily:'var(--font-mono)'}}>N°{r.bl_number}</div> : <span style={{color:'var(--ink-3)'}}>—</span>}
                      {r.delivery_date && <div style={{fontSize:10, color:'var(--ink-3)'}}>{r.delivery_date}</div>}
                    </td>
                    <td style={{padding:'7px 10px', fontSize:11}}>
                      {r.invoice_number ? <div style={{fontWeight:600, fontFamily:'var(--font-mono)'}}>N°{r.invoice_number}</div> : <span style={{color:'var(--ink-3)'}}>—</span>}
                      {r.invoice_date && <div style={{fontSize:10, color:'var(--ink-3)'}}>{r.invoice_date}</div>}
                    </td>
                    <td style={{padding:'7px 10px', textAlign:'right'}}>{r.qty_bl != null ? Number(r.qty_bl).toFixed(1) : '—'}</td>
                    <td style={{padding:'7px 10px', textAlign:'right', color: qtyDiff ? '#dc2626' : 'inherit', fontWeight: qtyDiff ? 700 : 400}}>
                      {r.qty_facture != null ? Number(r.qty_facture).toFixed(1) : '—'}
                    </td>
                    <td style={{padding:'7px 10px', textAlign:'right'}}>{r.prix_bl != null ? Number(r.prix_bl).toFixed(2) + ' €' : <span style={{color:'var(--ink-3)'}}>—</span>}</td>
                    <td style={{padding:'7px 10px', textAlign:'right', color: prixDiff ? '#dc2626' : 'inherit', fontWeight: prixDiff ? 700 : 400}}>
                      {r.prix_facture != null ? Number(r.prix_facture).toFixed(2) + ' €' : <span style={{color:'var(--ink-3)'}}>—</span>}
                    </td>
                    <td style={{padding:'7px 10px', textAlign:'right', fontWeight:700, color: ecart == null ? 'var(--ink-3)' : ecart > 0 ? '#dc2626' : ecart < 0 ? '#059669' : 'var(--ink-3)'}}>
                      {ecart == null ? '—' : (ecart >= 0 ? '+' : '') + ecart.toFixed(2) + ' €'}
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
        {sorted.length > 200 && (
          <div style={{padding:'10px', textAlign:'center', fontSize:12, color:'var(--ink-3)', borderTop:'1px solid var(--line-1)'}}>
            Affichage limité aux 200 plus gros écarts. Total : {sorted.length} lignes.
          </div>
        )}
      </div>
    </div>
  );
}

function CasinoPurchasesScreen() {
  const [tab, setTab] = useState('overview'); // overview | orders | suppliers | prices | topbought | deliveries | invoices | reconciliation
  const [monthlyTotals, setMonthlyTotals] = useState([]);
  const [orders, setOrders] = useState([]);
  const [suppliers, setSuppliers] = useState([]);
  const [priceChanges, setPriceChanges] = useState([]);
  const [topBought, setTopBought] = useState([]);
  const [deliveries, setDeliveries] = useState([]);
  const [invoices, setInvoices] = useState([]);
  const [reconciliation, setReconciliation] = useState([]);
  const [loading, setLoading] = useState(true);
  const storeId = window.VivalData?._storeId;

  const loadAll = async () => {
    if (!storeId) return;
    setLoading(true);
    const [mt, ord, sup, pc, tb, del, inv, rec] = await Promise.all([
      window.sb.from('v_casino_monthly_totals').select('*').eq('store_id', storeId).order('month', { ascending: false }).limit(12),
      window.sb.from('casino_orders').select('*').eq('store_id', storeId).order('order_date', { ascending: false }).limit(200),
      window.sb.from('v_casino_top_suppliers').select('*').eq('store_id', storeId).order('total_ht', { ascending: false }).limit(20),
      window.sb.from('v_casino_price_changes').select('*').eq('store_id', storeId).order('order_date', { ascending: false }).limit(100),
      window.sb.from('v_casino_top_bought_products').select('*').eq('store_id', storeId).order('total_ha_ht', { ascending: false, nullsFirst: false }).limit(50),
      window.sb.from('v_casino_deliveries_with_counts').select('*').eq('store_id', storeId).order('delivery_date', { ascending: false }).limit(200),
      window.sb.from('v_casino_invoices_with_counts').select('*').eq('store_id', storeId).order('invoice_date', { ascending: false }).limit(200),
      window.sb.from('v_casino_reconciliation').select('*').eq('store_id', storeId).order('order_date', { ascending: false }).limit(200),
    ]);
    setMonthlyTotals(mt.data || []);
    setOrders(ord.data || []);
    setSuppliers(sup.data || []);
    setPriceChanges(pc.data || []);
    setTopBought(tb.data || []);
    setDeliveries(del.data || []);
    setInvoices(inv.data || []);
    setReconciliation(rec.data || []);
    setLoading(false);
  };
  useEffect(() => { loadAll(); }, []);

  // Helper: export CSV generique (filename, headers[], rows[][])
  const downloadCsv = (filename, headers, rows) => {
    const esc = (c) => {
      const s = String(c ?? '');
      return /[",;\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
    };
    const csv = [headers, ...rows].map(r => r.map(esc).join(';')).join('\n');
    const blob = new Blob(['﻿' + csv], { type: 'text/csv;charset=utf-8;' });
    const a = document.createElement('a');
    a.href = URL.createObjectURL(blob);
    a.download = `${filename}-${new Date().toISOString().slice(0,10)}.csv`;
    document.body.appendChild(a); a.click(); a.remove();
  };

  if (loading) return <div style={{padding:32, color:'var(--ink-3)'}}>Chargement des données ERP Casino…</div>;

  const lastMonth = monthlyTotals[0];
  const prevMonth = monthlyTotals[1];
  const totalAllTime = monthlyTotals.reduce((s, m) => s + (Number(m.total_amount_ht) || 0), 0);

  if (!orders.length && !monthlyTotals.length) {
    return (
      <div style={{padding:32, textAlign:'center'}}>
        <div style={{fontSize:16, fontWeight:700, marginBottom:10}}>Aucune commande Casino importée</div>
        <div className="muted" style={{fontSize:13, marginBottom:20}}>
          Installe l'extension Chrome MonVival Sync, va sur casino-pro.groupe-casino.fr,<br/>
          et clique sur « Synchro commandes (ERP) » dans le menu déroulant du bouton.
        </div>
        <p className="muted" style={{fontSize:12}}>Sera visible ici : historique 6 mois, évolution des prix d'achat, top fournisseurs, suggestions de réappro.</p>
      </div>
    );
  }

  return (
    <div style={{padding:'0 32px 32px'}}>
      {/* Sub-tabs */}
      <div style={{display:'flex', gap:6, borderBottom:'1px solid var(--line-2)', marginBottom:16, flexWrap:'wrap'}}>
        {[
          {id:'overview', label:'Vue d’ensemble'},
          {id:'orders', label:`Commandes (${orders.length})`},
          {id:'deliveries', label:`Livraisons (${deliveries.length})`},
          {id:'invoices', label:`Factures (${invoices.length})`},
          {id:'reconciliation', label:'Contrôle livraisons'},
          {id:'ecarts', label:'⚠ Écarts fournisseur'},
          {id:'suppliers', label:`Fournisseurs (${suppliers.length})`},
          {id:'prices', label:`Évolutions prix (${priceChanges.length})`},
          {id:'topbought', label:'Top produits achetés'},
        ].map(t => (
          <button key={t.id} onClick={() => setTab(t.id)}
            style={{
              padding:'8px 14px', border:0, background:'transparent', cursor:'pointer',
              fontSize:13, fontWeight:700,
              borderBottom: tab === t.id ? '3px solid var(--vival-red)' : '3px solid transparent',
              color: tab === t.id ? 'var(--ink-1)' : 'var(--ink-3)',
              marginBottom:-1,
            }}>{t.label}</button>
        ))}
      </div>

      {tab === 'overview' && (
        <div>
          {/* KPI */}
          <div style={{display:'grid', gridTemplateColumns:'repeat(auto-fit, minmax(200px, 1fr))', gap:12, marginBottom:20}}>
            <div className="card" style={{padding:16}}>
              <div className="kpi-label">Achats ce mois</div>
              <div style={{fontSize:22, fontWeight:800, marginTop:4}}>{lastMonth ? Number(lastMonth.total_amount_ht).toFixed(2) : '0.00'} € HT</div>
              {prevMonth && lastMonth && (
                <div className="muted" style={{fontSize:11, marginTop:4}}>
                  {lastMonth.total_amount_ht > prevMonth.total_amount_ht ? '↗' : '↘'} vs {Number(prevMonth.total_amount_ht).toFixed(2)} € mois dernier
                </div>
              )}
            </div>
            <div className="card" style={{padding:16}}>
              <div className="kpi-label">Commandes ce mois</div>
              <div style={{fontSize:22, fontWeight:800, marginTop:4}}>{lastMonth ? lastMonth.nb_orders : 0}</div>
              <div className="muted" style={{fontSize:11, marginTop:4}}>{lastMonth?.nb_contracts || 0} contrats · {lastMonth?.nb_suppliers || 0} fournisseurs</div>
            </div>
            <div className="card" style={{padding:16}}>
              <div className="kpi-label">Total sur {monthlyTotals.length} mois</div>
              <div style={{fontSize:22, fontWeight:800, marginTop:4}}>{totalAllTime.toFixed(0)} € HT</div>
            </div>
            <div className="card" style={{padding:16}}>
              <div className="kpi-label">Hausses de prix récentes</div>
              <div style={{fontSize:22, fontWeight:800, marginTop:4, color: priceChanges.filter(p => p.price_delta > 0).length > 0 ? '#c00' : 'inherit'}}>
                {priceChanges.filter(p => p.price_delta > 0).length}
              </div>
              <div className="muted" style={{fontSize:11, marginTop:4}}>sur les 100 derniers changements</div>
            </div>
          </div>

          {/* Evolution mensuelle */}
          <div className="card" style={{padding:16, marginBottom:16}}>
            <h3 style={{margin:'0 0 12px', fontSize:15, fontWeight:800}}>Évolution mensuelle des achats</h3>
            <table style={{width:'100%', fontSize:12, borderCollapse:'collapse'}}>
              <thead>
                <tr style={{textAlign:'left', color:'var(--ink-3)'}}>
                  <th style={{padding:'6px 8px'}}>Mois</th>
                  <th style={{padding:'6px 8px', textAlign:'right'}}>Nb commandes</th>
                  <th style={{padding:'6px 8px', textAlign:'right'}}>Total HT</th>
                  <th style={{padding:'6px 8px', textAlign:'right'}}>UC</th>
                  <th style={{padding:'6px 8px', textAlign:'right'}}>Fournisseurs</th>
                </tr>
              </thead>
              <tbody>
                {monthlyTotals.map(m => (
                  <tr key={m.month} style={{borderTop:'1px solid var(--line-2)'}}>
                    <td style={{padding:'6px 8px', fontWeight:600}}>{new Date(m.month).toLocaleDateString('fr-FR', {month:'long', year:'numeric'})}</td>
                    <td style={{padding:'6px 8px', textAlign:'right'}}>{m.nb_orders}</td>
                    <td style={{padding:'6px 8px', textAlign:'right', fontWeight:700}}>{Number(m.total_amount_ht).toFixed(2)} €</td>
                    <td style={{padding:'6px 8px', textAlign:'right'}}>{m.total_uc}</td>
                    <td style={{padding:'6px 8px', textAlign:'right'}}>{m.nb_suppliers}</td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        </div>
      )}

      {tab === 'orders' && (
        <>
        <div style={{display:'flex', justifyContent:'flex-end', marginBottom:10}}>
          <button onClick={() => downloadCsv('casino-commandes',
            ['Date commande','Bon livraison','Contrat','Fournisseur','Origine','UC','Montant HT','Livraison'],
            orders.map(o => [
              o.order_date ? new Date(o.order_date).toLocaleDateString('fr-FR') : '',
              o.order_id, o.contract || '', o.supplier_label || '', o.origin || '',
              o.total_uc || '', Number(o.total_amount || 0).toFixed(2),
              o.delivery_date ? new Date(o.delivery_date).toLocaleDateString('fr-FR') : ''
            ])
          )} className="btn btn-outline" style={{fontSize:13, padding:'6px 12px'}}>
            📥 Export CSV ({orders.length})
          </button>
        </div>
        <div className="card" style={{padding:0, overflow:'hidden'}}>
          <table style={{width:'100%', fontSize:12, borderCollapse:'collapse'}}>
            <thead>
              <tr style={{textAlign:'left', background:'var(--surface-1)', color:'var(--ink-3)'}}>
                <th style={{padding:'8px 10px'}}>Date commande</th>
                <th style={{padding:'8px 10px'}}>Bon livraison</th>
                <th style={{padding:'8px 10px'}}>Contrat</th>
                <th style={{padding:'8px 10px'}}>Fournisseur</th>
                <th style={{padding:'8px 10px'}}>Origine</th>
                <th style={{padding:'8px 10px', textAlign:'right'}}>UC</th>
                <th style={{padding:'8px 10px', textAlign:'right'}}>Montant HT</th>
                <th style={{padding:'8px 10px'}}>Livraison</th>
              </tr>
            </thead>
            <tbody>
              {orders.map(o => (
                <tr key={o.id} style={{borderTop:'1px solid var(--line-2)'}}>
                  <td style={{padding:'6px 10px'}}>{o.order_date ? new Date(o.order_date).toLocaleDateString('fr-FR') : '—'}</td>
                  <td style={{padding:'6px 10px', fontFamily:'var(--font-mono)', fontSize:11}}>{o.order_id}</td>
                  <td style={{padding:'6px 10px'}}>{o.contract}</td>
                  <td style={{padding:'6px 10px'}}>{(o.supplier_label || '').slice(0, 40)}</td>
                  <td style={{padding:'6px 10px'}}>{o.origin}</td>
                  <td style={{padding:'6px 10px', textAlign:'right'}}>{o.total_uc}</td>
                  <td style={{padding:'6px 10px', textAlign:'right', fontWeight:700}}>{Number(o.total_amount || 0).toFixed(2)} €</td>
                  <td style={{padding:'6px 10px'}}>{o.delivery_date ? new Date(o.delivery_date).toLocaleDateString('fr-FR') : '—'}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
        </>
      )}

      {tab === 'ecarts' && <EcartsFournisseurScreen />}

      {tab === 'suppliers' && (
        <div className="card" style={{padding:0, overflow:'hidden'}}>
          <table style={{width:'100%', fontSize:12, borderCollapse:'collapse'}}>
            <thead>
              <tr style={{textAlign:'left', background:'var(--surface-1)', color:'var(--ink-3)'}}>
                <th style={{padding:'8px 10px'}}>Fournisseur</th>
                <th style={{padding:'8px 10px'}}>Contrat</th>
                <th style={{padding:'8px 10px', textAlign:'right'}}>Commandes</th>
                <th style={{padding:'8px 10px', textAlign:'right'}}>Total HT</th>
                <th style={{padding:'8px 10px', textAlign:'right'}}>UC</th>
                <th style={{padding:'8px 10px'}}>Dernière</th>
              </tr>
            </thead>
            <tbody>
              {suppliers.map((s, i) => (
                <tr key={i} style={{borderTop:'1px solid var(--line-2)'}}>
                  <td style={{padding:'6px 10px'}}>{s.supplier_label}</td>
                  <td style={{padding:'6px 10px'}}>{s.contract}</td>
                  <td style={{padding:'6px 10px', textAlign:'right'}}>{s.nb_orders}</td>
                  <td style={{padding:'6px 10px', textAlign:'right', fontWeight:700}}>{Number(s.total_ht || 0).toFixed(2)} €</td>
                  <td style={{padding:'6px 10px', textAlign:'right'}}>{s.total_uc}</td>
                  <td style={{padding:'6px 10px'}}>{s.last_order_date ? new Date(s.last_order_date).toLocaleDateString('fr-FR') : '—'}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}

      {tab === 'prices' && (
        <div className="card" style={{padding:0, overflow:'hidden'}}>
          <table style={{width:'100%', fontSize:12, borderCollapse:'collapse'}}>
            <thead>
              <tr style={{textAlign:'left', background:'var(--surface-1)', color:'var(--ink-3)'}}>
                <th style={{padding:'8px 10px'}}>Date</th>
                <th style={{padding:'8px 10px'}}>Produit</th>
                <th style={{padding:'8px 10px', textAlign:'right'}}>Ancien prix</th>
                <th style={{padding:'8px 10px', textAlign:'right'}}>Nouveau prix</th>
                <th style={{padding:'8px 10px', textAlign:'right'}}>Δ</th>
              </tr>
            </thead>
            <tbody>
              {priceChanges.map((p, i) => (
                <tr key={i} style={{borderTop:'1px solid var(--line-2)', background: p.price_delta > 0 ? '#fef2f2' : '#f0fdf4'}}>
                  <td style={{padding:'6px 10px'}}>{new Date(p.order_date).toLocaleDateString('fr-FR')}</td>
                  <td style={{padding:'6px 10px'}}>
                    <div>{p.name}</div>
                    <div className="muted" style={{fontSize:10}}>EAN {p.ean}</div>
                  </td>
                  <td style={{padding:'6px 10px', textAlign:'right'}}>{Number(p.old_price).toFixed(2)} €</td>
                  <td style={{padding:'6px 10px', textAlign:'right', fontWeight:700}}>{Number(p.new_price).toFixed(2)} €</td>
                  <td style={{padding:'6px 10px', textAlign:'right', fontWeight:800, color: p.price_delta > 0 ? '#c00' : '#0a7'}}>
                    {p.price_delta > 0 ? '+' : ''}{Number(p.price_delta).toFixed(2)} € ({Number(p.price_change_pct).toFixed(1)}%)
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}

      {tab === 'topbought' && (
        <div className="card" style={{padding:0, overflow:'hidden'}}>
          <table style={{width:'100%', fontSize:12, borderCollapse:'collapse'}}>
            <thead>
              <tr style={{textAlign:'left', background:'var(--surface-1)', color:'var(--ink-3)'}}>
                <th style={{padding:'8px 10px'}}>Produit</th>
                <th style={{padding:'8px 10px'}}>Famille</th>
                <th style={{padding:'8px 10px', textAlign:'right'}}>Commandes</th>
                <th style={{padding:'8px 10px', textAlign:'right'}}>UVC reçues</th>
                <th style={{padding:'8px 10px', textAlign:'right'}}>PA moyen</th>
                <th style={{padding:'8px 10px', textAlign:'right'}}>Total achat HT</th>
                <th style={{padding:'8px 10px'}}>Dernière commande</th>
              </tr>
            </thead>
            <tbody>
              {topBought.map((p, i) => (
                <tr key={i} style={{borderTop:'1px solid var(--line-2)'}}>
                  <td style={{padding:'6px 10px'}}>
                    <div>{p.name}</div>
                    <div className="muted" style={{fontSize:10, fontFamily:'var(--font-mono)'}}>EAN {p.ean}</div>
                  </td>
                  <td style={{padding:'6px 10px', fontSize:11}}>{(p.family_label || '').slice(0, 30)}</td>
                  <td style={{padding:'6px 10px', textAlign:'right'}}>{p.nb_orders}</td>
                  <td style={{padding:'6px 10px', textAlign:'right'}}>{p.total_uvc_received}</td>
                  <td style={{padding:'6px 10px', textAlign:'right'}}>{Number(p.avg_buying_price || 0).toFixed(3)} €</td>
                  <td style={{padding:'6px 10px', textAlign:'right', fontWeight:700}}>{Number(p.total_ha_ht || 0).toFixed(2)} €</td>
                  <td style={{padding:'6px 10px'}}>{p.last_order_date ? new Date(p.last_order_date).toLocaleDateString('fr-FR') : '—'}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}

      {tab === 'deliveries' && (
        deliveries.length === 0 ? (
          <EmptyErpState kind="BL" />
        ) : (
          <>
          <div style={{display:'flex', justifyContent:'flex-end', marginBottom:10}}>
            <button onClick={() => downloadCsv('casino-bl',
              ['Date livraison','N° BL','N° Tournée','Articles','Total débit HT','Total vente TTC','Marge €','PDF GED'],
              deliveries.map(d => {
                const debit = Number(d.total_debit_ht || 0);
                const vente = Number(d.total_vente_ht || 0);
                return [
                  d.delivery_date ? new Date(d.delivery_date).toLocaleDateString('fr-FR') : '',
                  d.bl_number, d.tour_number || '', d.total_articles_count || '',
                  debit.toFixed(2), vente.toFixed(2), (vente - debit).toFixed(2),
                  d.pdf_node_id ? `https://ged.groupe-casino.fr/OTCS/cs.exe/app/nodes/${d.pdf_node_id}` : ''
                ];
              })
            )} className="btn btn-outline" style={{fontSize:13, padding:'6px 12px'}}>
              📥 Export CSV ({deliveries.length})
            </button>
          </div>
          <div className="card" style={{padding:0, overflow:'hidden'}}>
            <table style={{width:'100%', fontSize:12, borderCollapse:'collapse'}}>
              <thead>
                <tr style={{textAlign:'left', background:'var(--surface-1)', color:'var(--ink-3)'}}>
                  <th style={{padding:'8px 10px'}}>Date livraison</th>
                  <th style={{padding:'8px 10px'}}>N° BL</th>
                  <th style={{padding:'8px 10px'}}>N° Tournée</th>
                  <th style={{padding:'8px 10px', textAlign:'right'}}>Articles</th>
                  <th style={{padding:'8px 10px', textAlign:'right'}}>Total débit HT</th>
                  <th style={{padding:'8px 10px', textAlign:'right'}}>Total vente TTC</th>
                  <th style={{padding:'8px 10px', textAlign:'right'}}>Marge €</th>
                  <th style={{padding:'8px 10px', textAlign:'center'}}>PDF</th>
                </tr>
              </thead>
              <tbody>
                {deliveries.map(d => {
                  const debit = Number(d.total_debit_ht || 0);
                  const vente = Number(d.total_vente_ht || 0);
                  const marge = vente - debit;
                  return (
                    <tr key={d.id} style={{borderTop:'1px solid var(--line-2)'}}>
                      <td style={{padding:'6px 10px'}}>{d.delivery_date ? new Date(d.delivery_date).toLocaleDateString('fr-FR') : '—'}</td>
                      <td style={{padding:'6px 10px', fontFamily:'var(--font-mono)', fontSize:11, fontWeight:700}}>{d.bl_number}</td>
                      <td style={{padding:'6px 10px', fontFamily:'var(--font-mono)', fontSize:11}}>{d.tour_number || '—'}</td>
                      <td style={{padding:'6px 10px', textAlign:'right'}}>{d.total_articles_count || '—'}</td>
                      <td style={{padding:'6px 10px', textAlign:'right'}}>{debit > 0 ? debit.toFixed(2) + ' €' : '—'}</td>
                      <td style={{padding:'6px 10px', textAlign:'right', fontWeight:700}}>{vente > 0 ? vente.toFixed(2) + ' €' : '—'}</td>
                      <td style={{padding:'6px 10px', textAlign:'right', color: marge > 0 ? '#059669' : 'inherit'}}>{marge > 0 ? '+' + marge.toFixed(2) + ' €' : '—'}</td>
                      <td style={{padding:'6px 10px', textAlign:'center'}}>
                        {d.pdf_node_id ? (
                          <a href={`https://ged.groupe-casino.fr/OTCS/cs.exe/app/nodes/${d.pdf_node_id}`} target="_blank" rel="noopener noreferrer"
                             title="Ouvrir le PDF dans la GED Casino"
                             style={{color:'#0891b2', textDecoration:'none', fontSize:16, fontWeight:700}}>
                            📄
                          </a>
                        ) : <span style={{color:'var(--ink-3)'}}>—</span>}
                      </td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          </div>
          </>
        )
      )}

      {tab === 'invoices' && (
        invoices.length === 0 ? (
          <EmptyErpState kind="Factures" />
        ) : (
          <>
          <div style={{display:'flex', justifyContent:'flex-end', marginBottom:10}}>
            <button onClick={() => downloadCsv('casino-factures',
              ['Date facture','N° facture','N° BL','Lignes','Total HT','Total TTC','Remises','PDF GED'],
              invoices.map(iv => [
                iv.invoice_date ? new Date(iv.invoice_date).toLocaleDateString('fr-FR') : '',
                iv.invoice_number, iv.bl_number || '', iv.nb_lines || 0,
                Number(iv.total_ht || 0).toFixed(2), Number(iv.total_ttc || 0).toFixed(2),
                iv.total_discount ? Number(iv.total_discount).toFixed(2) : '',
                iv.pdf_node_id ? `https://ged.groupe-casino.fr/OTCS/cs.exe/app/nodes/${iv.pdf_node_id}` : ''
              ])
            )} className="btn btn-outline" style={{fontSize:13, padding:'6px 12px'}}>
              📥 Export CSV ({invoices.length})
            </button>
          </div>
          <div className="card" style={{padding:0, overflow:'hidden'}}>
            <table style={{width:'100%', fontSize:12, borderCollapse:'collapse'}}>
              <thead>
                <tr style={{textAlign:'left', background:'var(--surface-1)', color:'var(--ink-3)'}}>
                  <th style={{padding:'8px 10px'}}>Date facture</th>
                  <th style={{padding:'8px 10px'}}>N° facture</th>
                  <th style={{padding:'8px 10px'}}>N° BL</th>
                  <th style={{padding:'8px 10px', textAlign:'right'}}>Lignes</th>
                  <th style={{padding:'8px 10px', textAlign:'right'}}>Total HT</th>
                  <th style={{padding:'8px 10px', textAlign:'right'}}>Total TTC</th>
                  <th style={{padding:'8px 10px', textAlign:'right'}}>Remises</th>
                  <th style={{padding:'8px 10px', textAlign:'center'}}>PDF</th>
                </tr>
              </thead>
              <tbody>
                {invoices.map(iv => (
                  <tr key={iv.id} style={{borderTop:'1px solid var(--line-2)'}}>
                    <td style={{padding:'6px 10px'}}>{iv.invoice_date ? new Date(iv.invoice_date).toLocaleDateString('fr-FR') : '—'}</td>
                    <td style={{padding:'6px 10px', fontFamily:'var(--font-mono)', fontSize:11}}>{iv.invoice_number}</td>
                    <td style={{padding:'6px 10px', fontFamily:'var(--font-mono)', fontSize:11}}>{iv.bl_number || '—'}</td>
                    <td style={{padding:'6px 10px', textAlign:'right'}}>{iv.nb_lines || 0}</td>
                    <td style={{padding:'6px 10px', textAlign:'right', fontWeight:700}}>{Number(iv.total_ht || 0).toFixed(2)} €</td>
                    <td style={{padding:'6px 10px', textAlign:'right'}}>{Number(iv.total_ttc || 0).toFixed(2)} €</td>
                    <td style={{padding:'6px 10px', textAlign:'right', color: (iv.total_discount || 0) > 0 ? '#0a7' : 'inherit'}}>
                      {iv.total_discount ? `-${Number(iv.total_discount).toFixed(2)} €` : '—'}
                    </td>
                    <td style={{padding:'6px 10px', textAlign:'center'}}>
                      {iv.pdf_node_id ? (
                        <a href={`https://ged.groupe-casino.fr/OTCS/cs.exe/app/nodes/${iv.pdf_node_id}`} target="_blank" rel="noopener noreferrer"
                           title="Ouvrir la facture dans la GED Casino"
                           style={{color:'#0891b2', textDecoration:'none', fontSize:16, fontWeight:700}}>
                          📄
                        </a>
                      ) : <span style={{color:'var(--ink-3)'}}>—</span>}
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
          </>
        )
      )}

      {tab === 'reconciliation' && (
        reconciliation.length === 0 ? (
          <EmptyErpState kind="Contrôle" />
        ) : (
          <div>
            {/* Bandeau d'explication */}
            <div style={{padding:'14px 16px', background:'#eff6ff', border:'1px solid #bfdbfe', borderRadius:12, marginBottom:16, fontSize:13, color:'#1e3a8a', lineHeight:1.5}}>
              <div style={{fontWeight:700, marginBottom:6, fontSize:14}}>Contrôle livraisons — Marge prévisionnelle par livraison</div>
              Pour chaque livraison Casino, on compare le <strong>total vente conseillé HT du BL</strong> (ce que tu vas facturer à tes clients) avec le <strong>total HT facturé par Casino</strong> (ton prix d'achat).
              Le résultat, c'est ta <strong>marge brute prévisionnelle</strong> sur cette livraison.
              <div style={{marginTop:10, paddingTop:10, borderTop:'1px solid #bfdbfe'}}>
                <strong style={{color:'#059669'}}>Marge positive (en vert)</strong> → normal, tu gagnes de l'argent en revendant.<br/>
                <strong style={{color:'#d97706'}}>Facture à venir</strong> → livraison reçue mais facture pas encore arrivée (délai Casino 2-5 jours).<br/>
                <strong style={{color:'#dc2626'}}>Marge négative</strong> → anormal, à vérifier (erreur Casino ou produit vendu à perte).
              </div>
            </div>

            {/* KPI synthèse - marge cumulée */}
            {(() => {
              const complete = reconciliation.filter(r => r.invoice_number && r.invoice_amount != null && r.order_amount != null);
              const attente = reconciliation.filter(r => !r.invoice_number);
              const margeTotal = complete.reduce((s, r) => s + (Number(r.order_amount) || 0) - (Number(r.invoice_amount) || 0), 0);
              const margesNegatives = complete.filter(r => (Number(r.order_amount) || 0) - (Number(r.invoice_amount) || 0) < -0.5);
              const totalAchat = complete.reduce((s, r) => s + (Number(r.invoice_amount) || 0), 0);
              const tauxMarge = totalAchat > 0 ? (margeTotal / totalAchat * 100) : 0;
              return (
                <div style={{display:'grid', gridTemplateColumns:'repeat(auto-fit, minmax(220px, 1fr))', gap:12, marginBottom:16}}>
                  <div className="card" style={{padding:14, borderLeft:'4px solid #059669'}}>
                    <div className="kpi-label">💰 Marge prévisionnelle totale</div>
                    <div style={{fontSize:22, fontWeight:800, marginTop:4, color:'#059669'}}>+{margeTotal.toFixed(0)} €</div>
                    <div style={{fontSize:11, color:'var(--ink-3)', marginTop:2}}>taux : {tauxMarge.toFixed(1)} % sur {complete.length} livraisons</div>
                  </div>
                  <div className="card" style={{padding:14, borderLeft:'4px solid #d97706'}}>
                    <div className="kpi-label">⏳ Facture à venir</div>
                    <div style={{fontSize:22, fontWeight:800, marginTop:4, color:'#d97706'}}>{attente.length}</div>
                    <div style={{fontSize:11, color:'var(--ink-3)', marginTop:2}}>livraisons sans facture reçue</div>
                  </div>
                  <div className="card" style={{padding:14, borderLeft:'4px solid #dc2626'}}>
                    <div className="kpi-label">⚠ Marges négatives</div>
                    <div style={{fontSize:22, fontWeight:800, marginTop:4, color:'#dc2626'}}>{margesNegatives.length}</div>
                    <div style={{fontSize:11, color:'var(--ink-3)', marginTop:2}}>livraisons où Casino te facture plus que la vente conseillée</div>
                  </div>
                </div>
              );
            })()}

            {/* Tableau détail */}
            <div className="card" style={{padding:0, overflow:'hidden'}}>
              <div style={{padding:'12px 16px', borderBottom:'1px solid var(--line-1)', background:'var(--surface-1)'}}>
                <div style={{fontWeight:700, fontSize:14}}>Détail livraison par livraison</div>
                <div style={{fontSize:12, color:'var(--ink-3)', marginTop:2}}>
                  Si un BL a plusieurs factures, chaque facture apparaît sur une ligne séparée.
                </div>
              </div>
              <table style={{width:'100%', fontSize:12, borderCollapse:'collapse'}}>
                <thead>
                  <tr style={{textAlign:'left', background:'var(--surface-2)', color:'var(--ink-2)'}}>
                    <th style={{padding:'8px 10px'}}>Date livraison</th>
                    <th style={{padding:'8px 10px'}}>N° BL</th>
                    <th style={{padding:'8px 10px'}}>N° facture</th>
                    <th style={{padding:'8px 10px', textAlign:'right'}} title="Total vente conseillé HT du BL (ce que tu vas revendre)">Vente conseillée</th>
                    <th style={{padding:'8px 10px', textAlign:'right'}} title="Total HT de la facture Casino (ton prix d'achat)">Prix d'achat</th>
                    <th style={{padding:'8px 10px', textAlign:'right'}}>Marge brute</th>
                    <th style={{padding:'8px 10px'}}>Situation</th>
                  </tr>
                </thead>
                <tbody>
                  {reconciliation.map((r, i) => {
                    const ventConseil = Number(r.order_amount) || 0;  // total_vente_ht BL
                    const achat = r.invoice_amount != null ? Number(r.invoice_amount) : null;
                    // Marge = vente conseillée - prix d'achat (inversé vs avant pour une logique comptable)
                    const marge = achat != null && ventConseil > 0 ? ventConseil - achat : null;
                    const margeNeg = marge != null && marge < -0.5;
                    let status, bg;
                    if (!r.invoice_number) { status = { label:'⏳ Facture à venir', color:'#d97706' }; bg = '#fffbeb'; }
                    else if (margeNeg) { status = { label:`⚠ Marge négative`, color:'#dc2626' }; bg = '#fef2f2'; }
                    else { status = { label:`✅ Marge ${marge > 0 ? '+' : ''}${(marge || 0).toFixed(2)} €`, color:'#059669' }; bg = '#f0fdf4'; }
                    return (
                      <tr key={i} style={{borderTop:'1px solid var(--line-2)', background: bg}}>
                        <td style={{padding:'8px 10px'}}>{r.order_date ? new Date(r.order_date).toLocaleDateString('fr-FR') : '—'}</td>
                        <td style={{padding:'8px 10px', fontFamily:'var(--font-mono)', fontSize:11, fontWeight:700}}>{r.bl_number}</td>
                        <td style={{padding:'8px 10px', fontFamily:'var(--font-mono)', fontSize:11}}>{r.invoice_number || <span style={{color:'#d97706'}}>—</span>}</td>
                        <td style={{padding:'8px 10px', textAlign:'right'}}>{ventConseil > 0 ? ventConseil.toFixed(2) + ' €' : '—'}</td>
                        <td style={{padding:'8px 10px', textAlign:'right', fontWeight: achat != null ? 700 : 400}}>
                          {achat != null ? achat.toFixed(2) + ' €' : <span style={{color:'var(--ink-3)'}}>—</span>}
                        </td>
                        <td style={{padding:'8px 10px', textAlign:'right', fontWeight:700, color: marge == null ? 'var(--ink-3)' : margeNeg ? '#dc2626' : '#059669'}}>
                          {marge != null ? (marge > 0 ? '+' : '') + marge.toFixed(2) + ' €' : '—'}
                        </td>
                        <td style={{padding:'8px 10px', color: status.color, fontWeight:700, fontSize:11}}>{status.label}</td>
                      </tr>
                    );
                  })}
                </tbody>
              </table>
            </div>
          </div>
        )
      )}
    </div>
  );
}

function EmptyErpState({ kind }) {
  return (
    <div style={{padding:40, textAlign:'center'}}>
      <div style={{fontSize:15, fontWeight:700, marginBottom:8}}>Aucune donnée {kind.toLowerCase()}</div>
      <div className="muted" style={{fontSize:13, marginBottom:16}}>
        Lance la synchro ERP depuis Réglages → Magasin pour récupérer<br/>
        tes {kind === 'BL' ? 'bordereaux de livraison' : kind === 'Factures' ? 'factures' : 'commandes à contrôler'} Casino.
      </div>
    </div>
  );
}

// ========== Écran Alertes ==========
// Traduction FR des codes techniques renvoyes par get_alerts()
const ALERT_TYPE_FR = {
  negative_margin: 'Marge négative',
  stock_zero: 'Rupture',
  rupture: 'Rupture',
  rupture_imminente: 'Rupture imminente',
  ratio_low: 'Marge faible',
  family_unmapped: 'Famille à mapper',
  missing_pa: 'Prix d’achat manquant',
  temperature_alert: 'Alerte température',
};
function alertTypeLabel(type) {
  return ALERT_TYPE_FR[type] || (type || '').replace(/_/g, ' ');
}

function AlertsScreen() {
  const [alerts, setAlerts] = useState(null);
  const [filter, setFilter] = useState('all'); // all | error | warning | info

  const load = async () => {
    try {
      const { data } = await window.sb.rpc('get_alerts');
      setAlerts(data || []);
    } catch (e) { console.error('alerts', e); setAlerts([]); }
  };
  useEffect(() => { load(); }, []);

  if (alerts === null) return <div className="muted" style={{padding:40, textAlign:'center'}}>Chargement…</div>;

  const byType = alerts.reduce((acc, a) => ({ ...acc, [a.severity]: (acc[a.severity] || 0) + 1 }), {});
  const filtered = filter === 'all' ? alerts : alerts.filter(a => a.severity === filter);

  const iconFor = (sev) => sev === 'error' ? '🔴' : sev === 'warning' ? '🟠' : '🔵';
  const colorFor = (sev) => sev === 'error' ? '#dc2626' : sev === 'warning' ? '#d97706' : '#2563eb';
  const bgFor = (sev) => sev === 'error' ? '#fef2f2' : sev === 'warning' ? '#fffbeb' : '#eff6ff';

  return (
    <div style={{padding:'0 32px 32px'}}>
      <div style={{display:'flex', gap:8, marginBottom:16, flexWrap:'wrap'}}>
        <button className="chip" aria-pressed={filter === 'all'} onClick={() => setFilter('all')}>
          Toutes ({alerts.length})
        </button>
        <button className="chip" aria-pressed={filter === 'error'} onClick={() => setFilter('error')}
          style={filter === 'error' ? {background:'#dc2626', color:'white', borderColor:'#dc2626'} : {}}>
          Erreurs ({byType.error || 0})
        </button>
        <button className="chip" aria-pressed={filter === 'warning'} onClick={() => setFilter('warning')}
          style={filter === 'warning' ? {background:'#d97706', color:'white', borderColor:'#d97706'} : {}}>
          Avertissements ({byType.warning || 0})
        </button>
        <button className="chip" aria-pressed={filter === 'info'} onClick={() => setFilter('info')}
          style={filter === 'info' ? {background:'#2563eb', color:'white', borderColor:'#2563eb'} : {}}>
          Infos ({byType.info || 0})
        </button>
      </div>

      {filtered.length === 0 && (
        <div style={{padding:48, textAlign:'center', background:'white', borderRadius:12, border:'1px solid var(--line-1)'}}>
          <div style={{fontSize:40, marginBottom:12}}>✅</div>
          <div style={{fontSize:16, fontWeight:700, color:'var(--ink-1)'}}>Aucune alerte !</div>
          <div style={{fontSize:13, color:'var(--ink-3)', marginTop:4}}>Tout est sous contrôle.</div>
        </div>
      )}

      {filtered.length > 0 && (
        <div style={{display:'flex', flexDirection:'column', gap:10}}>
          {filtered.map((a, i) => (
            <div key={`${a.alert_type}-${a.target_id}-${i}`}
              style={{
                padding:'14px 18px', background: bgFor(a.severity),
                borderRadius:10, borderLeft: '4px solid ' + colorFor(a.severity),
                display:'flex', gap:12, alignItems:'flex-start',
              }}>
              <div style={{fontSize:18, lineHeight:1, marginTop:2}}>{iconFor(a.severity)}</div>
              <div style={{flex:1, minWidth:0}}>
                <div style={{fontSize:14, fontWeight:700, color:colorFor(a.severity), marginBottom:3}}>
                  {a.title}
                </div>
                <div style={{fontSize:12, color:'var(--ink-2)', lineHeight:1.5}}>{a.detail}</div>
              </div>
              <span style={{fontSize:10, padding:'2px 8px', borderRadius:4, background:'white', color:colorFor(a.severity), fontWeight:700, textTransform:'uppercase', flexShrink:0}}>
                {alertTypeLabel(a.alert_type)}
              </span>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

// ========== Écran Top / Flop produits ==========
function TopFlopScreen() {
  const { setRoute } = useApp();
  const [subTab, setSubTab] = useState('top'); // top | dormant | low_margin
  const [days, setDays] = useState(30);
  const [loading, setLoading] = useState(true);
  const [topRows, setTopRows] = useState([]);
  const [dormantRows, setDormantRows] = useState([]);
  const [lowMarginRows, setLowMarginRows] = useState([]);

  // Clic sur une ligne: ouvre AdminCatalogScreen avec recherche pre-remplie sur l'EAN
  const openProductEdit = (ean, name) => {
    if (!ean && !name) return;
    sessionStorage.setItem('vival_admin_search', ean || name);
    sessionStorage.setItem('vival_admin_come_from', 'topflop');
    setRoute('admin-catalog');
  };

  const fmt = (v) => {
    if (v === null || v === undefined) return '—';
    return Number(v).toLocaleString('fr-FR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + ' €';
  };

  useEffect(() => {
    let cancelled = false;
    setLoading(true);
    Promise.all([
      window.sb.rpc('top_products_by_ca', { p_days: days, p_limit: 30 }),
      window.sb.rpc('dormant_products', { p_days: Math.max(days, 60), p_limit: 50 }),
      window.sb.rpc('low_margin_products', { p_days: days, p_min_rate: 5, p_limit: 30 }),
    ]).then(([t, d, l]) => {
      if (cancelled) return;
      setTopRows(t.data || []);
      setDormantRows(d.data || []);
      setLowMarginRows(l.data || []);
      setLoading(false);
    }).catch((e) => { console.error('top-flop load', e); if (!cancelled) setLoading(false); });
    return () => { cancelled = true; };
  }, [days]);

  const totalDormantValue = dormantRows.reduce((s, r) => s + Number(r.valeur_dormante || 0), 0);

  return (
    <>
      <div style={{padding:'0 32px 12px', display:'flex', gap:8, alignItems:'center', flexWrap:'wrap'}}>
        {[{id:'top', label:'Top ventes', count:topRows.length},
          {id:'dormant', label:'Stock dormant', count:dormantRows.length},
          {id:'low_margin', label:'Marges basses', count:lowMarginRows.length}].map(t => (
          <button key={t.id} onClick={() => setSubTab(t.id)}
            style={{
              padding: subTab === t.id ? '10px 18px' : '8px 16px',
              borderRadius:999, border: '2px solid ' + (subTab === t.id ? 'var(--vival-red)' : 'var(--line-1)'),
              background: subTab === t.id ? 'var(--vival-red)' : 'white',
              color: subTab === t.id ? 'white' : 'var(--ink-1)',
              fontSize: subTab === t.id ? 14 : 13, fontWeight: subTab === t.id ? 700 : 600, cursor:'pointer',
              boxShadow: subTab === t.id ? '0 2px 8px rgba(226, 0, 26, 0.25)' : 'none',
            }}>
            {t.label} <span style={{opacity:0.7, marginLeft:4}}>· {t.count}</span>
          </button>
        ))}
        <div style={{marginLeft:'auto', display:'flex', alignItems:'center', gap:8}}>
          <span className="muted" style={{fontSize:13}}>Période :</span>
          {[7, 30, 60, 90].map(d => (
            <button key={d} className="chip" aria-pressed={days === d} onClick={() => setDays(d)}>
              {d}j
            </button>
          ))}
        </div>
      </div>

      {loading && <div className="muted" style={{padding:40, textAlign:'center'}}>Chargement…</div>}

      {!loading && subTab === 'top' && (
        <div style={{padding:'0 32px 32px'}}>
          <div style={{marginBottom:12, fontSize:13, color:'var(--ink-2)'}}>
            Les <strong>{topRows.length} produits qui ont généré le plus de CA</strong> sur les <strong>{days} derniers jours</strong>.
          </div>
          <div className="card" style={{padding:0, overflow:'hidden'}}>
            <table style={{width:'100%', fontSize:13, borderCollapse:'collapse'}}>
              <thead><tr style={{background:'var(--surface-1)', textAlign:'left'}}>
                <th style={{padding:'10px 14px', width:40}}>#</th>
                <th style={{padding:'10px 14px'}}>Produit</th>
                <th style={{padding:'10px 14px'}}>Rayon</th>
                <th style={{padding:'10px 14px', textAlign:'right'}}>Qté</th>
                <th style={{padding:'10px 14px', textAlign:'right'}}>CA TTC</th>
                <th style={{padding:'10px 14px', textAlign:'right'}}>Marge</th>
                <th style={{padding:'10px 14px', textAlign:'right'}}>Ventes</th>
              </tr></thead>
              <tbody>
                {topRows.map((r, i) => (
                  <tr key={`${r.ean}-${i}`}
                      onClick={() => openProductEdit(r.ean, r.name)}
                      title="Cliquer pour modifier le produit"
                      style={{borderTop:'1px solid var(--line-2)', cursor:'pointer'}}
                      onMouseEnter={(e) => e.currentTarget.style.background = '#f9fafb'}
                      onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}>
                    <td style={{padding:'8px 14px', color:'var(--ink-3)', fontWeight:700}}>#{i+1}</td>
                    <td style={{padding:'8px 14px'}}>
                      <div style={{fontWeight:600}}>{r.name || '—'}</div>
                      {r.ean && <div className="muted" style={{fontSize:11, fontFamily:'var(--font-mono)'}}>EAN {r.ean}</div>}
                    </td>
                    <td style={{padding:'8px 14px', fontSize:12}}>{r.rayon}</td>
                    <td style={{padding:'8px 14px', textAlign:'right', fontVariantNumeric:'tabular-nums'}}>{Number(r.qty_total).toFixed(2).replace(/\.?0+$/, '')}</td>
                    <td style={{padding:'8px 14px', textAlign:'right', fontWeight:700}}>{fmt(r.ca_ttc_total)}</td>
                    <td style={{padding:'8px 14px', textAlign:'right', color: Number(r.marge_ht_total) > 0 ? '#16a34a' : 'var(--ink-3)'}}>{fmt(r.marge_ht_total)}</td>
                    <td style={{padding:'8px 14px', textAlign:'right', fontVariantNumeric:'tabular-nums'}}>{r.nb_ventes}</td>
                  </tr>
                ))}
                {topRows.length === 0 && <tr><td colSpan={7} style={{padding:32, textAlign:'center'}} className="muted">Aucune vente sur cette période.</td></tr>}
              </tbody>
            </table>
          </div>
        </div>
      )}

      {!loading && subTab === 'dormant' && (
        <div style={{padding:'0 32px 32px'}}>
          <div style={{display:'flex', gap:16, marginBottom:12, alignItems:'center', flexWrap:'wrap'}}>
            <div style={{fontSize:13, color:'var(--ink-2)', flex:1}}>
              Produits avec <strong>stock &gt; 0</strong> mais <strong>aucune vente</strong> sur les <strong>{Math.max(days, 60)} derniers jours</strong>.
            </div>
            <div style={{padding:'8px 16px', background:'#fef2f2', borderRadius:8, borderLeft:'4px solid #dc2626'}}>
              <div style={{fontSize:11, color:'#991b1b', fontWeight:600}}>Argent dormant</div>
              <div style={{fontSize:20, fontWeight:800, color:'#991b1b'}}>{fmt(totalDormantValue)}</div>
            </div>
          </div>
          <div className="card" style={{padding:0, overflow:'hidden'}}>
            <table style={{width:'100%', fontSize:13, borderCollapse:'collapse'}}>
              <thead><tr style={{background:'var(--surface-1)', textAlign:'left'}}>
                <th style={{padding:'10px 14px'}}>Produit</th>
                <th style={{padding:'10px 14px'}}>Rayon</th>
                <th style={{padding:'10px 14px', textAlign:'right'}}>Stock</th>
                <th style={{padding:'10px 14px', textAlign:'right'}}>PA</th>
                <th style={{padding:'10px 14px', textAlign:'right'}}>Val. dormante</th>
                <th style={{padding:'10px 14px'}}>Dernière vente</th>
              </tr></thead>
              <tbody>
                {dormantRows.map((r) => (
                  <tr key={r.id}
                      onClick={() => openProductEdit(r.ean, r.name)}
                      title="Cliquer pour modifier le produit"
                      style={{borderTop:'1px solid var(--line-2)', cursor:'pointer'}}
                      onMouseEnter={(e) => e.currentTarget.style.background = '#f9fafb'}
                      onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}>
                    <td style={{padding:'8px 14px'}}>
                      <div style={{fontWeight:600}}>{r.name || '—'}</div>
                      {r.ean && <div className="muted" style={{fontSize:11, fontFamily:'var(--font-mono)'}}>EAN {r.ean}</div>}
                    </td>
                    <td style={{padding:'8px 14px', fontSize:12}}>{r.rayon}</td>
                    <td style={{padding:'8px 14px', textAlign:'right', fontVariantNumeric:'tabular-nums', fontWeight:700}}>{r.stock}</td>
                    <td style={{padding:'8px 14px', textAlign:'right'}}>{fmt(r.purchase_price)}</td>
                    <td style={{padding:'8px 14px', textAlign:'right', color:'#991b1b', fontWeight:700}}>{fmt(r.valeur_dormante)}</td>
                    <td style={{padding:'8px 14px', fontSize:12, color:'var(--ink-3)'}}>
                      {r.last_sale_date ? new Date(r.last_sale_date).toLocaleDateString('fr-FR') : <em>Jamais vendu</em>}
                    </td>
                  </tr>
                ))}
                {dormantRows.length === 0 && <tr><td colSpan={6} style={{padding:32, textAlign:'center'}} className="muted">Aucun produit dormant. 🎉</td></tr>}
              </tbody>
            </table>
          </div>
        </div>
      )}

      {!loading && subTab === 'low_margin' && (
        <div style={{padding:'0 32px 32px'}}>
          <div style={{marginBottom:12, fontSize:13, color:'var(--ink-2)'}}>
            Produits vendus avec une <strong>marge &lt; 5%</strong> sur les <strong>{days} derniers jours</strong>.
          </div>
          <div className="card" style={{padding:0, overflow:'hidden'}}>
            <table style={{width:'100%', fontSize:13, borderCollapse:'collapse'}}>
              <thead><tr style={{background:'var(--surface-1)', textAlign:'left'}}>
                <th style={{padding:'10px 14px'}}>Produit</th>
                <th style={{padding:'10px 14px'}}>Rayon</th>
                <th style={{padding:'10px 14px', textAlign:'right'}}>Qté</th>
                <th style={{padding:'10px 14px', textAlign:'right'}}>CA TTC</th>
                <th style={{padding:'10px 14px', textAlign:'right'}}>Marge HT</th>
                <th style={{padding:'10px 14px', textAlign:'right'}}>Taux</th>
              </tr></thead>
              <tbody>
                {lowMarginRows.map((r, i) => (
                  <tr key={`${r.ean}-${i}`}
                      onClick={() => openProductEdit(r.ean, r.name)}
                      title="Cliquer pour modifier le produit"
                      style={{borderTop:'1px solid var(--line-2)', cursor:'pointer'}}
                      onMouseEnter={(e) => e.currentTarget.style.background = '#fef2f2'}
                      onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}>
                    <td style={{padding:'8px 14px'}}>
                      <div style={{fontWeight:600}}>{r.name || '—'}</div>
                      {r.ean && <div className="muted" style={{fontSize:11, fontFamily:'var(--font-mono)'}}>EAN {r.ean}</div>}
                    </td>
                    <td style={{padding:'8px 14px', fontSize:12}}>{r.rayon}</td>
                    <td style={{padding:'8px 14px', textAlign:'right', fontVariantNumeric:'tabular-nums'}}>{Number(r.qty_total).toFixed(2).replace(/\.?0+$/, '')}</td>
                    <td style={{padding:'8px 14px', textAlign:'right', fontWeight:600}}>{fmt(r.ca_ttc_total)}</td>
                    <td style={{padding:'8px 14px', textAlign:'right', color: Number(r.marge_ht_total) < 0 ? '#dc2626' : 'var(--ink-3)', fontWeight:700}}>{fmt(r.marge_ht_total)}</td>
                    <td style={{padding:'8px 14px', textAlign:'right', color: Number(r.taux_marge) < 0 ? '#dc2626' : '#d97706', fontWeight:700}}>{Number(r.taux_marge).toFixed(1).replace('.', ',')} %</td>
                  </tr>
                ))}
                {lowMarginRows.length === 0 && <tr><td colSpan={6} style={{padding:32, textAlign:'center'}} className="muted">Aucune marge basse détectée. 🎉</td></tr>}
              </tbody>
            </table>
          </div>
        </div>
      )}
    </>
  );
}

// =========== Tableau consolide : marge par rayon, TOUS les rayons ===========
function AllRayonsOverview({ yearMonth, onSelectRayon }) {
  const [rows, setRows] = useState(null);
  const [collapsed, setCollapsed] = useState(false);
  const [missingDetails, setMissingDetails] = useState(null); // { rayon_id, rayon_label, rows }
  const [reloadTick, setReloadTick] = useState(0);

  useEffect(() => {
    (async () => {
      const sid = window.VivalData?._storeId;
      if (!sid) return;
      try {
        const { data } = await window.sb.rpc('marge_mensuelle_tous_rayons', {
          p_store_id: sid, p_year_month: yearMonth,
        });
        setRows(data || []);
      } catch (e) { console.warn('marge_mensuelle_tous_rayons', e); setRows([]); }
    })();
  }, [yearMonth, reloadTick]);

  const openMissing = async (rayonId, rayonLabel) => {
    const sid = window.VivalData?._storeId;
    if (!sid) return;
    try {
      const { data } = await window.sb.rpc('lignes_sans_pa_rayon', {
        p_store_id: sid, p_rayon: rayonId, p_year_month: yearMonth,
      });
      setMissingDetails({ rayon_id: rayonId, rayon_label: rayonLabel, rows: data || [] });
    } catch (e) { console.warn('lignes_sans_pa_rayon', e); }
  };

  if (!rows) return null;
  if (rows.length === 0) return null;

  const totalCa = rows.reduce((s, r) => s + Number(r.ca_ht || 0), 0);
  const totalMarge = rows.reduce((s, r) => s + Number(r.marge_ht || 0), 0);
  const tauxGlobal = totalCa > 0 ? (totalMarge / totalCa) * 100 : 0;

  return (
    <div style={{padding:'0 32px 16px'}}>
      <div className="card" style={{padding:0, overflow:'hidden'}}>
        <div style={{padding:'12px 16px', background:'var(--surface-1)', borderBottom:'1px solid var(--line-1)', display:'flex', alignItems:'center', gap:12, cursor:'pointer'}}
             onClick={() => setCollapsed(c => !c)}>
          <div style={{flex:1}}>
            <div style={{fontWeight:800, fontSize:15}}>📊 Marge par rayon — {yearMonth}</div>
            <div style={{fontSize:12, color:'var(--ink-3)', marginTop:2}}>
              CA total HT : <strong>{totalCa.toFixed(2)} €</strong> · Marge : <strong style={{color: tauxGlobal >= 20 ? '#059669' : '#d97706'}}>{totalMarge.toFixed(2)} €</strong> ({tauxGlobal.toFixed(1).replace('.', ',')} %) · {rows.length} rayons actifs
            </div>
          </div>
          <span style={{fontSize:20, color:'var(--ink-3)'}}>{collapsed ? '▾' : '▴'}</span>
        </div>
        {!collapsed && (
          <div style={{overflowX:'auto'}}>
            <table style={{width:'100%', fontSize:13, borderCollapse:'collapse'}}>
              <thead>
                <tr style={{textAlign:'left', background:'var(--surface-2)', color:'var(--ink-3)'}}>
                  <th style={{padding:'10px'}}>Rayon</th>
                  <th style={{padding:'10px', textAlign:'right'}}>CA HT</th>
                  <th style={{padding:'10px', textAlign:'right'}}>Marge €</th>
                  <th style={{padding:'10px', textAlign:'right'}}>Taux %</th>
                  <th style={{padding:'10px', textAlign:'center'}}>Couv.</th>
                  <th style={{padding:'10px'}}>Mode</th>
                </tr>
              </thead>
              <tbody>
                {rows.map((r, i) => {
                  const taux = Number(r.taux_marge || 0);
                  const cov = Number(r.pct_couverture || 0);
                  const isManual = r.source === 'manual_tracker';
                  const canClick = isManual;
                  // Si le rayon manuel a un taux = 100% c est que les factures ne sont pas saisies
                  const unfilled = isManual && taux === 100 && Number(r.ca_ht) > 0;
                  return (
                    <tr key={r.rayon_id}
                        onClick={canClick ? () => onSelectRayon?.(r.rayon_id) : undefined}
                        style={{
                          borderTop:'1px solid var(--line-2)',
                          cursor: canClick ? 'pointer' : 'default',
                        }}>
                      <td style={{padding:'10px', fontWeight:600}}>
                        {r.rayon_label}
                        {unfilled && (
                          <span style={{marginLeft:6, padding:'2px 6px', background:'#fef3c7', color:'#92400e', borderRadius:4, fontSize:10, fontWeight:700}}>
                            ⚠ factures à saisir
                          </span>
                        )}
                      </td>
                      <td style={{padding:'10px', textAlign:'right', fontWeight:700}}>{Number(r.ca_ht || 0).toFixed(2)} €</td>
                      <td style={{padding:'10px', textAlign:'right', color: Number(r.marge_ht) >= 0 ? '#059669' : '#dc2626', fontWeight:700}}>
                        {Number(r.marge_ht || 0).toFixed(2)} €
                      </td>
                      <td style={{padding:'10px', textAlign:'right', fontWeight:600, color: taux >= 25 ? '#059669' : taux >= 15 ? '#d97706' : '#dc2626'}}>
                        {taux.toFixed(1).replace('.', ',')} %
                      </td>
                      <td style={{padding:'10px', textAlign:'center', fontSize:11}}>
                        {r.source === 'auto_bl' && cov < 100 ? (
                          <button
                            onClick={(e) => { e.stopPropagation(); openMissing(r.rayon_id, r.rayon_label); }}
                            style={{
                              background:'transparent', border:'1px solid var(--line-1)', borderRadius:6,
                              padding:'4px 8px', cursor:'pointer', fontSize:11, fontWeight:600,
                              color: cov >= 95 ? '#059669' : cov >= 70 ? '#d97706' : '#dc2626',
                            }}
                            title="Clique pour voir les lignes sans PA et corriger">
                            {cov >= 95 ? '✅' : cov >= 70 ? '🟡' : '🔴'} {cov.toFixed(0)} % →
                          </button>
                        ) : (
                          <span>{cov >= 95 ? '✅' : cov >= 70 ? '🟡' : '🔴'} {cov.toFixed(0)} %</span>
                        )}
                      </td>
                      <td style={{padding:'10px', fontSize:11}}>
                        {isManual ? (
                          <span style={{color:'#0891b2', fontWeight:600}} title="Geré manuellement via factures fournisseurs + stock + casse">
                            ✍️ Manuel {canClick && <span style={{color:'var(--ink-3)'}}>(clique)</span>}
                          </span>
                        ) : (
                          <span style={{color:'#059669', fontWeight:600}} title="Marge calculée automatiquement via les prix d'achat des BL Casino">
                            🤖 Auto (BL Casino)
                          </span>
                        )}
                      </td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
            <div style={{padding:'8px 16px', background:'#eff6ff', borderTop:'1px solid var(--line-1)', fontSize:11, color:'#1e3a8a', lineHeight:1.5}}>
              💡 <strong>Mode Auto</strong> : marge calculée à partir des BL Casino (prix débit) × ventes caisse — pas besoin de saisir quoi que ce soit.
              <strong> Mode Manuel</strong> : clique sur le rayon pour saisir tes factures fournisseurs, ton stock mensuel et ta casse (nécessaire pour les produits au poids).
              <br/>
              <strong>% Couverture</strong> : part des ventes du rayon avec un prix d'achat connu. Clique le pourcentage pour voir et corriger les lignes manquantes.
            </div>
          </div>
        )}
      </div>

      {missingDetails && (
        <MissingPaInlineModal
          details={missingDetails}
          onClose={() => setMissingDetails(null)}
          onUpdated={() => { setReloadTick(t => t + 1); }}
        />
      )}
    </div>
  );
}

// Modale : liste des lignes sans PA d un rayon, avec correction 1-clic
function MissingPaInlineModal({ details, onClose, onUpdated }) {
  const { showToast } = useApp();
  const [editingEan, setEditingEan] = useState(null);
  const [paInput, setPaInput] = useState('');
  const [saving, setSaving] = useState(false);

  const { rayon_label, rows } = details;
  const totalCa = rows.reduce((s, r) => s + Number(r.ca_ttc_impacte || 0), 0);
  const byCategorie = rows.reduce((acc, r) => {
    acc[r.categorie] = acc[r.categorie] || { count: 0, ca_ttc: 0 };
    acc[r.categorie].count++;
    acc[r.categorie].ca_ttc += Number(r.ca_ttc_impacte || 0);
    return acc;
  }, {});

  const startEdit = (row) => {
    setEditingEan(row.ean);
    setPaInput(row.suggestion_pa_ht != null ? String(row.suggestion_pa_ht) : '');
  };

  const saveEdit = async (row) => {
    const pa = Number(String(paInput).replace(',', '.'));
    if (!(pa >= 0)) { showToast('Prix invalide'); return; }
    setSaving(true);
    const sid = window.VivalData?._storeId;
    let data, error;
    if (row.categorie === 'ligne_taxe_caisse') {
      // Ciblage precis (ean + designation + rayon) pour ne pas impacter les autres rayons
      const res = await window.sb.rpc('set_tax_line_purchase_price', {
        p_store_id: sid,
        p_ean: row.ean,
        p_designation: row.designation,
        p_rayon: details.rayon_id,
        p_purchase_price_ht: pa,
      });
      data = res.data; error = res.error;
    } else {
      const res = await window.sb.rpc('set_product_purchase_price', {
        p_store_id: sid, p_ean: row.ean, p_purchase_price_ht: pa,
      });
      data = res.data; error = res.error;
    }
    setSaving(false);
    if (error) { showToast('Erreur : ' + error.message); return; }
    const r = data?.[0];
    showToast(`✓ PA enregistré (${r?.sales_lines_updated || 0} ventes recalculées)`);
    setEditingEan(null);
    setPaInput('');
    onUpdated?.();
  };

  const catLabels = {
    ligne_taxe_caisse: '⌨️ Vente saisie au clavier',
    produit_connu_sans_pa: '⚠ Catalogue sans PA',
    ean_alternatif_existant: '🔗 EAN secondaire',
    produit_inconnu: '❓ Hors catalogue',
  };
  const catColors = {
    ligne_taxe_caisse: { bg: '#fef3c7', color: '#92400e' },  // jaune = a traiter, pas gris
    produit_connu_sans_pa: { bg: '#fed7aa', color: '#9a3412' },
    ean_alternatif_existant: { bg: '#dbeafe', color: '#1e40af' },
    produit_inconnu: { bg: '#fee2e2', color: '#991b1b' },
  };

  return (
    <div style={{position:'fixed', inset:0, background:'rgba(0,0,0,0.5)', display:'flex', alignItems:'center', justifyContent:'center', zIndex:200, padding:16}}
         onClick={() => !saving && onClose()}>
      <div className="card" style={{padding:0, maxWidth:900, width:'100%', maxHeight:'90vh', overflow:'hidden', display:'flex', flexDirection:'column'}}
           onClick={e => e.stopPropagation()}>
        {/* Header */}
        <div style={{padding:'16px 20px', borderBottom:'1px solid var(--line-1)', background:'var(--surface-1)'}}>
          <div style={{display:'flex', alignItems:'center', gap:12}}>
            <div style={{flex:1}}>
              <div style={{fontSize:16, fontWeight:800}}>Lignes sans PA — {rayon_label}</div>
              <div style={{fontSize:12, color:'var(--ink-3)', marginTop:2}}>
                {rows.length} ligne{rows.length > 1 ? 's' : ''} · {totalCa.toFixed(2)} € CA TTC impacté
              </div>
            </div>
            <button onClick={onClose}
              style={{background:'transparent', border:0, fontSize:24, cursor:'pointer', color:'var(--ink-3)', padding:'0 4px'}}>×</button>
          </div>
          {/* Mini-stats par catégorie */}
          <div style={{display:'flex', gap:8, marginTop:10, flexWrap:'wrap'}}>
            {Object.entries(byCategorie).map(([k, v]) => (
              <span key={k} style={{
                padding:'4px 10px',
                borderRadius:14,
                fontSize:11, fontWeight:600,
                background: catColors[k]?.bg || '#f3f4f6',
                color: catColors[k]?.color || '#6b7280',
              }}>
                {catLabels[k] || k} · {v.count} · {v.ca_ttc.toFixed(2)} €
              </span>
            ))}
          </div>
        </div>

        {/* Tableau */}
        <div style={{overflowY:'auto', flex:1}}>
          <table style={{width:'100%', fontSize:13, borderCollapse:'collapse'}}>
            <thead style={{position:'sticky', top:0, background:'white', zIndex:1}}>
              <tr style={{textAlign:'left', borderBottom:'1px solid var(--line-1)', color:'var(--ink-3)'}}>
                <th style={{padding:'10px', width:'50%'}}>Produit</th>
                <th style={{padding:'10px', textAlign:'right'}}>CA TTC</th>
                <th style={{padding:'10px', textAlign:'right'}}>PV moy.</th>
                <th style={{padding:'10px', width:240}}></th>
              </tr>
            </thead>
            <tbody>
              {rows.map((r, i) => {
                const cat = r.categorie;
                const isEditing = editingEan === r.ean;
                return (
                  <tr key={(r.ean || 'no-ean') + '-' + i} style={{borderTop:'1px solid var(--line-2)'}}>
                    <td style={{padding:'10px'}}>
                      <div style={{fontWeight:600, fontSize:13}}>{r.designation}</div>
                      <div style={{fontSize:10, color:'var(--ink-3)', fontFamily:'var(--font-mono)', marginTop:2}}>
                        EAN {r.ean || '—'} · {r.nb_occurrences} vente{r.nb_occurrences > 1 ? 's' : ''}
                      </div>
                      <div style={{fontSize:11, color:'var(--ink-2)', marginTop:4, lineHeight:1.4, fontStyle:'italic'}}>
                        {r.raison}
                      </div>
                    </td>
                    <td style={{padding:'10px', textAlign:'right', fontWeight:700}}>
                      {Number(r.ca_ttc_impacte || 0).toFixed(2)} €
                    </td>
                    <td style={{padding:'10px', textAlign:'right', color:'var(--ink-3)'}}>
                      {r.prix_vente_moyen_ttc ? Number(r.prix_vente_moyen_ttc).toFixed(2) + ' €' : '—'}
                    </td>
                    <td style={{padding:'10px', textAlign:'right'}}>
                      {isEditing ? (
                        <div style={{display:'flex', gap:4, alignItems:'center', justifyContent:'flex-end'}}>
                          <Input
                            value={paInput}
                            onChange={e => setPaInput(e.target.value)}
                            placeholder="PA HT"
                            style={{width:80, fontSize:13}}
                            autoFocus
                            onKeyDown={e => { if (e.key === 'Enter') saveEdit(r); if (e.key === 'Escape') setEditingEan(null); }}
                          />
                          <Button variant="primary" size="sm" icon="check" onClick={() => saveEdit(r)} disabled={saving}>
                            {saving ? '…' : 'OK'}
                          </Button>
                          <Button variant="ghost" size="sm" onClick={() => { setEditingEan(null); setPaInput(''); }}>×</Button>
                        </div>
                      ) : (
                        <Button variant="outline" size="sm" onClick={() => startEdit(r)}>
                          {cat === 'ligne_taxe_caisse' ? 'PA moyen rayon' : 'Renseigner PA'}
                          {r.suggestion_pa_ht ? ` (${Number(r.suggestion_pa_ht).toFixed(2)} €)` : ''}
                        </Button>
                      )}
                    </td>
                  </tr>
                );
              })}
              {rows.length === 0 && (
                <tr><td colSpan={4} style={{padding:30, textAlign:'center', color:'var(--ink-3)'}}>✅ Toutes les lignes ont un PA !</td></tr>
              )}
            </tbody>
          </table>
        </div>

        {/* Footer explicatif */}
        <div style={{padding:'10px 20px', background:'#eff6ff', borderTop:'1px solid var(--line-1)', fontSize:11, color:'#1e3a8a', lineHeight:1.5}}>
          💡 <strong>Renseigner un PA produit</strong> (EAN réel) : met à jour le catalogue ET recalcule toutes les ventes passées du même EAN.<br/>
          💡 <strong>PA moyen rayon</strong> (ventes saisies au clavier sans code-barres) : le gérant estime un PA moyen HT qui s'applique UNIQUEMENT à ces lignes de ce rayon. Exemple : "LIQUIDES TX NORMAL" à 20,70 € TTC = 3 bouteilles vendues sans scan → tu saisis un PA moyen 4,50 € HT et la marge du rayon Boissons sera juste.
        </div>
      </div>
    </div>
  );
}

function RayonMarginTracker() {
  const app = useApp();
  const today = new Date();
  const [rayonId, setRayonId] = useState('fruits-et-legumes');
  const [yearMonth, setYearMonth] = useState(`${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}`);
  const [kpi, setKpi] = useState(null);
  const [invoices, setInvoices] = useState([]);
  const [stocks, setStocks] = useState([]);
  const [casse, setCasse] = useState([]);
  const [reloadKey, setReloadKey] = useState(0);

  const [newInvDate, setNewInvDate] = useState(() => toDateInput(new Date()));
  const [newInvSupplier, setNewInvSupplier] = useState('Casino');
  const [suppliers, setSuppliers] = useState([]);
  const [newInvAmount, setNewInvAmount] = useState('');
  const [newStockDate, setNewStockDate] = useState(() => toDateInput(new Date()));
  const [newStockValue, setNewStockValue] = useState('');
  const [newCasseDate, setNewCasseDate] = useState(() => toDateInput(new Date()));
  const [newCasseAmount, setNewCasseAmount] = useState('');
  const [caHt, setCaHt] = useState('');
  const [savingCa, setSavingCa] = useState(false);
  const [subTab, setSubTab] = useState('invoices'); // invoices | stocks | casse

  const storeId = window.VivalData?._storeId;
  const firstDay = `${yearMonth}-01`;
  const lastDay = (() => {
    const d = new Date(firstDay + 'T00:00:00');
    d.setMonth(d.getMonth() + 1); d.setDate(0);
    return toDateInput(d);
  })();

  const rayonLabel = MARGIN_RAYONS.find(r => r.id === rayonId)?.label || rayonId;

  const load = async () => {
    if (!storeId) return;
    try {
      const [k, inv, st, ca] = await Promise.all([
        window.sb.rpc('fl_marge_mensuelle', { p_store_id: storeId, p_year_month: yearMonth, p_rayon_id: rayonId }),
        window.sb.from('fl_invoices').select('*').eq('store_id', storeId).eq('rayon_id', rayonId).gte('date', firstDay).lte('date', lastDay).order('date', { ascending: false }),
        window.sb.from('fl_stocks').select('*').eq('store_id', storeId).eq('rayon_id', rayonId).order('date', { ascending: false }).limit(20),
        window.sb.from('fl_casse').select('*').eq('store_id', storeId).eq('rayon_id', rayonId).gte('date', firstDay).lte('date', lastDay).order('date', { ascending: false }),
      ]);
      setKpi((k.data || [])[0] || null);
      setInvoices(inv.data || []);
      setStocks(st.data || []);
      setCasse(ca.data || []);
      // Pré-remplit le champ seulement si manuel ; en auto on laisse vide pour que le placeholder indique le chiffre auto
      const k0 = (k.data || [])[0];
      setCaHt(k0?.ca_ht_source === 'manual' ? (k0.ca_ht || '') : '');
    } catch (e) { console.error('tracker load', e); }
  };
  useEffect(() => { load(); }, [yearMonth, rayonId, reloadKey]);

  // Charge la liste des fournisseurs dispo pour ce magasin
  const loadSuppliers = async () => {
    if (!storeId) return;
    const { data } = await window.sb.from('fl_suppliers')
      .select('id, name, is_default')
      .eq('store_id', storeId)
      .order('name');
    setSuppliers(data || []);
    // Si le fournisseur courant n'existe pas, on prend le 1er
    if (data && data.length > 0 && !data.find(s => s.name === newInvSupplier)) {
      setNewInvSupplier(data[0].name);
    }
  };
  useEffect(() => { loadSuppliers(); }, [storeId, reloadKey]);

  const addSupplier = async () => {
    const name = prompt('Nom du nouveau fournisseur :');
    if (!name || !name.trim()) return;
    const trimmed = name.trim();
    try {
      const { error } = await window.sb.from('fl_suppliers').insert({
        store_id: storeId, name: trimmed, is_default: false,
      });
      if (error) throw error;
      await loadSuppliers();
      setNewInvSupplier(trimmed);
      app.showToast && app.showToast(`Fournisseur « ${trimmed} » ajouté`);
    } catch (e) {
      if (String(e.message || '').includes('duplicate')) {
        alert('Ce fournisseur existe déjà.');
      } else {
        alert('Erreur : ' + (e.message || e));
      }
    }
  };

  const delSupplier = async (id, name) => {
    if (!confirm(`Supprimer le fournisseur « ${name} » de la liste ?\n\nLes factures déjà saisies avec ce fournisseur sont conservées.`)) return;
    try {
      await window.sb.from('fl_suppliers').delete().eq('id', id);
      await loadSuppliers();
    } catch (e) { alert('Erreur : ' + (e.message || e)); }
  };

  const addInvoice = async () => {
    const amt = parseFloat(newInvAmount.replace(',', '.'));
    if (!newInvDate || !amt || amt <= 0) { alert('Date et montant obligatoires.'); return; }
    try {
      const { error } = await window.sb.from('fl_invoices').insert({
        store_id: storeId, rayon_id: rayonId, date: newInvDate, supplier: newInvSupplier, amount_ht: amt,
      });
      if (error) throw error;
      setNewInvAmount('');
      setReloadKey(k => k + 1);
    } catch (e) { alert('Erreur : ' + (e.message || e)); }
  };

  const delInvoice = async (id) => {
    if (!confirm('Supprimer cette facture ?')) return;
    await window.sb.from('fl_invoices').delete().eq('id', id);
    setReloadKey(k => k + 1);
  };

  const addStock = async () => {
    const v = parseFloat(newStockValue.replace(',', '.'));
    if (!newStockDate || isNaN(v)) { alert('Date et valeur obligatoires.'); return; }
    try {
      const { error } = await window.sb.from('fl_stocks').upsert({
        store_id: storeId, rayon_id: rayonId, date: newStockDate, value_ht: v,
      }, { onConflict: 'store_id,rayon_id,date' });
      if (error) throw error;
      setNewStockValue('');
      setReloadKey(k => k + 1);
    } catch (e) { alert('Erreur : ' + (e.message || e)); }
  };

  const delStock = async (id) => {
    if (!confirm('Supprimer ce relevé de stock ?')) return;
    await window.sb.from('fl_stocks').delete().eq('id', id);
    setReloadKey(k => k + 1);
  };

  const addCasse = async () => {
    const amt = parseFloat(newCasseAmount.replace(',', '.'));
    if (!newCasseDate || !amt || amt <= 0) { alert('Date et montant obligatoires.'); return; }
    try {
      const { error } = await window.sb.from('fl_casse').insert({
        store_id: storeId, rayon_id: rayonId, date: newCasseDate, amount: amt,
      });
      if (error) throw error;
      setNewCasseAmount('');
      setReloadKey(k => k + 1);
    } catch (e) { alert('Erreur : ' + (e.message || e)); }
  };

  const delCasse = async (id) => {
    if (!confirm('Supprimer cette ligne de casse ?')) return;
    await window.sb.from('fl_casse').delete().eq('id', id);
    setReloadKey(k => k + 1);
  };

  const saveCa = async () => {
    const v = parseFloat(String(caHt).replace(',', '.'));
    if (isNaN(v)) { alert('CA HT invalide.'); return; }
    setSavingCa(true);
    try {
      const { error } = await window.sb.from('fl_ca_monthly').upsert({
        store_id: storeId, rayon_id: rayonId, year_month: yearMonth, ca_ht: v,
      }, { onConflict: 'store_id,rayon_id,year_month' });
      if (error) throw error;
      setReloadKey(k => k + 1);
      app.showToast && app.showToast('CA HT enregistré (override manuel)');
    } catch (e) { alert('Erreur : ' + (e.message || e)); }
    finally { setSavingCa(false); }
  };

  const resetCa = async () => {
    if (!confirm('Revenir au calcul automatique depuis les imports caisse ?')) return;
    setSavingCa(true);
    try {
      await window.sb.from('fl_ca_monthly').delete()
        .eq('store_id', storeId).eq('rayon_id', rayonId).eq('year_month', yearMonth);
      setCaHt('');
      setReloadKey(k => k + 1);
      app.showToast && app.showToast('CA HT remis en auto');
    } catch (e) { alert('Erreur : ' + (e.message || e)); }
    finally { setSavingCa(false); }
  };

  const fmt = (v) => {
    if (v === null || v === undefined) return '—';
    const n = Number(v);
    return n.toLocaleString('fr-FR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + ' €';
  };
  const splitEuros = (v) => {
    if (v === null || v === undefined) return { euros: '—', cents: '' };
    const n = Number(v);
    const [e, c] = n.toFixed(2).split('.');
    return { euros: Number(e).toLocaleString('fr-FR'), cents: c };
  };

  const monthOptions = (() => {
    const opts = [];
    const d = new Date();
    for (let i = 0; i < 13; i++) {
      const ym = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`;
      const lbl = d.toLocaleDateString('fr-FR', { month: 'long', year: 'numeric' });
      opts.push({ id: ym, label: lbl.charAt(0).toUpperCase() + lbl.slice(1) });
      d.setMonth(d.getMonth() - 1);
    }
    return opts;
  })();

  const caParts = splitEuros(kpi?.ca_ht);
  const achatsParts = splitEuros(kpi?.achats_ht);
  const margeParts = splitEuros(kpi?.marge_brute);
  const taux = kpi?.taux_marge != null ? Number(kpi.taux_marge) : null;
  const tauxColor = taux != null ? (taux >= 25 ? 'var(--success)' : taux >= 15 ? '#d97706' : 'var(--danger)') : 'var(--ink-3)';

  const trackerRef = useRef(null);
  return (
    <>
      {/* Tableau consolidé de TOUS les rayons */}
      <AllRayonsOverview yearMonth={yearMonth} onSelectRayon={(rid) => {
        if (MARGIN_RAYONS.find(r => r.id === rid)) {
          setRayonId(rid);
          // Scroll automatique vers le tracker detaille apres le changement de rayon
          setTimeout(() => {
            trackerRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });
          }, 100);
        }
      }} />

      {/* Sélecteur de rayon : chips avec style "actif" très visible */}
      <div ref={trackerRef} style={{padding:'0 32px 12px', display:'flex', gap:8, alignItems:'center', flexWrap:'wrap', scrollMarginTop:16}}>
        {MARGIN_RAYONS.map(r => {
          const active = rayonId === r.id;
          return (
            <button key={r.id} onClick={() => setRayonId(r.id)}
              style={{
                padding: active ? '10px 18px' : '8px 16px',
                borderRadius: 999,
                border: '2px solid ' + (active ? 'var(--vival-red)' : 'var(--line-1)'),
                background: active ? 'var(--vival-red)' : 'white',
                color: active ? 'white' : 'var(--ink-1)',
                fontSize: active ? 14 : 13,
                fontWeight: active ? 700 : 600,
                cursor: 'pointer',
                transition: 'all 0.15s ease',
                boxShadow: active ? '0 2px 8px rgba(226, 0, 26, 0.25)' : 'none',
              }}>
              {r.label}
            </button>
          );
        })}
        <div style={{marginLeft:'auto', display:'flex', alignItems:'center', gap:8}}>
          <span className="muted" style={{fontSize:13}}>Mois :</span>
          <select value={yearMonth} onChange={e => setYearMonth(e.target.value)}
            style={{padding:'6px 10px', border:'1px solid var(--line-1)', borderRadius:8, fontSize:14}}>
            {monthOptions.map(o => <option key={o.id} value={o.id}>{o.label}</option>)}
          </select>
        </div>
      </div>

      {/* Bandeau titre du rayon courant */}
      <div style={{
        padding:'16px 24px',
        margin:'0 32px 20px',
        background:'linear-gradient(135deg, #fef2f2, #fff)',
        borderRadius:12,
        borderLeft:'4px solid var(--vival-red)',
      }}>
        <div style={{fontSize:11, fontWeight:600, color:'var(--ink-3)', textTransform:'uppercase', letterSpacing:'0.05em'}}>Rayon sélectionné</div>
        <div style={{fontSize:22, fontWeight:800, color:'var(--ink-1)', marginTop:2}}>{rayonLabel}</div>
        <div style={{fontSize:12, color:'var(--ink-3)', marginTop:2}}>
          {monthOptions.find(o => o.id === yearMonth)?.label || yearMonth}
        </div>
      </div>

      {/* KPI — style identique à l'onglet Par période */}
      <div className="kpi-grid">
        <div className="kpi">
          <div className="kpi-icon"><Icon name="euro" /></div>
          <div className="kpi-label" style={{display:'flex', alignItems:'center', gap:6}}>
            CA HT
            {kpi?.ca_ht_source === 'auto' && (
              <span style={{fontSize:9, padding:'2px 6px', borderRadius:4, background:'#dcfce7', color:'#166534', fontWeight:700}}>AUTO CAISSE</span>
            )}
            {kpi?.ca_ht_source === 'manual' && (
              <span style={{fontSize:9, padding:'2px 6px', borderRadius:4, background:'#fef3c7', color:'#92400e', fontWeight:700}}>MANUEL</span>
            )}
            {kpi?.ca_ht_source === 'none' && (
              <span style={{fontSize:9, padding:'2px 6px', borderRadius:4, background:'#e5e7eb', color:'#6b7280', fontWeight:700}}>VIDE</span>
            )}
          </div>
          <div className="kpi-value">{caParts.euros}<span className="unit">,{caParts.cents} €</span></div>
          {kpi?.ca_ht_source === 'auto' && Number(kpi?.ca_ht_auto) > 0 && (
            <div className="kpi-delta" style={{fontSize:10, color:'#166534'}}>
              Calculé depuis {kpi.nb_factures !== undefined ? '' : ''}imports caisse du mois
            </div>
          )}
          {kpi?.ca_ht_source === 'manual' && Number(kpi?.ca_ht_auto) > 0 && (
            <div className="kpi-delta" style={{fontSize:10}}>
              Auto caisse : {Number(kpi.ca_ht_auto).toFixed(2).replace('.', ',')} €
            </div>
          )}
          <div style={{marginTop:8, display:'flex', gap:6}}>
            <input type="text" inputMode="decimal" value={caHt} onChange={e => setCaHt(e.target.value)}
              placeholder={kpi?.ca_ht_source === 'auto' ? 'Forcer un CA HT (sinon auto)' : 'Saisir CA HT'}
              onKeyDown={e => { if (e.key === 'Enter') saveCa(); }}
              style={{flex:1, padding:'6px 8px', fontSize:12, border:'1px solid var(--line-1)', borderRadius:6}} />
            <button onClick={saveCa} disabled={savingCa || !caHt}
              style={{padding:'6px 10px', fontSize:11, fontWeight:700, background:'var(--vival-red)', color:'white', border:0, borderRadius:6, cursor: (savingCa || !caHt) ? 'not-allowed' : 'pointer', opacity: (savingCa || !caHt) ? 0.5 : 1}}>
              {savingCa ? '…' : 'OK'}
            </button>
          </div>
          {kpi?.ca_ht_source === 'manual' && (
            <button onClick={resetCa} disabled={savingCa}
              style={{marginTop:4, padding:'3px 8px', fontSize:10, background:'transparent', color:'var(--ink-3)', border:'1px solid var(--line-1)', borderRadius:4, cursor:'pointer', width:'100%'}}>
              ↺ Revenir au calcul auto
            </button>
          )}
        </div>

        <div className="kpi">
          <div className="kpi-icon"><Icon name="download" /></div>
          <div className="kpi-label">Achats HT</div>
          <div className="kpi-value">{achatsParts.euros}<span className="unit">,{achatsParts.cents} €</span></div>
          <div className="kpi-delta">{kpi?.nb_factures || 0} facture{(kpi?.nb_factures || 0) > 1 ? 's' : ''}</div>
        </div>

        <div className="kpi">
          <div className="kpi-icon"><Icon name="list" /></div>
          <div className="kpi-label">Stock début → fin</div>
          <div style={{fontSize:15, fontWeight:700, marginTop:6}}>
            {fmt(kpi?.stock_debut)} <span className="muted">→</span> {fmt(kpi?.stock_fin)}
          </div>
          <div className="kpi-delta">Coût vente : {fmt(kpi?.cout_vente)}</div>
        </div>

        <div className="kpi">
          <div className="kpi-icon"><Icon name="trash" /></div>
          <div className="kpi-label">Casse</div>
          <div className="kpi-value" style={{color: (kpi?.casse || 0) > 0 ? 'var(--danger)' : 'var(--ink-1)'}}>
            {splitEuros(kpi?.casse).euros}<span className="unit">,{splitEuros(kpi?.casse).cents} €</span>
          </div>
        </div>

        <div className="kpi" style={{gridColumn:'span 2', border:'2px solid ' + tauxColor}}>
          <div className="kpi-icon"><Icon name="tag" /></div>
          <div className="kpi-label">Marge brute · taux</div>
          <div style={{display:'flex', gap:16, alignItems:'baseline'}}>
            <div className="kpi-value">{margeParts.euros}<span className="unit">,{margeParts.cents} €</span></div>
            <div style={{fontSize:24, fontWeight:800, color: tauxColor}}>
              {taux != null ? taux.toFixed(1).replace('.', ',') + ' %' : '—'}
            </div>
          </div>
          <div className="kpi-delta">CA HT − (stock début + achats − stock fin) − casse</div>
        </div>
      </div>

      {/* Onglets internes : Factures / Stock / Casse */}
      <div style={{padding:'0 32px 12px', display:'flex', gap:6, borderBottom:'1px solid var(--line-1)', marginBottom:16}}>
        <button onClick={() => setSubTab('invoices')}
          style={{
            padding:'10px 16px', border:0, background:'transparent', cursor:'pointer',
            fontSize:14, fontWeight:700,
            borderBottom: subTab === 'invoices' ? '3px solid var(--vival-red)' : '3px solid transparent',
            color: subTab === 'invoices' ? 'var(--ink-1)' : 'var(--ink-3)',
            marginBottom:-1,
          }}>Factures {invoices.length > 0 && <span style={{opacity:0.7, fontSize:12}}>· {invoices.length}</span>}</button>
        <button onClick={() => setSubTab('stocks')}
          style={{
            padding:'10px 16px', border:0, background:'transparent', cursor:'pointer',
            fontSize:14, fontWeight:700,
            borderBottom: subTab === 'stocks' ? '3px solid var(--vival-red)' : '3px solid transparent',
            color: subTab === 'stocks' ? 'var(--ink-1)' : 'var(--ink-3)',
            marginBottom:-1,
          }}>Relevés de stock {stocks.length > 0 && <span style={{opacity:0.7, fontSize:12}}>· {stocks.length}</span>}</button>
        <button onClick={() => setSubTab('casse')}
          style={{
            padding:'10px 16px', border:0, background:'transparent', cursor:'pointer',
            fontSize:14, fontWeight:700,
            borderBottom: subTab === 'casse' ? '3px solid var(--vival-red)' : '3px solid transparent',
            color: subTab === 'casse' ? 'var(--ink-1)' : 'var(--ink-3)',
            marginBottom:-1,
          }}>Casse {casse.length > 0 && <span style={{opacity:0.7, fontSize:12}}>· {casse.length}</span>}</button>
      </div>

      {/* Contenu de l'onglet actif */}
      <div style={{padding:'0 32px 32px'}}>
        {subTab === 'invoices' && (
          <>
            {/* Formulaire d'ajout */}
            <div className="card" style={{padding:16, marginBottom:16}}>
              <h3 style={{margin:'0 0 12px', fontSize:14, fontWeight:700}}>Nouvelle facture fournisseur</h3>
              <div style={{display:'grid', gridTemplateColumns:'repeat(auto-fit, minmax(200px, 1fr))', gap:10, alignItems:'end'}}>
                <label style={{display:'flex', flexDirection:'column', gap:4, fontSize:12, fontWeight:600}}>Date
                  <input type="date" value={newInvDate} onChange={e => setNewInvDate(e.target.value)}
                    style={{padding:'10px 12px', border:'1px solid var(--line-1)', borderRadius:8, fontSize:14}} />
                </label>
                <div style={{display:'flex', flexDirection:'column', gap:4, fontSize:12, fontWeight:600}}>
                  <label>Fournisseur</label>
                  <div style={{display:'flex', gap:6, alignItems:'stretch'}}>
                    <select value={newInvSupplier} onChange={e => setNewInvSupplier(e.target.value)}
                      style={{flex:1, padding:'10px 12px', border:'1px solid var(--line-1)', borderRadius:8, fontSize:14, height:42, boxSizing:'border-box'}}>
                      {suppliers.map(s => <option key={s.id} value={s.name}>{s.name}</option>)}
                      {suppliers.length === 0 && <option value="">— Aucun —</option>}
                    </select>
                    <button type="button" onClick={addSupplier} title="Ajouter un fournisseur"
                      style={{width:42, height:42, padding:0, background:'var(--vival-red)', color:'white', border:0, borderRadius:8, fontSize:22, fontWeight:700, cursor:'pointer', display:'flex', alignItems:'center', justifyContent:'center', flexShrink:0, boxSizing:'border-box', lineHeight:1}}>
                      +
                    </button>
                  </div>
                  {suppliers.filter(s => !s.is_default).length > 0 && (
                    <details style={{marginTop:4, fontSize:10, color:'var(--ink-3)', fontWeight:400}}>
                      <summary style={{cursor:'pointer'}}>Gérer la liste ({suppliers.length})</summary>
                      <div style={{marginTop:4, display:'flex', flexDirection:'column', gap:2}}>
                        {suppliers.filter(s => !s.is_default).map(s => (
                          <div key={s.id} style={{display:'flex', justifyContent:'space-between', alignItems:'center', padding:'2px 4px'}}>
                            <span>{s.name}</span>
                            <button type="button" onClick={() => delSupplier(s.id, s.name)}
                              style={{background:'transparent', border:0, cursor:'pointer', color:'var(--danger)', fontSize:12}}>×</button>
                          </div>
                        ))}
                      </div>
                    </details>
                  )}
                </div>
                <label style={{display:'flex', flexDirection:'column', gap:4, fontSize:12, fontWeight:600}}>Montant HT
                  <input type="text" inputMode="decimal" value={newInvAmount}
                    onChange={e => setNewInvAmount(e.target.value)}
                    placeholder="Ex. 160.17"
                    onKeyDown={e => { if (e.key === 'Enter') addInvoice(); }}
                    style={{padding:'10px 12px', border:'1px solid var(--line-1)', borderRadius:8, fontSize:14}} />
                </label>
                <Button variant="primary" icon="plus" onClick={addInvoice}>Ajouter facture</Button>
              </div>
            </div>

            {/* Liste */}
            <div className="card" style={{padding:0, overflow:'hidden'}}>
              <div style={{padding:'12px 16px', borderBottom:'1px solid var(--line-1)', fontSize:13, fontWeight:700}}>
                Factures du mois <span className="muted" style={{fontWeight:400}}>· {invoices.length} · Total {fmt(invoices.reduce((s,i) => s + Number(i.amount_ht||0), 0))}</span>
              </div>
              <div style={{overflowX:'auto'}}>
                <table style={{width:'100%', fontSize:14, borderCollapse:'collapse'}}>
                  <thead><tr style={{background:'var(--surface-1)', textAlign:'left'}}>
                    <th style={{padding:'10px 14px'}}>Date</th>
                    <th style={{padding:'10px 14px'}}>Fournisseur</th>
                    <th style={{padding:'10px 14px', textAlign:'right'}}>Montant HT</th>
                    <th style={{padding:'10px 14px', width:60}}></th>
                  </tr></thead>
                  <tbody>
                    {invoices.map(i => (
                      <tr key={i.id} style={{borderTop:'1px solid var(--line-2)'}}>
                        <td style={{padding:'10px 14px'}}>{new Date(i.date).toLocaleDateString('fr-FR')}</td>
                        <td style={{padding:'10px 14px', textTransform:'capitalize'}}>{i.supplier}</td>
                        <td style={{padding:'10px 14px', textAlign:'right', fontVariantNumeric:'tabular-nums', fontWeight:600}}>{fmt(i.amount_ht)}</td>
                        <td style={{padding:'6px 14px', textAlign:'right'}}>
                          <button onClick={() => delInvoice(i.id)} title="Supprimer"
                            style={{background:'transparent', border:0, cursor:'pointer', color:'var(--danger)', fontSize:18, padding:6}}>×</button>
                        </td>
                      </tr>
                    ))}
                    {invoices.length === 0 && <tr><td colSpan={4} style={{padding:32, textAlign:'center'}} className="muted">Aucune facture pour ce mois.</td></tr>}
                  </tbody>
                </table>
              </div>
            </div>
          </>
        )}

        {subTab === 'stocks' && (
          <>
            <div className="card" style={{padding:16, marginBottom:16}}>
              <h3 style={{margin:'0 0 12px', fontSize:14, fontWeight:700}}>Relevé de stock</h3>
              <div style={{display:'grid', gridTemplateColumns:'repeat(auto-fit, minmax(200px, 1fr))', gap:10, alignItems:'end'}}>
                <label style={{display:'flex', flexDirection:'column', gap:4, fontSize:12, fontWeight:600}}>Date
                  <input type="date" value={newStockDate} onChange={e => setNewStockDate(e.target.value)}
                    style={{padding:'10px 12px', border:'1px solid var(--line-1)', borderRadius:8, fontSize:14}} />
                </label>
                <label style={{display:'flex', flexDirection:'column', gap:4, fontSize:12, fontWeight:600}}>Valeur stock HT
                  <input type="text" inputMode="decimal" value={newStockValue}
                    onChange={e => setNewStockValue(e.target.value)}
                    placeholder="Ex. 621.00"
                    onKeyDown={e => { if (e.key === 'Enter') addStock(); }}
                    style={{padding:'10px 12px', border:'1px solid var(--line-1)', borderRadius:8, fontSize:14}} />
                </label>
                <Button variant="primary" icon="plus" onClick={addStock}>Enregistrer stock</Button>
              </div>
              <div className="muted" style={{fontSize:11, marginTop:8}}>
                Astuce : saisis au moins 1 relevé en début de mois et 1 en fin de mois pour un calcul précis.
              </div>
            </div>

            <div className="card" style={{padding:0, overflow:'hidden'}}>
              <div style={{padding:'12px 16px', borderBottom:'1px solid var(--line-1)', fontSize:13, fontWeight:700}}>Derniers relevés (tous mois confondus)</div>
              <div style={{overflowX:'auto'}}>
                <table style={{width:'100%', fontSize:14, borderCollapse:'collapse'}}>
                  <thead><tr style={{background:'var(--surface-1)', textAlign:'left'}}>
                    <th style={{padding:'10px 14px'}}>Date</th>
                    <th style={{padding:'10px 14px', textAlign:'right'}}>Valeur HT</th>
                    <th style={{padding:'10px 14px', width:60}}></th>
                  </tr></thead>
                  <tbody>
                    {stocks.map(s => (
                      <tr key={s.id} style={{borderTop:'1px solid var(--line-2)'}}>
                        <td style={{padding:'10px 14px'}}>{new Date(s.date).toLocaleDateString('fr-FR')}</td>
                        <td style={{padding:'10px 14px', textAlign:'right', fontVariantNumeric:'tabular-nums', fontWeight:600}}>{fmt(s.value_ht)}</td>
                        <td style={{padding:'6px 14px', textAlign:'right'}}>
                          <button onClick={() => delStock(s.id)} title="Supprimer"
                            style={{background:'transparent', border:0, cursor:'pointer', color:'var(--danger)', fontSize:18, padding:6}}>×</button>
                        </td>
                      </tr>
                    ))}
                    {stocks.length === 0 && <tr><td colSpan={3} style={{padding:32, textAlign:'center'}} className="muted">Aucun relevé.</td></tr>}
                  </tbody>
                </table>
              </div>
            </div>
          </>
        )}

        {subTab === 'casse' && (
          <>
            <div className="card" style={{padding:16, marginBottom:16}}>
              <h3 style={{margin:'0 0 12px', fontSize:14, fontWeight:700}}>Nouvelle casse</h3>
              <div style={{display:'grid', gridTemplateColumns:'repeat(auto-fit, minmax(200px, 1fr))', gap:10, alignItems:'end'}}>
                <label style={{display:'flex', flexDirection:'column', gap:4, fontSize:12, fontWeight:600}}>Date
                  <input type="date" value={newCasseDate} onChange={e => setNewCasseDate(e.target.value)}
                    style={{padding:'10px 12px', border:'1px solid var(--line-1)', borderRadius:8, fontSize:14}} />
                </label>
                <label style={{display:'flex', flexDirection:'column', gap:4, fontSize:12, fontWeight:600}}>Montant jeté (€)
                  <input type="text" inputMode="decimal" value={newCasseAmount}
                    onChange={e => setNewCasseAmount(e.target.value)}
                    placeholder="Ex. 15.50"
                    onKeyDown={e => { if (e.key === 'Enter') addCasse(); }}
                    style={{padding:'10px 12px', border:'1px solid var(--line-1)', borderRadius:8, fontSize:14}} />
                </label>
                <Button variant="primary" icon="plus" onClick={addCasse}>Ajouter casse</Button>
              </div>
            </div>

            <div className="card" style={{padding:0, overflow:'hidden'}}>
              <div style={{padding:'12px 16px', borderBottom:'1px solid var(--line-1)', fontSize:13, fontWeight:700}}>
                Casse du mois <span className="muted" style={{fontWeight:400}}>· {casse.length} · Total {fmt(casse.reduce((s,c) => s + Number(c.amount||0), 0))}</span>
              </div>
              <div style={{overflowX:'auto'}}>
                <table style={{width:'100%', fontSize:14, borderCollapse:'collapse'}}>
                  <thead><tr style={{background:'var(--surface-1)', textAlign:'left'}}>
                    <th style={{padding:'10px 14px'}}>Date</th>
                    <th style={{padding:'10px 14px', textAlign:'right'}}>Montant</th>
                    <th style={{padding:'10px 14px', width:60}}></th>
                  </tr></thead>
                  <tbody>
                    {casse.map(c => (
                      <tr key={c.id} style={{borderTop:'1px solid var(--line-2)'}}>
                        <td style={{padding:'10px 14px'}}>{new Date(c.date).toLocaleDateString('fr-FR')}</td>
                        <td style={{padding:'10px 14px', textAlign:'right', color:'var(--danger)', fontVariantNumeric:'tabular-nums', fontWeight:600}}>{fmt(c.amount)}</td>
                        <td style={{padding:'6px 14px', textAlign:'right'}}>
                          <button onClick={() => delCasse(c.id)} title="Supprimer"
                            style={{background:'transparent', border:0, cursor:'pointer', color:'var(--danger)', fontSize:18, padding:6}}>×</button>
                        </td>
                      </tr>
                    ))}
                    {casse.length === 0 && <tr><td colSpan={3} style={{padding:32, textAlign:'center'}} className="muted">Aucune casse enregistrée ce mois.</td></tr>}
                  </tbody>
                </table>
              </div>
            </div>
          </>
        )}
      </div>
    </>
  );
}


// =========== Ecran "Prix d'achat manquants" (lignes de vente sans PA) ===========
function MissingPaScreen() {
  const [rows, setRows] = useState(null);
  const [filterCat, setFilterCat] = useState('all'); // all | ligne_ajustement_tva | produit_au_poids | produit_manquant_catalogue | sans_ean
  const [search, setSearch] = useState('');
  const [editing, setEditing] = useState(null); // { ean, designation, suggestedPa }
  const [paInput, setPaInput] = useState('');
  const [saving, setSaving] = useState(false);
  const { showToast } = useApp();

  const load = async () => {
    const sid = window.VivalData?._storeId;
    if (!sid) return;
    const { data } = await window.sb.from('v_sales_missing_pa')
      .select('*')
      .eq('store_id', sid)
      .order('ca_ttc_impacte', { ascending: false })
      .limit(500);
    setRows(data || []);
  };
  useEffect(() => { load(); }, []);

  const onCorrect = (row) => {
    setEditing(row);
    // Suggestion: 65% du prix de vente TTC (marge moyenne 30% sur HT)
    const suggest = row.prix_vente_moyen_ttc ? (row.prix_vente_moyen_ttc / (1 + Number(row.tva_rate || 0.055)) * 0.65).toFixed(2) : '';
    setPaInput(suggest);
  };

  const onSave = async () => {
    if (!editing) return;
    const pa = Number(String(paInput).replace(',', '.'));
    if (!(pa >= 0)) { showToast('Prix invalide'); return; }
    setSaving(true);
    const sid = window.VivalData?._storeId;
    const { data, error } = await window.sb.rpc('set_product_purchase_price', {
      p_store_id: sid, p_ean: editing.ean, p_purchase_price_ht: pa
    });
    setSaving(false);
    if (error) { showToast('Erreur : ' + error.message); return; }
    const r = data?.[0];
    showToast(`✓ PA enregistré (${r?.sales_lines_updated || 0} ventes recalculées)`);
    setEditing(null);
    setPaInput('');
    load();
  };

  if (rows === null) return <div style={{padding:40, textAlign:'center', color:'var(--ink-3)'}}>Chargement…</div>;

  const stats = rows.reduce((acc, r) => {
    const k = r.categorie || 'autre';
    acc[k] = acc[k] || { count:0, ca_ttc:0, ca_ht:0 };
    acc[k].count++;
    acc[k].ca_ttc += Number(r.ca_ttc_impacte || 0);
    acc[k].ca_ht += Number(r.ca_ht_impacte || 0);
    return acc;
  }, {});

  const searchL = search.trim().toLowerCase();
  const filtered = rows.filter(r => {
    if (filterCat !== 'all' && r.categorie !== filterCat) return false;
    if (searchL && !(r.designation || '').toLowerCase().includes(searchL) && !(r.ean || '').includes(searchL)) return false;
    return true;
  });

  const catLabels = {
    gere_via_marges_rayon: '⚖️ Géré via Marges rayon',
    ligne_ajustement_tva: '📊 Totaux caisse (ignorer)',
    produit_manquant_catalogue: '📦 À renseigner à la main',
  };
  const catExplain = {
    gere_via_marges_rayon: (
      <>
        Ces produits appartiennent aux <strong>rayons au poids</strong> (F&L, boulangerie, boucherie/charcuterie, fromage à la coupe).
        Leur marge est calculée <strong>globalement au niveau du rayon</strong> (pas produit par produit) dans l'onglet <em>« Marges rayon »</em> :
        <ul style={{margin:'6px 0 0 18px', padding:0}}>
          <li>Le <strong>CA</strong> est pris automatiquement depuis la caisse quand le rayon est rempli dans le PLU</li>
          <li>Les <strong>achats Casino</strong> (fromage à la coupe emballé, charcuterie tranchée…) apparaissent dans ton onglet <em>Factures</em> du rayon</li>
          <li>Tu ajoutes aussi tes <strong>factures non-Casino</strong> (F&L locaux, boulanger…) à la main</li>
          <li>La <strong>marge = CA − (stock début + achats − stock fin) − casse</strong></li>
        </ul>
      </>
    ),
    ligne_ajustement_tva: "Lignes récapitulatives de taxe imprimées par ta caisse (BOULANGERIE TX RE, etc.). Ce ne sont pas de vrais produits — tu peux les ignorer.",
    produit_manquant_catalogue: "Produits scannés en caisse mais pas encore dans ton catalogue Casino. Renseigne leur prix d'achat pour qu'ils comptent dans ta marge produit par produit.",
  };

  return (
    <div style={{padding:'0 32px 32px'}}>
      <div style={{padding:'14px 16px', background:'#fffbeb', border:'1px solid #fcd34d', borderRadius:12, marginBottom:16, fontSize:13, color:'#78350f', lineHeight:1.5}}>
        <div style={{fontWeight:700, marginBottom:6, fontSize:14}}>⚠️ Ces produits ne comptent pas dans ta marge</div>
        On n'a pas de prix d'achat pour ces lignes, donc leur marge n'est pas calculée.<br/>
        <strong>Renseigne le PA HT</strong> à la main et toutes les ventes passées sur ce produit seront recalculées automatiquement.
      </div>

      {/* Cartes par catégorie */}
      <div style={{display:'grid', gridTemplateColumns:'repeat(auto-fit, minmax(200px, 1fr))', gap:10, marginBottom:16}}>
        {Object.entries(catLabels).map(([k, label]) => (
          <button key={k}
            onClick={() => setFilterCat(k === filterCat ? 'all' : k)}
            className="card"
            style={{
              padding:14, textAlign:'left', cursor:'pointer', border:0,
              background: filterCat === k ? '#fef3c7' : 'white',
              borderLeft: filterCat === k ? '4px solid #d97706' : '4px solid var(--line-1)',
            }}>
            <div style={{fontSize:11, color:'var(--ink-3)', fontWeight:600, textTransform:'uppercase'}}>{label}</div>
            <div style={{fontSize:22, fontWeight:800, marginTop:4}}>{stats[k]?.count || 0}</div>
            <div style={{fontSize:11, color:'var(--ink-3)', marginTop:2}}>
              {stats[k]?.ca_ttc ? Number(stats[k].ca_ttc).toFixed(2) + ' € CA TTC' : '—'}
            </div>
          </button>
        ))}
      </div>

      {filterCat !== 'all' && (
        <div style={{padding:'10px 14px', background:'#f9fafb', border:'1px solid var(--line-1)', borderRadius:8, fontSize:12, marginBottom:12, color:'var(--ink-2)'}}>
          {catExplain[filterCat]}
          <button onClick={() => setFilterCat('all')} style={{marginLeft:8, background:'transparent', border:0, color:'var(--vival-red)', cursor:'pointer', fontSize:12, fontWeight:600}}>× Voir tout</button>
        </div>
      )}

      <div style={{display:'flex', gap:8, alignItems:'center', marginBottom:12}}>
        <input type="search" placeholder="Rechercher (désignation ou EAN)…" value={search} onChange={e => setSearch(e.target.value)}
          style={{padding:'8px 12px', border:'1px solid var(--line-1)', borderRadius:8, fontSize:14, minWidth:240, flex:1, maxWidth:400}} />
        <span className="muted" style={{fontSize:12}}>{filtered.length} / {rows.length}</span>
      </div>

      <div className="card" style={{padding:0, overflow:'hidden'}}>
        <div style={{overflowX:'auto', maxHeight:600, overflowY:'auto'}}>
          <table style={{width:'100%', fontSize:12, borderCollapse:'collapse'}}>
            <thead style={{position:'sticky', top:0, background:'var(--surface-1)', zIndex:1}}>
              <tr style={{textAlign:'left', color:'var(--ink-3)'}}>
                <th style={{padding:'10px'}}>Produit</th>
                <th style={{padding:'10px', textAlign:'right'}}>Nb ventes</th>
                <th style={{padding:'10px', textAlign:'right'}}>Qté</th>
                <th style={{padding:'10px', textAlign:'right'}}>PV moyen TTC</th>
                <th style={{padding:'10px', textAlign:'right'}}>CA impacté</th>
                <th style={{padding:'10px', width:100}}></th>
              </tr>
            </thead>
            <tbody>
              {filtered.map((r, i) => (
                <tr key={(r.ean || 'noean') + '-' + i} style={{borderTop:'1px solid var(--line-2)'}}>
                  <td style={{padding:'8px 10px', maxWidth:320}}>
                    <div style={{fontWeight:600}}>{(r.designation || '').slice(0, 50)}</div>
                    <div className="muted" style={{fontSize:10, fontFamily:'var(--font-mono)'}}>
                      {r.ean || '—'} · {catLabels[r.categorie] || r.categorie}
                    </div>
                  </td>
                  <td style={{padding:'8px 10px', textAlign:'right'}}>{r.nb_occurrences}</td>
                  <td style={{padding:'8px 10px', textAlign:'right'}}>{Number(r.qty_cumulee || 0).toFixed(1)}</td>
                  <td style={{padding:'8px 10px', textAlign:'right'}}>{r.prix_vente_moyen_ttc ? Number(r.prix_vente_moyen_ttc).toFixed(2) + ' €' : '—'}</td>
                  <td style={{padding:'8px 10px', textAlign:'right', fontWeight:700}}>{Number(r.ca_ttc_impacte || 0).toFixed(2)} €</td>
                  <td style={{padding:'8px 10px', textAlign:'right'}}>
                    {r.ean && r.categorie === 'produit_manquant_catalogue' && (
                      <Button variant="outline" size="sm" onClick={() => onCorrect(r)}>Renseigner PA</Button>
                    )}
                    {r.categorie === 'gere_via_marges_rayon' && (
                      <span style={{fontSize:11, color:'#0891b2', fontWeight:600}}>→ Marges rayon</span>
                    )}
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </div>

      {/* Modal correction PA */}
      {editing && (
        <div style={{position:'fixed', inset:0, background:'rgba(0,0,0,0.5)', display:'flex', alignItems:'center', justifyContent:'center', zIndex:200}}
             onClick={() => !saving && setEditing(null)}>
          <div className="card" style={{padding:24, maxWidth:480, width:'90%'}} onClick={e => e.stopPropagation()}>
            <h3 style={{margin:'0 0 12px', fontSize:16, fontWeight:800}}>Renseigner le prix d'achat</h3>
            <div style={{padding:'10px 12px', background:'#f9fafb', borderRadius:8, marginBottom:16}}>
              <div style={{fontSize:14, fontWeight:600}}>{editing.designation}</div>
              <div className="muted" style={{fontSize:11, fontFamily:'var(--font-mono)', marginTop:2}}>EAN {editing.ean}</div>
              <div style={{fontSize:12, marginTop:6}}>
                Vendu <strong>{editing.nb_occurrences}</strong> fois · Prix de vente moyen : <strong>{Number(editing.prix_vente_moyen_ttc || 0).toFixed(2)} € TTC</strong> · TVA {(Number(editing.tva_rate) * 100).toFixed(1)} %
              </div>
            </div>
            <label style={{display:'block', fontSize:13, fontWeight:600, marginBottom:6}}>
              Prix d'achat HT (€)
            </label>
            <Input
              value={paInput}
              onChange={e => setPaInput(e.target.value)}
              placeholder="Ex. 2.50"
              autoFocus
              style={{fontSize:16}}
            />
            <div style={{fontSize:11, color:'var(--ink-3)', marginTop:6, lineHeight:1.4}}>
              💡 Suggestion : <strong>65 %</strong> du prix de vente HT (= marge 35 % typique épicerie). Ajuste selon ton fournisseur.
            </div>
            <div style={{display:'flex', gap:8, justifyContent:'flex-end', marginTop:20}}>
              <Button variant="ghost" onClick={() => setEditing(null)} disabled={saving}>Annuler</Button>
              <Button variant="primary" icon="check" onClick={onSave} disabled={saving || !paInput}>{saving ? '…' : 'Enregistrer'}</Button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

// =========== Ecran "Stock theorique" (stock DB + BL non appliques) ===========
function StockTheoriqueScreen() {
  const [rows, setRows] = useState(null);
  const [search, setSearch] = useState('');
  const [filterRayon, setFilterRayon] = useState('all');
  const [filterLow, setFilterLow] = useState(false);

  useEffect(() => {
    (async () => {
      const sid = window.VivalData?._storeId;
      if (!sid) return;
      const { data } = await window.sb.from('v_stock_theorique')
        .select('ean, name, rayon, casino_family_label, stock_actuel, qty_bl_pending, stock_theorique, pa_real, valeur_stock_pa')
        .eq('store_id', sid)
        .order('valeur_stock_pa', { ascending: false, nullsFirst: false })
        .limit(2000);
      setRows(data || []);
    })();
  }, []);

  if (rows === null) return <div style={{padding:40, textAlign:'center', color:'var(--ink-3)'}}>Chargement…</div>;

  const rayonsList = [...new Set(rows.map(r => r.rayon).filter(Boolean))].sort();
  const searchL = search.trim().toLowerCase();
  const filtered = rows.filter(r => {
    if (filterRayon !== 'all' && r.rayon !== filterRayon) return false;
    if (filterLow && Number(r.stock_theorique) >= 5) return false;
    if (searchL && !(r.name || '').toLowerCase().includes(searchL) && !(r.ean || '').includes(searchL)) return false;
    return true;
  });

  const totalValue = filtered.reduce((s, r) => s + Number(r.valeur_stock_pa || 0), 0);
  const inRupture = filtered.filter(r => Number(r.stock_theorique || 0) <= 0).length;
  const hasBlPending = filtered.filter(r => Number(r.qty_bl_pending || 0) > 0).length;

  return (
    <div style={{padding:'0 32px 32px'}}>
      <div style={{padding:'14px 16px', background:'#eff6ff', border:'1px solid #bfdbfe', borderRadius:12, marginBottom:16, fontSize:13, color:'#1e3a8a', lineHeight:1.5}}>
        <div style={{fontWeight:700, marginBottom:6, fontSize:14}}>📦 Qu'est-ce que le stock théorique ?</div>
        Le <strong>stock actuel</strong> c'est ce qui est en base MonVival aujourd'hui.<br/>
        Le <strong>stock théorique</strong> = <em>stock actuel + livraisons BL pas encore appliquées</em>.<br/>
        Clique « Appliquer au stock » dans Réglages → Synchro ERP pour intégrer les BL en attente.
      </div>

      <div style={{display:'grid', gridTemplateColumns:'repeat(auto-fit, minmax(180px, 1fr))', gap:10, marginBottom:16}}>
        <div className="card" style={{padding:14}}>
          <div className="kpi-label">Valeur stock (PA)</div>
          <div style={{fontSize:22, fontWeight:800, marginTop:4}}>{totalValue.toFixed(2)} €</div>
          <div className="muted" style={{fontSize:11, marginTop:2}}>{filtered.length} référence{filtered.length > 1 ? 's' : ''}</div>
        </div>
        <div className="card" style={{padding:14, borderLeft: inRupture > 0 ? '4px solid #dc2626' : 'none'}}>
          <div className="kpi-label">En rupture</div>
          <div style={{fontSize:22, fontWeight:800, marginTop:4, color: inRupture > 0 ? '#dc2626' : 'inherit'}}>{inRupture}</div>
          <div className="muted" style={{fontSize:11, marginTop:2}}>stock théorique ≤ 0</div>
        </div>
        <div className="card" style={{padding:14, borderLeft: hasBlPending > 0 ? '4px solid #d97706' : 'none'}}>
          <div className="kpi-label">Livraisons en attente</div>
          <div style={{fontSize:22, fontWeight:800, marginTop:4, color: hasBlPending > 0 ? '#d97706' : 'inherit'}}>{hasBlPending}</div>
          <div className="muted" style={{fontSize:11, marginTop:2}}>BL pas encore appliqués</div>
        </div>
      </div>

      <div style={{display:'flex', gap:8, flexWrap:'wrap', marginBottom:12, alignItems:'center'}}>
        <input type="search" placeholder="Rechercher (nom ou EAN)…" value={search} onChange={e => setSearch(e.target.value)}
          style={{padding:'8px 12px', border:'1px solid var(--line-1)', borderRadius:8, fontSize:14, minWidth:240}} />
        <select value={filterRayon} onChange={e => setFilterRayon(e.target.value)}
          style={{padding:'8px 12px', border:'1px solid var(--line-1)', borderRadius:8, fontSize:14}}>
          <option value="all">Tous rayons</option>
          {rayonsList.map(r => <option key={r} value={r}>{r}</option>)}
        </select>
        <label style={{fontSize:13, display:'flex', alignItems:'center', gap:6, cursor:'pointer'}}>
          <input type="checkbox" checked={filterLow} onChange={e => setFilterLow(e.target.checked)} />
          Stock bas (&lt; 5) uniquement
        </label>
      </div>

      <div className="card" style={{padding:0, overflow:'hidden'}}>
        <div style={{overflowX:'auto', maxHeight:600, overflowY:'auto'}}>
          <table style={{width:'100%', fontSize:12, borderCollapse:'collapse'}}>
            <thead style={{position:'sticky', top:0, background:'var(--surface-1)', zIndex:1}}>
              <tr style={{textAlign:'left', color:'var(--ink-3)'}}>
                <th style={{padding:'10px 10px'}}>Produit</th>
                <th style={{padding:'10px 10px'}}>Rayon</th>
                <th style={{padding:'10px 10px', textAlign:'right'}}>Stock</th>
                <th style={{padding:'10px 10px', textAlign:'right'}}>BL en attente</th>
                <th style={{padding:'10px 10px', textAlign:'right'}}>Théorique</th>
                <th style={{padding:'10px 10px', textAlign:'right'}}>PA (€)</th>
                <th style={{padding:'10px 10px', textAlign:'right'}}>Valeur stock</th>
              </tr>
            </thead>
            <tbody>
              {filtered.slice(0, 500).map(r => {
                const theo = Number(r.stock_theorique || 0);
                const pending = Number(r.qty_bl_pending || 0);
                const bg = theo <= 0 ? '#fef2f2' : pending > 0 ? '#fffbeb' : 'transparent';
                return (
                  <tr key={r.ean} style={{borderTop:'1px solid var(--line-2)', background: bg}}>
                    <td style={{padding:'7px 10px', maxWidth:280}}>
                      <div style={{fontWeight:600}}>{(r.name || '').slice(0, 50)}</div>
                      <div className="muted" style={{fontSize:10, fontFamily:'var(--font-mono)'}}>EAN {r.ean}</div>
                    </td>
                    <td style={{padding:'7px 10px', fontSize:11}}>{r.rayon || '—'}</td>
                    <td style={{padding:'7px 10px', textAlign:'right', color: Number(r.stock_actuel) <= 0 ? '#dc2626' : 'inherit'}}>{r.stock_actuel}</td>
                    <td style={{padding:'7px 10px', textAlign:'right', color: pending > 0 ? '#d97706' : 'var(--ink-3)', fontWeight: pending > 0 ? 700 : 400}}>{pending > 0 ? '+' + pending : '—'}</td>
                    <td style={{padding:'7px 10px', textAlign:'right', fontWeight:700, color: theo <= 0 ? '#dc2626' : '#059669'}}>{theo}</td>
                    <td style={{padding:'7px 10px', textAlign:'right'}}>{Number(r.pa_real || 0) > 0 ? Number(r.pa_real).toFixed(2) + ' €' : '—'}</td>
                    <td style={{padding:'7px 10px', textAlign:'right', fontWeight:700}}>{Number(r.valeur_stock_pa || 0) > 0 ? Number(r.valeur_stock_pa).toFixed(2) + ' €' : '—'}</td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
        {filtered.length > 500 && (
          <div style={{padding:'10px 16px', textAlign:'center', fontSize:12, color:'var(--ink-3)', borderTop:'1px solid var(--line-1)'}}>
            Affichage limité aux 500 premières lignes. Affine la recherche pour voir le reste. Total : {filtered.length} lignes.
          </div>
        )}
      </div>
    </div>
  );
}

// =========== Carte CA previsionnel fin de mois ===========
function CaPrevisionnelCard() {
  const [kpi, setKpi] = useState(null);

  useEffect(() => {
    (async () => {
      const sid = window.VivalData?._storeId;
      if (!sid) return;
      try {
        const { data } = await window.sb.rpc('ca_previsionnel_mois', { p_store_id: sid });
        setKpi(data?.[0] || null);
      } catch (e) { console.warn('ca_previsionnel_mois', e); }
    })();
  }, []);

  if (!kpi || Number(kpi.ca_ht_actuel || 0) === 0) return null;

  const actuel = Number(kpi.ca_ht_actuel || 0);
  const projection = Number(kpi.ca_ht_projection_ajustee || 0);
  const moyJour = Number(kpi.ca_ht_moyen_jour_ecoule || 0);
  const restants = kpi.nb_jours_restants;
  const ecoulesPct = Math.round(100 * kpi.nb_jours_ecoules / kpi.nb_jours_total);
  const hasN1 = kpi.tendance !== 'no_data';
  const tend = kpi.tendance;
  const tendLabel = tend === 'up' ? '↗ en hausse' : tend === 'down' ? '↘ en baisse' : tend === 'stable' ? '→ stable' : '';
  const tendColor = tend === 'up' ? '#059669' : tend === 'down' ? '#dc2626' : 'var(--ink-2)';

  return (
    <div className="card" style={{margin:'0 32px 16px', padding:'16px 20px', borderLeft:'4px solid #0891b2', background:'#f0f9ff'}}>
      <div style={{display:'flex', gap:16, alignItems:'center', flexWrap:'wrap'}}>
        <div style={{flex:'1 1 240px', minWidth:200}}>
          <div style={{fontSize:12, color:'#0c4a6e', fontWeight:700, textTransform:'uppercase', letterSpacing:'0.5px'}}>
            📈 CA prévisionnel fin de mois
          </div>
          <div style={{fontSize:28, fontWeight:800, marginTop:4, color:'#0c4a6e'}}>
            {projection.toFixed(0)} € HT
          </div>
          <div style={{fontSize:12, color:'var(--ink-3)', marginTop:4}}>
            Actuel : <strong>{actuel.toFixed(0)} €</strong> sur {kpi.nb_jours_ecoules}/{kpi.nb_jours_total} jours · {moyJour.toFixed(0)} €/jour en moyenne
            {hasN1 && <> · <span style={{color: tendColor, fontWeight:700}}>{tendLabel} vs N-1</span></>}
          </div>
        </div>

        <div style={{flex:'0 0 auto', minWidth:140, paddingLeft:16, borderLeft:'1px solid #bae6fd'}}>
          <div style={{fontSize:11, color:'var(--ink-3)', fontWeight:600}}>Jours restants</div>
          <div style={{fontSize:20, fontWeight:800, color:'#0c4a6e'}}>{restants} j</div>
          <div style={{fontSize:10, color:'var(--ink-3)'}}>{ecoulesPct} % du mois écoulé</div>
        </div>

        {hasN1 && (
          <div style={{flex:'0 0 auto', minWidth:140, paddingLeft:16, borderLeft:'1px solid #bae6fd'}}>
            <div style={{fontSize:11, color:'var(--ink-3)', fontWeight:600}}>N-1 même période</div>
            <div style={{fontSize:16, fontWeight:700}}>{Number(kpi.ca_ht_n_moins_1_meme_periode || 0).toFixed(0)} €</div>
            <div style={{fontSize:10, color:'var(--ink-3)'}}>Fin mois N-1 : {Number(kpi.ca_ht_n_moins_1_total_mois || 0).toFixed(0)} €</div>
          </div>
        )}
      </div>
      <div style={{marginTop:10, fontSize:11, color:'var(--ink-3)', fontStyle:'italic'}}>
        💡 Projection basée sur la moyenne du mois en cours{hasN1 ? `, ajustée avec la saisonnalité N-1 (facteur ×${Number(kpi.facteur_saison || 1).toFixed(2)})` : ''}.
      </div>
    </div>
  );
}

// =========== Carte "Ruptures imminentes" ===========
function RuptureImminenteCard({ onClick }) {
  const [stats, setStats] = useState(null);

  useEffect(() => {
    (async () => {
      const sid = window.VivalData?._storeId;
      if (!sid) return;
      const { data } = await window.sb.from('v_rupture_imminente')
        .select('niveau_alerte, ean, name, pa_real, ventes_moy_jour')
        .eq('store_id', sid);
      setStats(data || []);
    })();
  }, []);

  if (!stats || stats.length === 0) return null;

  const rupture = stats.filter(r => r.niveau_alerte === 'rupture').length;
  const imminente = stats.filter(r => r.niveau_alerte === 'imminente').length;
  const faible = stats.filter(r => r.niveau_alerte === 'faible').length;
  const total = rupture + imminente + faible;
  if (total === 0) return null;

  const valeurManquante = stats
    .filter(r => r.niveau_alerte === 'rupture')
    .reduce((s, r) => s + Number(r.pa_real || 0) * Math.max(1, Number(r.ventes_moy_jour || 0) * 7), 0);

  return (
    <div className="card" style={{margin:'0 32px 16px', padding:'16px 20px', borderLeft:'4px solid #dc2626', background:'#fef2f2', cursor: onClick ? 'pointer' : 'default'}}
         onClick={onClick}>
      <div style={{display:'flex', gap:16, alignItems:'center', flexWrap:'wrap'}}>
        <div style={{flex:'1 1 240px', minWidth:200}}>
          <div style={{fontSize:12, color:'#991b1b', fontWeight:700, textTransform:'uppercase', letterSpacing:'0.5px'}}>
            🚨 Ruptures & stocks critiques
          </div>
          <div style={{fontSize:28, fontWeight:800, marginTop:4, color:'#991b1b'}}>
            {total} produits
          </div>
          <div style={{fontSize:12, color:'var(--ink-3)', marginTop:4}}>
            <strong style={{color:'#dc2626'}}>{rupture}</strong> en rupture · <strong style={{color:'#d97706'}}>{imminente}</strong> imminent (&lt;2j) · <strong style={{color:'#ca8a04'}}>{faible}</strong> faible (&lt;4j)
          </div>
        </div>

        {valeurManquante > 0 && (
          <div style={{flex:'0 0 auto', minWidth:160, paddingLeft:16, borderLeft:'1px solid #fecaca'}}>
            <div style={{fontSize:11, color:'var(--ink-3)', fontWeight:600}}>CA potentiellement perdu</div>
            <div style={{fontSize:20, fontWeight:800, color:'#991b1b'}}>~{valeurManquante.toFixed(0)} €</div>
            <div style={{fontSize:10, color:'var(--ink-3)'}}>si ruptures sur 7 jours</div>
          </div>
        )}

        {onClick && (
          <Button variant="outline" size="sm" onClick={onClick}>Voir le détail →</Button>
        )}
      </div>
    </div>
  );
}

// Modale liste détaillée des ruptures
function RuptureImminenteModal({ open, onClose }) {
  const [rows, setRows] = useState(null);
  const [filter, setFilter] = useState('all');

  useEffect(() => {
    if (!open) return;
    (async () => {
      const sid = window.VivalData?._storeId;
      const { data } = await window.sb.from('v_rupture_imminente')
        .select('*').eq('store_id', sid)
        .order('jours_couverture', { ascending: true, nullsFirst: true })
        .limit(500);
      setRows(data || []);
    })();
  }, [open]);

  if (!open) return null;

  const filtered = rows?.filter(r => filter === 'all' || r.niveau_alerte === filter) || [];

  return (
    <div style={{position:'fixed', inset:0, background:'rgba(0,0,0,0.5)', display:'flex', alignItems:'center', justifyContent:'center', zIndex:200, padding:16}}
         onClick={onClose}>
      <div className="card" style={{padding:0, maxWidth:900, width:'100%', maxHeight:'90vh', overflow:'hidden', display:'flex', flexDirection:'column'}}
           onClick={e => e.stopPropagation()}>
        <div style={{padding:'16px 20px', borderBottom:'1px solid var(--line-1)', background:'var(--surface-1)', display:'flex', alignItems:'center', gap:12}}>
          <div style={{flex:1}}>
            <div style={{fontSize:16, fontWeight:800}}>🚨 Ruptures & stocks critiques</div>
            <div style={{fontSize:12, color:'var(--ink-3)', marginTop:2}}>{filtered.length} produits · trié par couverture (plus urgent en haut)</div>
          </div>
          <button onClick={onClose} style={{background:'transparent', border:0, fontSize:24, cursor:'pointer', color:'var(--ink-3)'}}>×</button>
        </div>
        <div style={{padding:'10px 20px', borderBottom:'1px solid var(--line-1)', display:'flex', gap:8, flexWrap:'wrap'}}>
          <button className="chip" aria-pressed={filter === 'all'} onClick={() => setFilter('all')}>Tous ({rows?.length || 0})</button>
          <button className="chip" aria-pressed={filter === 'rupture'} onClick={() => setFilter('rupture')}>Rupture ({rows?.filter(r => r.niveau_alerte === 'rupture').length || 0})</button>
          <button className="chip" aria-pressed={filter === 'imminente'} onClick={() => setFilter('imminente')}>Imminente ({rows?.filter(r => r.niveau_alerte === 'imminente').length || 0})</button>
          <button className="chip" aria-pressed={filter === 'faible'} onClick={() => setFilter('faible')}>Faible ({rows?.filter(r => r.niveau_alerte === 'faible').length || 0})</button>
        </div>
        <div style={{overflowY:'auto', flex:1}}>
          <table style={{width:'100%', fontSize:12, borderCollapse:'collapse'}}>
            <thead style={{position:'sticky', top:0, background:'white', zIndex:1}}>
              <tr style={{textAlign:'left', borderBottom:'1px solid var(--line-1)', color:'var(--ink-3)'}}>
                <th style={{padding:'10px'}}>Produit</th>
                <th style={{padding:'10px'}}>Rayon</th>
                <th style={{padding:'10px', textAlign:'right'}}>Stock théorique</th>
                <th style={{padding:'10px', textAlign:'right'}}>Ventes /j</th>
                <th style={{padding:'10px', textAlign:'right'}}>Couverture</th>
                <th style={{padding:'10px'}}>Niveau</th>
              </tr>
            </thead>
            <tbody>
              {filtered.map((r, i) => {
                const niv = r.niveau_alerte;
                const col = niv === 'rupture' ? '#dc2626' : niv === 'imminente' ? '#d97706' : '#ca8a04';
                const bg = niv === 'rupture' ? '#fef2f2' : niv === 'imminente' ? '#fffbeb' : '#fefce8';
                return (
                  <tr key={i} style={{borderTop:'1px solid var(--line-2)', background: bg}}>
                    <td style={{padding:'8px 10px'}}>
                      <div style={{fontWeight:600}}>{(r.name || '').slice(0, 60)}</div>
                      <div style={{fontSize:10, color:'var(--ink-3)', fontFamily:'var(--font-mono)'}}>EAN {r.ean}</div>
                    </td>
                    <td style={{padding:'8px 10px', fontSize:11}}>{r.rayon || '—'}</td>
                    <td style={{padding:'8px 10px', textAlign:'right', fontWeight:700, color: Number(r.stock_theorique) <= 0 ? '#dc2626' : 'inherit'}}>{r.stock_theorique}</td>
                    <td style={{padding:'8px 10px', textAlign:'right'}}>{Number(r.ventes_moy_jour).toFixed(2)}</td>
                    <td style={{padding:'8px 10px', textAlign:'right', fontWeight:700, color: col}}>
                      {r.jours_couverture != null ? Number(r.jours_couverture).toFixed(1) + ' j' : '—'}
                    </td>
                    <td style={{padding:'8px 10px', fontSize:11, color: col, fontWeight:700, textTransform:'uppercase'}}>{niv}</td>
                  </tr>
                );
              })}
              {filtered.length === 0 && <tr><td colSpan={6} style={{padding:30, textAlign:'center', color:'var(--ink-3)'}}>Aucun produit dans cette catégorie 🎉</td></tr>}
            </tbody>
          </table>
        </div>
      </div>
    </div>
  );
}

// =========== Carte "Marge reelle du jour" (base sur les PA issus des BL Casino) ===========
function MargeReelleBadgeCard() {
  const [kpi, setKpi] = useState(null);
  const [loading, setLoading] = useState(true);
  const [recalculating, setRecalculating] = useState(false);
  const { showToast } = useApp();

  const load = async () => {
    setLoading(true);
    const sid = window.VivalData?._storeId;
    if (!sid) { setLoading(false); return; }
    try {
      const { data, error } = await window.sb.rpc('kpi_marge_jour', { p_store_id: sid });
      if (error) throw error;
      setKpi(data?.[0] || null);
    } catch (e) {
      console.warn('kpi_marge_jour', e);
    }
    setLoading(false);
  };
  useEffect(() => { load(); }, []);

  const onRecalc = async () => {
    const sid = window.VivalData?._storeId;
    if (!sid) return;
    if (!confirm('Recalculer la marge des 6 derniers mois avec les prix d\'achat réels des BL Casino ?')) return;
    setRecalculating(true);
    try {
      const from = new Date(); from.setMonth(from.getMonth() - 6);
      const { data, error } = await window.sb.rpc('refresh_sales_marge_from_bl', { p_store_id: sid, p_from_date: from.toISOString().slice(0,10) });
      if (error) throw error;
      const r = data?.[0];
      showToast(`Marge recalculée : ${r?.lignes_affected || 0} lignes sur ${r?.imports_affected || 0} imports`);
      load();
    } catch (e) {
      showToast('Erreur : ' + (e.message || e));
    }
    setRecalculating(false);
  };

  if (loading) return null;
  if (!kpi || Number(kpi.nb_lignes_total || 0) === 0) return null;

  const margeToday = Number(kpi.marge_ht_reelle || 0);
  const margeYest = Number(kpi.marge_ht_hier || 0);
  const delta = margeYest > 0 ? ((margeToday - margeYest) / margeYest) * 100 : null;
  const coverage = Number(kpi.pct_couverture_bl || 0);

  return (
    <div className="card" style={{margin:'0 32px 16px', padding:'16px 20px', borderLeft:'4px solid #059669', background:'#f0fdf4'}}>
      <div style={{display:'flex', gap:16, alignItems:'center', flexWrap:'wrap'}}>
        <div style={{flex:'1 1 240px', minWidth:200}}>
          <div style={{fontSize:12, color:'#166534', fontWeight:700, textTransform:'uppercase', letterSpacing:'0.5px'}}>💰 Marge réelle aujourd'hui (PA issu des BL Casino)</div>
          <div style={{fontSize:28, fontWeight:800, marginTop:4, color:'#166534'}}>
            {margeToday.toFixed(2).replace('.', ',')} €
            <span style={{fontSize:16, marginLeft:8, color:'var(--ink-2)'}}>
              ({Number(kpi.taux_marge_pct || 0).toFixed(1).replace('.', ',')} %)
            </span>
          </div>
          <div style={{fontSize:12, color:'var(--ink-3)', marginTop:4}}>
            CA HT : {Number(kpi.ca_ht || 0).toFixed(2).replace('.', ',')} €
            {delta != null && (
              <> · <span style={{color: delta >= 0 ? '#059669' : '#dc2626', fontWeight:700}}>
                {delta >= 0 ? '+' : ''}{delta.toFixed(1).replace('.', ',')} % vs hier
              </span></>
            )}
          </div>
        </div>

        <div style={{flex:'0 0 auto', minWidth:140, paddingLeft:16, borderLeft:'1px solid #bbf7d0'}}>
          <div style={{fontSize:11, color:'var(--ink-3)', fontWeight:600}}>Fiabilité</div>
          <div style={{fontSize:20, fontWeight:800, color: coverage >= 70 ? '#059669' : coverage >= 40 ? '#d97706' : '#dc2626'}}>
            {coverage.toFixed(0)} %
          </div>
          <div style={{fontSize:10, color:'var(--ink-3)', lineHeight:1.3}}>
            {kpi.nb_lignes_avec_pa_bl}/{kpi.nb_lignes_total} lignes avec PA BL
          </div>
        </div>

        <div style={{flex:'0 0 auto'}}>
          <Button variant="outline" icon="refresh" size="sm" disabled={recalculating} onClick={onRecalc}>
            {recalculating ? 'Calcul…' : 'Recalculer sur 6 mois'}
          </Button>
        </div>
      </div>
      <div style={{marginTop:10, fontSize:11, color:'var(--ink-3)', fontStyle:'italic'}}>
        💡 Marge calculée avec le <strong>prix débit du dernier BL Casino</strong> reçu pour chaque produit (+ précis que le PCE catalogue théorique). Fiabilité = part des ventes qu'on a pu lier à un BL.
      </div>
    </div>
  );
}

function ReportsScreen() {
  const stockEnabled = useModule('stock');
  // Tab init: priorite sessionStorage (retour depuis fiche produit) sinon localStorage (dernier onglet)
  const initialTab = (() => {
    try {
      const fromBack = sessionStorage.getItem('vival_reports_tab');
      if (fromBack) { sessionStorage.removeItem('vival_reports_tab'); return fromBack; }
      const lastTab = localStorage.getItem('vival_reports_last_tab');
      if (lastTab) return lastTab;
    } catch (_) {}
    return 'period';
  })();
  const [tab, _setTab] = useState(initialTab); // period | history | families | fl | margins
  const setTab = (t) => {
    _setTab(t);
    try { localStorage.setItem('vival_reports_last_tab', t); } catch (_) {}
  };
  // Defaut "ce mois-ci": le gerant rentre ses chiffres en fin de journee, donc
  // afficher "Aujourd'hui" donne souvent 0 EUR jusqu'a l'import du soir.
  const [period, setPeriod] = useState('this-month');
  const [customFrom, setCustomFrom] = useState(() => toDateInput(new Date()));
  const [customTo, setCustomTo] = useState(() => toDateInput(new Date()));
  const [source, setSource] = useState('all'); // all | app | caisse
  const [loading, setLoading] = useState(true);
  const [lines, setLines] = useState([]);
  const [prevLines, setPrevLines] = useState([]);
  const [yearAgoLines, setYearAgoLines] = useState([]);
  const [expanded, setExpanded] = useState({});
  const [importOpen, setImportOpen] = useState(false);
  const [inventoryOpen, setInventoryOpen] = useState(false);
  const [reloadKey, setReloadKey] = useState(0);
  const [detailImportId, setDetailImportId] = useState(null);
  const [showRupture, setShowRupture] = useState(false);
  const [alertCount, setAlertCount] = useState(null);
  useEffect(() => {
    window.sb.rpc('get_alerts').then(r => setAlertCount((r.data || []).length)).catch(() => setAlertCount(0));
  }, [reloadKey]);

  const { from, to } = getReportRange(period, customFrom, customTo);
  const prev = previousRange(from, to);
  const ya = yearAgoRange(from, to);

  useEffect(() => {
    let cancelled = false;
    setLoading(true);
    Promise.all([
      fetchReportData(from, to, source),
      fetchReportData(prev.from, prev.to, source),
      fetchReportData(ya.from, ya.to, source),
    ]).then(([curr, prv, yearAgo]) => {
      if (cancelled) return;
      setLines(enrichReportLines(curr));
      setPrevLines(enrichReportLines(prv));
      setYearAgoLines(enrichReportLines(yearAgo));
      setLoading(false);
    }).catch(() => { if (!cancelled) setLoading(false); });
    return () => { cancelled = true; };
  }, [period, customFrom, customTo, source, reloadKey]);

  const kpi = useMemo(() => computeKpi(lines), [lines]);
  const prevKpi = useMemo(() => computeKpi(prevLines), [prevLines]);
  const yearAgoKpi = useMemo(() => computeKpi(yearAgoLines), [yearAgoLines]);
  const byRayon = useMemo(() => aggregateReportByRayon(lines), [lines]);
  const topProducts = useMemo(() => aggregateTopProducts(lines, 20), [lines]);

  const periods = [
    { id: 'today', label: "Aujourd'hui" },
    { id: 'yesterday', label: 'Hier' },
    { id: 'week', label: '7 derniers jours' },
    { id: 'this-month', label: 'Ce mois-ci' },
    { id: 'last-month', label: 'Mois dernier' },
    { id: 'custom', label: 'Personnalisé' },
  ];

  return (
    <>
      <div className="page-header">
        <div className="page-header-grow">
          <h1 className="page-title">Rapports</h1>
          <p className="page-subtitle">
            {tab === 'period' ? (<>{formatPeriodLabel(period, from, to)}{loading && ' · chargement…'}</>)
              : tab === 'history' ? 'Historique des imports caisse'
              : tab === 'families' ? 'Familles Casino — libellé et rayon (clic pour éditer)'
              : tab === 'fl' ? 'Marge par rayon — factures fournisseurs, stock, casse (calcul exact)'
              : tab === 'topflop' ? 'Top / Flop — meilleurs vendeurs, stock dormant, marges basses'
              : tab === 'budget' ? 'Budget mensuel — objectif CA et taux de marge'
              : 'Alertes actives — rupture, marge négative, familles à valider'}
          </p>
        </div>
        <div style={{display:'flex', gap:8}}>
          <Button variant="outline" icon="store" onClick={() => setInventoryOpen(true)}>
            Importer inventaire
          </Button>
          <Button variant="outline" icon="download" onClick={() => setImportOpen(true)}>
            Importer export caisse
          </Button>
        </div>
      </div>

      {/* Tabs */}
      <div style={{padding:'0 32px 12px', display:'flex', gap:6, borderBottom:'1px solid var(--line-1)', marginBottom:16}}>
        <button data-tour-tab="period"
          onClick={() => setTab('period')}
          style={{
            padding:'10px 16px', border:0, background:'transparent', cursor:'pointer',
            fontSize:14, fontWeight:700,
            borderBottom: tab === 'period' ? '3px solid var(--vival-red)' : '3px solid transparent',
            color: tab === 'period' ? 'var(--ink-1)' : 'var(--ink-3)',
            marginBottom:-1,
          }}>Par période</button>
        <button data-tour-tab="history"
          onClick={() => setTab('history')}
          style={{
            padding:'10px 16px', border:0, background:'transparent', cursor:'pointer',
            fontSize:14, fontWeight:700,
            borderBottom: tab === 'history' ? '3px solid var(--vival-red)' : '3px solid transparent',
            color: tab === 'history' ? 'var(--ink-1)' : 'var(--ink-3)',
            marginBottom:-1,
          }}>Historique des imports</button>
        <button data-tour-tab="families"
          onClick={() => setTab('families')}
          style={{
            padding:'10px 16px', border:0, background:'transparent', cursor:'pointer',
            fontSize:14, fontWeight:700,
            borderBottom: tab === 'families' ? '3px solid var(--vival-red)' : '3px solid transparent',
            color: tab === 'families' ? 'var(--ink-1)' : 'var(--ink-3)',
            marginBottom:-1,
          }}>Familles Casino</button>
        <button data-tour-tab="fl"
          onClick={() => setTab('fl')}
          style={{
            padding:'10px 16px', border:0, background:'transparent', cursor:'pointer',
            fontSize:14, fontWeight:700,
            borderBottom: tab === 'fl' ? '3px solid var(--vival-red)' : '3px solid transparent',
            color: tab === 'fl' ? 'var(--ink-1)' : 'var(--ink-3)',
            marginBottom:-1,
          }}>Marges rayon</button>
        <button data-tour-tab="topflop"
          onClick={() => setTab('topflop')}
          style={{
            padding:'10px 16px', border:0, background:'transparent', cursor:'pointer',
            fontSize:14, fontWeight:700,
            borderBottom: tab === 'topflop' ? '3px solid var(--vival-red)' : '3px solid transparent',
            color: tab === 'topflop' ? 'var(--ink-1)' : 'var(--ink-3)',
            marginBottom:-1,
          }}>Top / Flop</button>
        <button data-tour-tab="budget"
          onClick={() => setTab('budget')}
          style={{
            padding:'10px 16px', border:0, background:'transparent', cursor:'pointer',
            fontSize:14, fontWeight:700,
            borderBottom: tab === 'budget' ? '3px solid var(--vival-red)' : '3px solid transparent',
            color: tab === 'budget' ? 'var(--ink-1)' : 'var(--ink-3)',
            marginBottom:-1,
          }}>Budget</button>
        <button data-tour-tab="alerts"
          onClick={() => setTab('alerts')}
          style={{
            padding:'10px 16px', border:0, background:'transparent', cursor:'pointer',
            fontSize:14, fontWeight:700,
            borderBottom: tab === 'alerts' ? '3px solid var(--vival-red)' : '3px solid transparent',
            color: tab === 'alerts' ? 'var(--ink-1)' : 'var(--ink-3)',
            marginBottom:-1,
            display:'flex', alignItems:'center', gap:6,
          }}>
          Alertes
          {alertCount > 0 && (
            <span style={{display:'inline-block', minWidth:20, padding:'2px 6px', borderRadius:10, background:'#dc2626', color:'white', fontSize:11, fontWeight:700}}>
              {alertCount}
            </span>
          )}
        </button>
        <button data-tour-tab="casino-purchases"
          onClick={() => setTab('casino-purchases')}
          style={{
            padding:'10px 16px', border:0, background:'transparent', cursor:'pointer',
            fontSize:14, fontWeight:700,
            borderBottom: tab === 'casino-purchases' ? '3px solid var(--vival-red)' : '3px solid transparent',
            color: tab === 'casino-purchases' ? 'var(--ink-1)' : 'var(--ink-3)',
            marginBottom:-1,
          }}>Achats Casino</button>
        {stockEnabled && <button data-tour-tab="stock-theorique"
          onClick={() => setTab('stock-theorique')}
          style={{
            padding:'10px 16px', border:0, background:'transparent', cursor:'pointer',
            fontSize:14, fontWeight:700,
            borderBottom: tab === 'stock-theorique' ? '3px solid var(--vival-red)' : '3px solid transparent',
            color: tab === 'stock-theorique' ? 'var(--ink-1)' : 'var(--ink-3)',
            marginBottom:-1,
          }}>Stock théorique</button>}
        <button data-tour-tab="missing-pa"
          onClick={() => setTab('missing-pa')}
          style={{
            padding:'10px 16px', border:0, background:'transparent', cursor:'pointer',
            fontSize:14, fontWeight:700,
            borderBottom: tab === 'missing-pa' ? '3px solid var(--vival-red)' : '3px solid transparent',
            color: tab === 'missing-pa' ? 'var(--ink-1)' : 'var(--ink-3)',
            marginBottom:-1,
          }}>Prix d'achat manquants</button>
      </div>

      {tab === 'history' && <SalesImportsHistory onOpenImport={(id) => setDetailImportId(id)} />}
      {tab === 'families' && <CasinoFamiliesEditor />}
      {tab === 'fl' && <RayonMarginTracker />}
      {tab === 'topflop' && <TopFlopScreen />}
      {tab === 'budget' && <BudgetScreen />}
      {tab === 'alerts' && <AlertsScreen />}
      {tab === 'casino-purchases' && <CasinoPurchasesScreen />}
      {tab === 'stock-theorique' && stockEnabled && <StockTheoriqueScreen />}
      {tab === 'missing-pa' && <MissingPaScreen />}

      {tab === 'period' && <div style={{padding:'0 32px 16px', display:'flex', gap:8, flexWrap:'wrap', alignItems:'center'}}>
        {periods.map(p => (
          <button key={p.id} className="chip" aria-pressed={period === p.id} onClick={() => setPeriod(p.id)}>
            {p.label}
          </button>
        ))}
      </div>}

      {tab === 'period' && <>

      {period === 'custom' && (
        <div style={{padding:'0 32px 16px', display:'flex', gap:12, flexWrap:'wrap', alignItems:'center'}}>
          <label style={{fontSize:13, display:'flex', alignItems:'center', gap:8}}>
            Du <input type="date" value={customFrom} max={customTo} onChange={e => setCustomFrom(e.target.value)} style={{padding:'8px 10px', border:'1px solid var(--line-1)', borderRadius:8, fontSize:14}} />
          </label>
          <label style={{fontSize:13, display:'flex', alignItems:'center', gap:8}}>
            Au <input type="date" value={customTo} min={customFrom} max={toDateInput(new Date())} onChange={e => setCustomTo(e.target.value)} style={{padding:'8px 10px', border:'1px solid var(--line-1)', borderRadius:8, fontSize:14}} />
          </label>
        </div>
      )}

      <div style={{padding:'0 32px 16px', display:'flex', gap:6, flexWrap:'wrap', alignItems:'center', fontSize:13}}>
        <span className="muted">Source :</span>
        {[{id:'all',label:'Tout'},{id:'app',label:'Ventes app'},{id:'caisse',label:'Caisse (PDF)'}].map(s => (
          <button key={s.id} className="chip" aria-pressed={source === s.id} onClick={() => setSource(s.id)}>{s.label}</button>
        ))}
      </div>

      <div className="kpi-grid">
        <div className="kpi">
          <div className="kpi-icon"><Icon name="euro" /></div>
          <div className="kpi-label">CA TTC</div>
          <div className="kpi-value">{formatPrice(kpi.ca_ttc).euros}<span className="unit">,{formatPrice(kpi.ca_ttc).cents} €</span></div>
          <div className="kpi-delta" style={{display:'flex', flexDirection:'column', gap:2, alignItems:'flex-start'}}>
            <div><DeltaBadge value={deltaPct(kpi.ca_ttc, prevKpi.ca_ttc)} /> <span style={{fontSize:10, color:'var(--ink-3)'}}>vs période préc.</span></div>
            {yearAgoKpi.ca_ttc > 0 && (
              <div><DeltaBadge value={deltaPct(kpi.ca_ttc, yearAgoKpi.ca_ttc)} /> <span style={{fontSize:10, color:'var(--ink-3)'}}>vs N-1 ({formatPrice(yearAgoKpi.ca_ttc).full})</span></div>
            )}
          </div>
        </div>
        <div className="kpi">
          <div className="kpi-icon"><Icon name="tag" /></div>
          <div className="kpi-label">CA HT</div>
          <div className="kpi-value">{formatPrice(kpi.ca_ht).euros}<span className="unit">,{formatPrice(kpi.ca_ht).cents} €</span></div>
          <div className="kpi-delta" style={{display:'flex', flexDirection:'column', gap:2, alignItems:'flex-start'}}>
            <div><DeltaBadge value={deltaPct(kpi.ca_ht, prevKpi.ca_ht)} /> <span style={{fontSize:10, color:'var(--ink-3)'}}>vs période préc.</span></div>
            {yearAgoKpi.ca_ht > 0 && (
              <div><DeltaBadge value={deltaPct(kpi.ca_ht, yearAgoKpi.ca_ht)} /> <span style={{fontSize:10, color:'var(--ink-3)'}}>vs N-1</span></div>
            )}
          </div>
        </div>
        <div className="kpi">
          <div className="kpi-icon"><Icon name="tag" /></div>
          <div className="kpi-label">Marge HT</div>
          <div className="kpi-value">{formatPrice(kpi.marge_ht).euros}<span className="unit">,{formatPrice(kpi.marge_ht).cents} €</span></div>
          <div className="kpi-delta" style={{display:'flex', flexDirection:'column', gap:2, alignItems:'flex-start'}}>
            <div>{kpi.marge_rate.toFixed(1).replace('.', ',')} % · <DeltaBadge value={deltaPct(kpi.marge_ht, prevKpi.marge_ht)} /> <span style={{fontSize:10, color:'var(--ink-3)'}}>vs période préc.</span></div>
            {yearAgoKpi.marge_ht > 0 && (
              <div><DeltaBadge value={deltaPct(kpi.marge_ht, yearAgoKpi.marge_ht)} /> <span style={{fontSize:10, color:'var(--ink-3)'}}>vs N-1</span></div>
            )}
          </div>
        </div>
        <div className="kpi">
          <div className="kpi-icon"><Icon name="list" /></div>
          <div className="kpi-label">Jours importés</div>
          <div className="kpi-value">{kpi.nb_imports_caisse}<span className="unit"> jour{kpi.nb_imports_caisse > 1 ? 's' : ''}</span></div>
          <div className="kpi-delta">
            <span style={{fontSize:11, color:'var(--ink-3)'}}>imports PDF caisse Casino sur la période</span>
          </div>
        </div>
      </div>

      {/* Marge reelle BL vs marge catalogue */}
      <MargeReelleBadgeCard />

      {/* CA previsionnel fin de mois */}
      <CaPrevisionnelCard />

      {/* Ruptures imminentes */}
      {stockEnabled && <RuptureImminenteCard onClick={() => setShowRupture(true)} />}
      {stockEnabled && showRupture && <RuptureImminenteModal open={showRupture} onClose={() => setShowRupture(false)} />}

      {/* Tableau par rayon */}
      {byRayon.length > 0 && (
        <div className="card" style={{margin:'0 32px 24px', padding:'20px 24px'}}>
          <h2 style={{fontSize:16, fontWeight:800, margin:'0 0 12px'}}>Répartition par rayon</h2>
          <table style={{width:'100%', borderCollapse:'collapse', fontSize:13}}>
            <thead>
              <tr style={{background:'var(--surface-1)', textAlign:'left'}}>
                <th style={{padding:'8px 10px'}}>Rayon</th>
                <th style={{padding:'8px 10px', textAlign:'right'}}>CA TTC</th>
                <th style={{padding:'8px 10px', textAlign:'right'}}>Marge HT</th>
                <th style={{padding:'8px 10px', textAlign:'right'}}>Taux</th>
              </tr>
            </thead>
            <tbody>
              {byRayon.flatMap(r => {
                const rayonObj = RAYONS.find(x => x.id === r.rayon);
                const rayonName = rayonObj?.name || (r.rayon === 'non-classe' ? 'Non classé' : r.rayon);
                const rate = r.ca_ht > 0 ? (r.marge_ht / r.ca_ht) * 100 : 0;
                const isOpen = !!expanded[r.rayon];
                const TRACKED_RAYONS = ['fruits-et-legumes', 'boulangerie-et-patisserie', 'viandes-et-poissons', 'produits-frais'];
                const isTracked = TRACKED_RAYONS.includes(r.rayon) && (r.nb_plu_internal || 0) > 0;
                const rowMain = (
                  <tr key={'r-' + r.rayon}
                      onClick={() => setExpanded(s => ({...s, [r.rayon]: !isOpen}))}
                      style={{cursor:'pointer', borderBottom:'1px solid var(--line-2)'}}>
                    <td style={{padding:'8px 10px', fontWeight:700}}>
                      {isOpen ? '▼' : '▶'} {rayonName}
                      <span className="muted" style={{marginLeft:8, fontSize:11, fontWeight:400}}>{r.nb_lignes} lignes{r.nb_plu_internal ? ` · ${r.nb_plu_internal} PLU caisse` : ''}</span>
                    </td>
                    <td style={{padding:'8px 10px', textAlign:'right'}}>{formatPrice(r.ca_ttc).full}</td>
                    <td style={{padding:'8px 10px', textAlign:'right', fontWeight:700, color: isTracked ? 'var(--ink-3)' : '#16a34a'}}>
                      {isTracked
                        ? <span onClick={(e) => { e.stopPropagation(); setTab('fl'); }}
                            style={{fontSize:11, color:'var(--vival-red)', cursor:'pointer', textDecoration:'underline', fontWeight:600}}>
                            → voir onglet Marges rayon
                          </span>
                        : formatPrice(r.marge_ht).full}
                    </td>
                    <td style={{padding:'8px 10px', textAlign:'right', fontWeight:700}}>
                      {isTracked ? <span className="muted" style={{fontSize:11}}>—</span> : rate.toFixed(1).replace('.', ',') + ' %'}
                    </td>
                  </tr>
                );
                if (!isOpen) return [rowMain];
                const subRows = [...r.subs.values()].sort((a,b) => b.ca_ttc - a.ca_ttc).flatMap(s => {
                  const srate = s.ca_ht > 0 ? (s.marge_ht / s.ca_ht) * 100 : 0;
                  const subKey = r.rayon + '::' + s.sub;
                  const isSubOpen = !!expanded[subKey];
                  const hasSubFam = s.subFamilies && s.subFamilies.size > 0
                    && !(s.subFamilies.size === 1 && s.subFamilies.has('Non classé'));
                  const subRow = (
                    <tr key={'s-' + subKey}
                        onClick={hasSubFam ? () => setExpanded(st => ({...st, [subKey]: !isSubOpen})) : undefined}
                        style={{background:'#f9fafb', borderBottom:'1px solid var(--line-2)', cursor: hasSubFam ? 'pointer' : 'default'}}>
                      <td style={{padding:'6px 10px 6px 38px', fontSize:12}}>
                        {hasSubFam ? (isSubOpen ? '▼ ' : '▶ ') : ''}{s.sub} <span className="muted">· {s.nb_lignes}</span>
                      </td>
                      <td style={{padding:'6px 10px', textAlign:'right', fontSize:12}}>{formatPrice(s.ca_ttc).full}</td>
                      <td style={{padding:'6px 10px', textAlign:'right', fontSize:12, color:'#16a34a'}}>{formatPrice(s.marge_ht).full}</td>
                      <td style={{padding:'6px 10px', textAlign:'right', fontSize:12}}>{srate.toFixed(1).replace('.', ',')} %</td>
                    </tr>
                  );
                  if (!isSubOpen || !hasSubFam) return [subRow];
                  const subFamRows = [...s.subFamilies.values()].sort((a,b) => b.ca_ttc - a.ca_ttc).map(sf => {
                    const sfrate = sf.ca_ht > 0 ? (sf.marge_ht / sf.ca_ht) * 100 : 0;
                    return (
                      <tr key={'sf-' + subKey + '-' + sf.sub_family} style={{background:'#f3f4f6', borderBottom:'1px solid var(--line-2)'}}>
                        <td style={{padding:'4px 10px 4px 64px', fontSize:11, color:'var(--ink-2)'}}>
                          {sf.sub_family} <span className="muted">· {sf.nb_lignes}</span>
                        </td>
                        <td style={{padding:'4px 10px', textAlign:'right', fontSize:11}}>{formatPrice(sf.ca_ttc).full}</td>
                        <td style={{padding:'4px 10px', textAlign:'right', fontSize:11, color:'#16a34a'}}>{formatPrice(sf.marge_ht).full}</td>
                        <td style={{padding:'4px 10px', textAlign:'right', fontSize:11}}>{sfrate.toFixed(1).replace('.', ',')} %</td>
                      </tr>
                    );
                  });
                  return [subRow, ...subFamRows];
                });
                return [rowMain, ...subRows];
              })}
            </tbody>
          </table>
        </div>
      )}

      {/* Top 20 produits */}
      {topProducts.length > 0 && (
        <div className="card" style={{margin:'0 32px 24px', padding:'20px 24px'}}>
          <h2 style={{fontSize:16, fontWeight:800, margin:'0 0 12px'}}>Top 20 produits</h2>
          <table style={{width:'100%', borderCollapse:'collapse', fontSize:13}}>
            <thead>
              <tr style={{background:'var(--surface-1)', textAlign:'left'}}>
                <th style={{padding:'8px 10px', width:36}}>#</th>
                <th style={{padding:'8px 10px'}}>Produit</th>
                <th style={{padding:'8px 10px', textAlign:'right'}}>Qté</th>
                <th style={{padding:'8px 10px', textAlign:'right'}}>CA TTC</th>
                <th style={{padding:'8px 10px', textAlign:'right'}}>Marge HT</th>
              </tr>
            </thead>
            <tbody>
              {topProducts.map((p, i) => (
                <tr key={p.ean || p.name} style={{borderBottom:'1px solid var(--line-2)'}}>
                  <td style={{padding:'8px 10px', fontWeight:700, color:'var(--ink-3)'}}>#{i+1}</td>
                  <td style={{padding:'8px 10px'}}>
                    <div style={{fontWeight:600}}>{p.name || '—'}</div>
                    {p.ean && <div className="muted" style={{fontSize:11, fontFamily:'var(--font-mono)'}}>EAN {p.ean}</div>}
                  </td>
                  <td style={{padding:'8px 10px', textAlign:'right', fontVariantNumeric:'tabular-nums'}}>{p.qty % 1 === 0 ? p.qty : p.qty.toFixed(2)}</td>
                  <td style={{padding:'8px 10px', textAlign:'right', fontWeight:700}}>{formatPrice(p.ca_ttc).full}</td>
                  <td style={{padding:'8px 10px', textAlign:'right', color:'#16a34a'}}>{formatPrice(p.marge_ht).full}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}

      {!loading && lines.length === 0 && (
        <div className="empty-state" style={{margin:'40px 32px'}}>
          <Icon name="list" />
          <div>Aucune vente sur la période sélectionnée</div>
          <div className="muted" style={{fontSize:13, marginTop:8}}>
            Clique sur « Importer export caisse » en haut pour charger un PDF, ou passe une commande depuis la Vente.
          </div>
        </div>
      )}

      </>}

      {detailImportId && <SalesImportDetailModal importId={detailImportId} onClose={() => setDetailImportId(null)} />}
      {importOpen && <SalesImportModal onClose={() => { setImportOpen(false); setReloadKey(k => k + 1); }} />}
      {inventoryOpen && <InventoryImportModal onClose={() => { setInventoryOpen(false); setReloadKey(k => k + 1); }} onDone={() => setReloadKey(k => k + 1)} />}
    </>
  );
}

// ========== Historique des imports caisse ==========
function SalesImportsHistory({ onOpenImport }) {
  const [imports, setImports] = useState(null);
  const [deleting, setDeleting] = useState(null);

  const load = async () => {
    const { data } = await window.sb
      .from('sales_imports')
      .select('id, created_at, uploaded_by, filename, source_format, sale_date_from, sale_date_to, total_lines, total_ca_ttc, total_ca_ht, total_marge_ht, unmatched_ean_count')
      .order('created_at', { ascending: false })
      .limit(50);
    setImports(data || []);
  };

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

  const deleteImport = async (id, name, ca, dateFrom, dateTo) => {
    const dateRange = dateFrom && dateTo
      ? `du ${new Date(dateFrom).toLocaleDateString('fr-FR')} au ${new Date(dateTo).toLocaleDateString('fr-FR')}`
      : '';
    const caStr = ca ? `${formatPrice(ca).full} de CA TTC` : '';
    const msg = `Supprimer définitivement cet import ?\n\n` +
      `Fichier : ${name || id}\n` +
      (dateRange ? `Période : ${dateRange}\n` : '') +
      (caStr ? `\n⚠ ${caStr} disparaîtra aussi des rapports (onglet Par période).\n` : '') +
      `\nCette action est irréversible.`;
    if (!confirm(msg)) return;
    setDeleting(id);
    try {
      await window.sb.from('sales_import_lines').delete().eq('import_id', id);
      await window.sb.from('sales_imports').delete().eq('id', id);
      await load();
    } catch (e) {
      alert('Erreur suppression : ' + e.message);
    } finally {
      setDeleting(null);
    }
  };

  if (imports === null) return <div className="muted" style={{padding:'40px 32px', textAlign:'center'}}>Chargement…</div>;
  if (imports.length === 0) return (
    <div className="empty-state" style={{margin:'40px 32px'}}>
      <Icon name="list" />
      <div>Aucun import enregistré pour le moment</div>
      <div className="muted" style={{fontSize:13, marginTop:8}}>
        Clique sur « Importer export caisse » pour charger un PDF et l'enregistrer.
      </div>
    </div>
  );

  return (
    <div style={{padding:'0 32px 32px'}}>
      <table style={{width:'100%', borderCollapse:'collapse', fontSize:13, background:'#fff', borderRadius:10, overflow:'hidden'}}>
        <thead>
          <tr style={{background:'var(--surface-1)', textAlign:'left'}}>
            <th style={{padding:'10px 12px'}}>Import</th>
            <th style={{padding:'10px 12px'}}>Période de vente</th>
            <th style={{padding:'10px 12px', textAlign:'right'}}>Lignes</th>
            <th style={{padding:'10px 12px', textAlign:'right'}}>CA TTC</th>
            <th style={{padding:'10px 12px', textAlign:'right'}}>Marge HT</th>
            <th style={{padding:'10px 12px', textAlign:'right'}}>Taux</th>
            <th style={{padding:'10px 12px', textAlign:'right'}}></th>
          </tr>
        </thead>
        <tbody>
          {imports.map(imp => {
            const rate = imp.total_ca_ht > 0 ? (imp.total_marge_ht / imp.total_ca_ht) * 100 : 0;
            const fmtD = (d) => d ? new Date(d).toLocaleDateString('fr-FR', {day:'2-digit', month:'short', year:'2-digit'}) : '—';
            return (
              <tr key={imp.id} style={{borderBottom:'1px solid var(--line-2)', cursor:'pointer'}} onClick={() => onOpenImport(imp.id)}>
                <td style={{padding:'10px 12px'}}>
                  <div style={{fontWeight:700}}>{imp.filename || '—'}</div>
                  <div className="muted" style={{fontSize:11, marginTop:2}}>
                    {imp.source_format?.toUpperCase()} · {new Date(imp.created_at).toLocaleString('fr-FR', {day:'2-digit', month:'short', year:'numeric', hour:'2-digit', minute:'2-digit'})}
                    {imp.uploaded_by && ` · ${imp.uploaded_by}`}
                  </div>
                </td>
                <td style={{padding:'10px 12px'}}>
                  <div>{fmtD(imp.sale_date_from)} → {fmtD(imp.sale_date_to)}</div>
                </td>
                <td style={{padding:'10px 12px', textAlign:'right', fontVariantNumeric:'tabular-nums'}}>
                  {imp.total_lines}
                  {imp.unmatched_ean_count > 0 && <div style={{fontSize:11, color:'#991b1b'}}>{imp.unmatched_ean_count} non reconnus</div>}
                </td>
                <td style={{padding:'10px 12px', textAlign:'right', fontWeight:700, fontVariantNumeric:'tabular-nums'}}>{formatPrice(imp.total_ca_ttc || 0).full}</td>
                <td style={{padding:'10px 12px', textAlign:'right', fontWeight:700, color:'#16a34a', fontVariantNumeric:'tabular-nums'}}>{formatPrice(imp.total_marge_ht || 0).full}</td>
                <td style={{padding:'10px 12px', textAlign:'right', fontWeight:700, fontVariantNumeric:'tabular-nums'}}>{rate.toFixed(1).replace('.', ',')} %</td>
                <td style={{padding:'10px 12px', textAlign:'right'}} onClick={e => e.stopPropagation()}>
                  <Button variant="ghost" size="sm" icon="trash" onClick={() => deleteImport(imp.id, imp.filename, imp.total_ca_ttc, imp.sale_date_from, imp.sale_date_to)} disabled={deleting === imp.id} />
                </td>
              </tr>
            );
          })}
        </tbody>
      </table>
    </div>
  );
}

// ========== Détail d'un import passé ==========
function SalesImportDetailModal({ importId, onClose }) {
  const [imp, setImp] = useState(null);
  const [lines, setLines] = useState([]);
  const [loading, setLoading] = useState(true);
  const [expanded, setExpanded] = useState({});

  useEffect(() => {
    (async () => {
      setLoading(true);
      const [impRes, linesRes] = await Promise.all([
        window.sb.from('sales_imports').select('*').eq('id', importId).single(),
        window.sb.from('sales_import_lines').select('*').eq('import_id', importId).limit(5000),
      ]);
      setImp(impRes.data);
      setLines(linesRes.data || []);
      setLoading(false);
    })();
  }, [importId]);

  const matched = lines.filter(l => l.matched);
  const unmatched = lines.filter(l => !l.matched);
  const totalTtc = lines.reduce((s, l) => s + (Number(l.ca_ttc) || 0), 0);
  const totalHt = matched.reduce((s, l) => s + (Number(l.ca_ht) || 0), 0);
  const totalMarge = matched.reduce((s, l) => s + (Number(l.marge_ht) || 0), 0);
  const rate = totalHt > 0 ? (totalMarge / totalHt) * 100 : 0;

  // Agrégation par rayon
  const byRayon = useMemo(() => {
    const map = new Map();
    for (const l of lines) {
      if (!l.matched) continue;
      const key = l.rayon || 'non-classe';
      if (!map.has(key)) map.set(key, { rayon: key, ca_ttc: 0, ca_ht: 0, marge_ht: 0, nb_lignes: 0, subs: new Map() });
      const a = map.get(key);
      a.ca_ttc += Number(l.ca_ttc) || 0;
      a.ca_ht += Number(l.ca_ht) || 0;
      a.marge_ht += Number(l.marge_ht) || 0;
      a.nb_lignes++;
      const sk = l.sub || 'Tout';
      if (!a.subs.has(sk)) a.subs.set(sk, { sub: sk, ca_ttc: 0, ca_ht: 0, marge_ht: 0, nb_lignes: 0 });
      const s = a.subs.get(sk);
      s.ca_ttc += Number(l.ca_ttc) || 0;
      s.ca_ht += Number(l.ca_ht) || 0;
      s.marge_ht += Number(l.marge_ht) || 0;
      s.nb_lignes++;
    }
    return [...map.values()].sort((a, b) => b.ca_ttc - a.ca_ttc);
  }, [lines]);

  const fmtD = (d) => d ? new Date(d).toLocaleDateString('fr-FR', {day:'2-digit', month:'long', year:'numeric'}) : '—';

  return (
    <Modal open={true} onClose={onClose} size="xl" title={imp ? `Import ${imp.filename || ''}` : 'Chargement…'}
      footer={<Button variant="ghost" onClick={onClose}>Fermer</Button>}
    >
      {loading && <div className="muted" style={{padding:40, textAlign:'center'}}>Chargement…</div>}
      {!loading && imp && (
        <div>
          <div style={{padding:'10px 14px', background:'var(--surface-1)', borderRadius:8, marginBottom:16, fontSize:13}}>
            <div><strong>Période de vente :</strong> {fmtD(imp.sale_date_from)} → {fmtD(imp.sale_date_to)}</div>
            <div className="muted" style={{marginTop:4, fontSize:12}}>
              Import créé le {new Date(imp.created_at).toLocaleString('fr-FR')} par {imp.uploaded_by || 'admin'}
            </div>
          </div>

          <div style={{display:'grid', gridTemplateColumns:'repeat(auto-fit, minmax(160px, 1fr))', gap:12, marginBottom:20}}>
            <div style={{padding:14, background:'var(--surface-1)', borderRadius:10}}>
              <div style={{fontSize:11, textTransform:'uppercase', color:'var(--ink-3)', fontWeight:700}}>CA TTC total</div>
              <div style={{fontSize:22, fontWeight:800, marginTop:4}}>{formatPrice(totalTtc).full}</div>
              <div style={{fontSize:11, color:'var(--ink-3)'}}>{lines.length} lignes</div>
            </div>
            <div style={{padding:14, background:'#dcfce7', borderRadius:10}}>
              <div style={{fontSize:11, textTransform:'uppercase', color:'#166534', fontWeight:700}}>Marge HT</div>
              <div style={{fontSize:22, fontWeight:800, marginTop:4, color:'#14532d'}}>{formatPrice(totalMarge).full}</div>
              <div style={{fontSize:11, color:'#166534'}}>{rate.toFixed(1).replace('.', ',')} %</div>
            </div>
            {unmatched.length > 0 && (
              <div style={{padding:14, background:'#fee2e2', borderRadius:10}}>
                <div style={{fontSize:11, textTransform:'uppercase', color:'#991b1b', fontWeight:700}}>EAN non reconnus</div>
                <div style={{fontSize:22, fontWeight:800, marginTop:4, color:'#991b1b'}}>{unmatched.length}</div>
              </div>
            )}
          </div>

          {byRayon.length > 0 && (
            <div>
              <h3 style={{fontSize:15, fontWeight:800, margin:'0 0 10px'}}>Marge par rayon</h3>
              <table style={{width:'100%', borderCollapse:'collapse', fontSize:13, marginBottom:20}}>
                <thead>
                  <tr style={{background:'var(--surface-1)', textAlign:'left'}}>
                    <th style={{padding:'8px 10px'}}>Rayon</th>
                    <th style={{padding:'8px 10px', textAlign:'right'}}>CA TTC</th>
                    <th style={{padding:'8px 10px', textAlign:'right'}}>Marge HT</th>
                    <th style={{padding:'8px 10px', textAlign:'right'}}>Taux</th>
                  </tr>
                </thead>
                <tbody>
                  {byRayon.flatMap(r => {
                    const rayonName = window.VivalData.RAYONS.find(x => x.id === r.rayon)?.name || (r.rayon === 'non-classe' ? 'Non classé' : r.rayon);
                    const rrate = r.ca_ht > 0 ? (r.marge_ht / r.ca_ht) * 100 : 0;
                    const isOpen = !!expanded[r.rayon];
                    const rowMain = (
                      <tr key={'r-' + r.rayon} onClick={() => setExpanded(s => ({...s, [r.rayon]: !isOpen}))} style={{cursor:'pointer', borderBottom:'1px solid var(--line-2)'}}>
                        <td style={{padding:'8px 10px', fontWeight:700}}>
                          {isOpen ? '▼' : '▶'} {rayonName}
                          <span className="muted" style={{marginLeft:8, fontSize:11, fontWeight:400}}>{r.nb_lignes} lignes</span>
                        </td>
                        <td style={{padding:'8px 10px', textAlign:'right'}}>{formatPrice(r.ca_ttc).full}</td>
                        <td style={{padding:'8px 10px', textAlign:'right', fontWeight:700, color:'#16a34a'}}>{formatPrice(r.marge_ht).full}</td>
                        <td style={{padding:'8px 10px', textAlign:'right', fontWeight:700}}>{rrate.toFixed(1).replace('.', ',')} %</td>
                      </tr>
                    );
                    if (!isOpen) return [rowMain];
                    const subRows = [...r.subs.values()].sort((a,b) => b.ca_ttc - a.ca_ttc).map(s => {
                      const srate = s.ca_ht > 0 ? (s.marge_ht / s.ca_ht) * 100 : 0;
                      return (
                        <tr key={'s-' + r.rayon + '-' + s.sub} style={{background:'#f9fafb', borderBottom:'1px solid var(--line-2)'}}>
                          <td style={{padding:'6px 10px 6px 38px', fontSize:12}}>{s.sub} <span className="muted">· {s.nb_lignes}</span></td>
                          <td style={{padding:'6px 10px', textAlign:'right', fontSize:12}}>{formatPrice(s.ca_ttc).full}</td>
                          <td style={{padding:'6px 10px', textAlign:'right', fontSize:12, color:'#16a34a'}}>{formatPrice(s.marge_ht).full}</td>
                          <td style={{padding:'6px 10px', textAlign:'right', fontSize:12}}>{srate.toFixed(1).replace('.', ',')} %</td>
                        </tr>
                      );
                    });
                    return [rowMain, ...subRows];
                  })}
                </tbody>
              </table>
            </div>
          )}
        </div>
      )}
    </Modal>
  );
}

function TopProducts({ orders }) {
  const since = Date.now() - 7 * 86400000;
  const counts = {};
  orders.filter(o => new Date(o.createdAt).getTime() >= since && o.status !== 'annulee').forEach(o => {
    o.lines.forEach(l => {
      counts[l.pid] = (counts[l.pid] || 0) + (l.qty || 0);
    });
  });
  const top = Object.entries(counts)
    .sort((a,b) => b[1] - a[1])
    .slice(0, 5)
    .map(([pid, qty]) => ({ p: PRODUCTS.find(x => x.id === pid), qty }))
    .filter(x => x.p);
  if (top.length === 0) return null;
  return (
    <div className="dash-top-products" style={{margin:'0 32px 16px', padding:'16px 20px', background:'#fff', border:'1px solid var(--line-1)', borderRadius:'var(--r-lg)'}}>
      <div style={{display:'flex', alignItems:'center', marginBottom:12}}>
        <h3 style={{margin:0, fontSize:14, fontWeight:800, flex:1}}>Top produits · 7 derniers jours</h3>
      </div>
      <div style={{display:'grid', gap:6}}>
        {top.map((t, i) => (
          <div key={t.p.id} style={{display:'flex', alignItems:'center', gap:10, padding:'6px 0'}}>
            <div style={{width:24, height:24, borderRadius:6, background:'var(--surface-2)', display:'flex', alignItems:'center', justifyContent:'center', fontWeight:700, fontSize:12}}>{i+1}</div>
            <div style={{flex:1, minWidth:0}}>
              <div style={{fontWeight:600, fontSize:13, whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis'}}>{t.p.name}</div>
              <div className="muted" style={{fontSize:11}}>{t.p.format} · {formatPrice(t.p.price).full}</div>
            </div>
            <div style={{fontWeight:700, fontSize:14, fontVariantNumeric:'tabular-nums'}}>{t.qty}<span className="muted" style={{fontWeight:400, fontSize:11}}> vendus</span></div>
          </div>
        ))}
      </div>
    </div>
  );
}

// ===== Orders list + details =====
function OrderList({ orders, title, onOpen }) {
  const { setSelectedOrder } = useApp();
  const open = onOpen || setSelectedOrder;
  return (
    <div className="orderlist">
      <div className="orderlist-header">
        <h3 className="orderlist-title">{title}</h3>
        <span className="pill">{orders.length}</span>
      </div>
      {orders.length === 0 ? (
        <div className="empty-state"><Icon name="list" /><div>Aucune commande</div></div>
      ) : orders.map(o => {
        const customer = CUSTOMERS.find(c => c.id === o.customerId);
        const name = customer?.name || o.customerName || 'Client libre';
        const total = o.lines.reduce((s, l) => {
          if (l.manual) return s + (Number(l.unitPrice ?? l.price) || 0) * l.qty;
          const p = PRODUCTS.find(x => x.id === l.pid);
          return s + (p ? p.price * l.qty : 0);
        }, 0);
        const t = new Date(o.createdAt);
        return (
          <div key={o.id} className="order-row" onClick={() => open(o)}>
            <div className="order-avatar">{name.split(' ').slice(-1)[0][0]}</div>
            <div>
              <div className="order-name">
                {name}
                {o.source === 'online' && <span style={{marginLeft:8, background:'#dbeafe', color:'#1e40af', fontSize:10, fontWeight:700, padding:'2px 7px', borderRadius:10, verticalAlign:'middle'}}>EN LIGNE · {o.deliveryMode === 'delivery' ? 'LIVRAISON' : 'RETRAIT'}</span>}
              </div>
              <div className="order-meta col-meta">{o.id} · {o.lines.length} articles · {t.toLocaleTimeString('fr-FR', {hour:'2-digit', minute:'2-digit'})}{o.customerPhone && ` · ${o.customerPhone}`}</div>
            </div>
            <div className="order-total col-total">{formatPrice(total).full}</div>
            <div className="col-status"><StatusPill status={o.status} /></div>
            <Icon name="chevron-right" style={{color:'var(--ink-4)'}} />
          </div>
        );
      })}
    </div>
  );
}

function OrdersScreen() {
  const { orders, setRoute } = useApp();
  const [tab, setTab] = useState('today');
  const [q, setQ] = useState('');
  const [status, setStatus] = useState('all');
  const today = orders.filter(o => new Date(o.createdAt).toDateString() === new Date().toDateString());
  const history = orders.filter(o => new Date(o.createdAt).toDateString() !== new Date().toDateString());
  let list = tab === 'today' ? today : history;
  if (status !== 'all') list = list.filter(o => o.status === status);
  if (q.trim()) {
    const qq = q.toLowerCase();
    list = list.filter(o =>
      (o.id || '').toLowerCase().includes(qq) ||
      (o.customerName || '').toLowerCase().includes(qq) ||
      (CUSTOMERS.find(c => c.id === o.customerId)?.name || '').toLowerCase().includes(qq)
    );
  }
  return (
    <>
      <div className="page-header">
        <div className="page-header-grow">
          <h1 className="page-title">Commandes</h1>
          <p className="page-subtitle">{today.length} aujourd'hui · {history.length} dans l'historique</p>
        </div>
        <div className="segmented">
          <button aria-pressed={tab==='today'} onClick={() => setTab('today')}>Aujourd'hui</button>
          <button aria-pressed={tab==='history'} onClick={() => setTab('history')}>Historique</button>
        </div>
        <Button variant="outline" icon="download" onClick={() => exportOrdersCsv(list)}>Exporter CSV</Button>
        <Button variant="primary" icon="plus" onClick={() => setRoute('catalog')}>Nouvelle</Button>
      </div>
      <div style={{padding:'0 32px 12px', display:'flex', gap:10, flexWrap:'wrap'}}>
        <div style={{flex:'1 1 260px', minWidth:220}}>
          <Input icon="search" placeholder="Rechercher par N° ou client…" value={q} onChange={e => setQ(e.target.value)} />
        </div>
        <select value={status} onChange={e => setStatus(e.target.value)} style={{padding:'10px 12px', border:'1px solid var(--line)', borderRadius:8, fontSize:14, background:'#fff'}}>
          <option value="all">Tous statuts</option>
          <option value="en-cours">En cours</option>
          <option value="prete">Prête</option>
          <option value="livree">Livrée</option>
          <option value="payee">Payée</option>
          <option value="annulee">Annulée</option>
        </select>
      </div>
      <OrderList orders={list} title={tab === 'today' ? 'Aujourd\'hui' : 'Commandes passées'} />
    </>
  );
}

function ReceivingModal() {
  const { receivingOpen, setReceivingOpen, showToast } = useApp();
  const [q, setQ] = useState('');
  const [items, setItems] = useState([]); // [{pid, qty}]
  const [saving, setSaving] = useState(false);
  if (!receivingOpen) return null;
  const matches = q.trim() ? PRODUCTS.filter(p => !p.deleted && ((p.name||'').toLowerCase().includes(q.toLowerCase()) || (p.barcode||'').includes(q))).slice(0, 10) : [];

  const addItem = (p) => {
    setItems(prev => {
      const ex = prev.find(i => i.pid === p.id);
      if (ex) return prev.map(i => i.pid === p.id ? { ...i, qty: i.qty + 1 } : i);
      return [...prev, { pid: p.id, qty: 1 }];
    });
    setQ('');
  };
  const setQty = (pid, qty) => setItems(prev => prev.map(i => i.pid === pid ? { ...i, qty: Math.max(0, qty) } : i).filter(i => i.qty > 0));

  const save = async () => {
    if (items.length === 0) { setReceivingOpen(false); return; }
    setSaving(true);
    try {
      for (const it of items) {
        const p = PRODUCTS.find(x => x.id === it.pid);
        if (!p) continue;
        const newStock = (p.stock || 0) + it.qty;
        p.stock = newStock;
        await window.sb.from('products').update({ stock: newStock }).eq('id', p.id);
        await window.sb.from('stock_movements').insert({ product_id: p.id, qty_change: it.qty, reason: 'reception', store_id: VivalData._storeId });
      }
      showToast(`Réception : ${items.reduce((s,i) => s+i.qty, 0)} unités ajoutées`);
      setItems([]);
      setReceivingOpen(false);
    } catch (e) {
      showToast('Erreur: ' + (e.message || e));
    }
    setSaving(false);
  };

  return (
    <Modal open={true} onClose={() => { setItems([]); setReceivingOpen(false); }} size="lg" title="Réception marchandise"
      footer={<>
        <Button variant="ghost" onClick={() => { setItems([]); setReceivingOpen(false); }}>Annuler</Button>
        <Button variant="primary" icon="check" onClick={save} disabled={saving || items.length === 0}>Valider l'entrée stock</Button>
      </>}>
      <p className="muted" style={{marginTop:0, fontSize:13}}>Scanne ou recherche les produits reçus, puis valide pour mettre à jour les stocks.</p>
      <Input icon="search" autoFocus placeholder="Nom, EAN…" value={q} onChange={e => setQ(e.target.value)} />
      {matches.length > 0 && (
        <div style={{maxHeight:200, overflowY:'auto', border:'1px solid var(--line-2)', borderRadius:8, marginTop:8}}>
          {matches.map(p => (
            <div key={p.id} onClick={() => addItem(p)} style={{padding:'8px 12px', display:'flex', justifyContent:'space-between', alignItems:'center', cursor:'pointer', borderBottom:'1px solid var(--line-2)'}}>
              <div>
                <div style={{fontWeight:600, fontSize:13}}>{p.name}</div>
                <div className="muted" style={{fontSize:11}}>{p.format} · stock {p.stock}</div>
              </div>
              <Icon name="plus" />
            </div>
          ))}
        </div>
      )}
      {items.length > 0 && (
        <div style={{marginTop:16}}>
          <div className="kpi-label" style={{marginBottom:8}}>À réceptionner ({items.reduce((s,i) => s+i.qty, 0)} unités)</div>
          <div style={{display:'grid', gap:6}}>
            {items.map(it => {
              const p = PRODUCTS.find(x => x.id === it.pid);
              return (
                <div key={it.pid} style={{display:'flex', gap:10, alignItems:'center', padding:'8px 10px', background:'var(--surface-1)', borderRadius:6}}>
                  <div style={{flex:1, minWidth:0}}>
                    <div style={{fontWeight:600, fontSize:13}}>{p?.name}</div>
                    <div className="muted" style={{fontSize:11}}>Stock actuel: {p?.stock} → futur: {(p?.stock||0) + it.qty}</div>
                  </div>
                  <QtyControl value={it.qty} onChange={q => setQty(it.pid, q)} step={1} />
                  <Button variant="ghost" size="sm" icon="close" onClick={() => setQty(it.pid, 0)} />
                </div>
              );
            })}
          </div>
        </div>
      )}
    </Modal>
  );
}

function exportOrdersCsv(orders) {
  const rows = [['id', 'date', 'heure', 'client', 'articles', 'total_ttc', 'paiement', 'statut']];
  orders.forEach(o => {
    const d = new Date(o.createdAt);
    rows.push([
      o.id,
      d.toLocaleDateString('fr-FR'),
      d.toLocaleTimeString('fr-FR', {hour:'2-digit', minute:'2-digit'}),
      (o.customerName || 'Client libre').replace(/"/g, '""'),
      o.lines.length,
      (o.totalTtc || 0).toFixed(2).replace('.', ','),
      o.payment || '',
      o.status || '',
    ]);
  });
  const csv = '\uFEFF' + rows.map(r => r.map(c => /[,;"\n]/.test(String(c)) ? `"${c}"` : c).join(';')).join('\n');
  const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = `commandes-vival-${new Date().toISOString().slice(0,10)}.csv`;
  a.click();
  URL.revokeObjectURL(url);
}

function OrderDetailModal() {
  const { selectedOrder, setSelectedOrder, setPrintOrder, setRoute, setOrders, orders, showToast } = useApp();
  const [editing, setEditing] = useState(false);
  const [editLines, setEditLines] = useState([]);
  useEffect(() => { if (selectedOrder) { setEditing(false); setEditLines(selectedOrder.lines || []); } }, [selectedOrder?.id]);
  if (!selectedOrder) return null;
  const o = selectedOrder;
  const customer = CUSTOMERS.find(c => c.id === o.customerId);
  const currentLines = editing ? editLines : o.lines;
  const total = currentLines.reduce((s, l) => {
    const p = PRODUCTS.find(x => x.id === l.pid);
    return s + (p ? p.price * l.qty : 0);
  }, 0);
  const saveEdits = async () => {
    // Compute diff for stock restoration/removal
    const newLines = editLines.filter(l => l.qty > 0);
    const diffs = {};
    o.lines.forEach(l => { diffs[l.pid] = (diffs[l.pid] || 0) - l.qty; });
    newLines.forEach(l => { diffs[l.pid] = (diffs[l.pid] || 0) + l.qty; });
    // Apply stock delta (negative diff = restored, positive = taken)
    for (const pid in diffs) {
      const p = PRODUCTS.find(x => x.id === pid);
      if (!p || p.byWeight) continue;
      p.stock = Math.max(0, (p.stock || 0) - diffs[pid]);
      try { await window.sb.from('products').update({ stock: p.stock }).eq('id', p.id); } catch {}
    }
    // Compute new totals
    let ttc = 0, ht = 0, tva = 0;
    newLines.forEach(l => {
      const p = PRODUCTS.find(x => x.id === l.pid) || {};
      const rate = l.tvaRate || p.tvaRate || 0.055;
      const lineTtc = (p.price || l.unitPrice || 0) * l.qty;
      const lineHt = lineTtc / (1 + rate);
      ttc += lineTtc; ht += lineHt; tva += (lineTtc - lineHt);
    });
    const pct = o.discountPct || 0;
    const finalTtc = ttc * (1 - pct / 100);
    const finalHt = ht * (1 - pct / 100);
    const finalTva = tva * (1 - pct / 100);
    const updated = { ...o, lines: newLines, totalTtc: Number(finalTtc.toFixed(2)), totalHt: Number(finalHt.toFixed(2)), totalTva: Number(finalTva.toFixed(2)), discountAmount: Number((ttc * pct / 100).toFixed(2)) };
    setOrders(orders.map(x => x.id === o.id ? updated : x));
    setSelectedOrder(updated);
    try { await window.saveOrder(updated); showToast('Commande mise à jour'); } catch (e) { showToast('Erreur: ' + e.message); }
    setEditing(false);
  };
  const advance = async () => {
    const next = { 'en-cours':'prete', 'prete':'livree', 'livree':'payee', 'payee':'payee' }[o.status];
    setOrders(orders.map(x => x.id === o.id ? { ...x, status: next } : x));
    setSelectedOrder({ ...o, status: next });
    try { await window.sb.from('orders').update({ status: next }).eq('id', o.id); } catch {}
  };
  const cancel = async () => {
    if (!confirm('Annuler cette commande ? Le stock sera restauré.')) return;
    const restored = PRODUCTS.map(p => {
      const line = o.lines.find(l => l.pid === p.id);
      return line ? { ...p, stock: (p.stock || 0) + (line.qty || 0) } : p;
    });
    VivalData.PRODUCTS.splice(0, VivalData.PRODUCTS.length, ...restored);
    setOrders(orders.map(x => x.id === o.id ? { ...x, status: 'annulee' } : x));
    setSelectedOrder({ ...o, status: 'annulee' });
    try {
      await window.sb.from('orders').update({ status: 'annulee' }).eq('id', o.id);
      for (const l of o.lines) {
        const p = PRODUCTS.find(x => x.id === l.pid);
        if (p) await window.sb.from('products').update({ stock: p.stock }).eq('id', p.id);
      }
    } catch {}
  };
  const isCancelled = o.status === 'annulee';
  return (
    <Modal open={true} onClose={() => setSelectedOrder(null)} size="lg"
      title={<>Commande <span className="mono" style={{color:'var(--ink-3)'}}>{o.id}</span></>}
      footer={editing ? <>
        <Button variant="ghost" onClick={() => { setEditing(false); setEditLines(o.lines); }}>Annuler édition</Button>
        <Button variant="primary" icon="check" onClick={saveEdits}>Enregistrer</Button>
      </> : <>
        <Button variant="ghost" onClick={() => setSelectedOrder(null)}>Fermer</Button>
        {!isCancelled && o.status !== 'payee' && o.status !== 'livree' && <Button variant="outline" icon="edit" onClick={() => setEditing(true)}>Éditer lignes</Button>}
        {!isCancelled && o.status !== 'payee' && <Button variant="outline" icon="close" onClick={cancel} style={{color:'var(--danger, #c02424)'}}>Annuler</Button>}
        <Button variant="outline" icon="printer" onClick={() => { setPrintOrder(o); setRoute('print'); setSelectedOrder(null); }}>Imprimer bon</Button>
        {!isCancelled && o.status !== 'payee' && <Button variant="primary" icon="check" onClick={advance}>
          {{'en-cours':'Marquer prête','prete':'Marquer livrée','livree':'Marquer payée'}[o.status]}
        </Button>}
      </>}
    >
      <div style={{display:'flex', gap:16, alignItems:'center', padding:'12px 16px', background:'var(--surface-1)', borderRadius:'var(--r-md)', marginBottom:16}}>
        <div className="customer-avatar">{(customer?.name || 'CL').split(' ').slice(-1)[0][0]}</div>
        <div style={{flex:1}}>
          <div style={{fontWeight:700}}>{customer?.name || o.customerName || 'Client libre'}</div>
          <div className="muted" style={{fontSize:13}}>{customer?.phone} · {customer?.address}</div>
        </div>
        <StatusPill status={o.status} />
      </div>

      <table style={{width:'100%', borderCollapse:'collapse'}}>
        <thead>
          <tr style={{textAlign:'left', fontSize:11, textTransform:'uppercase', letterSpacing:'0.05em', color:'var(--ink-3)'}}>
            <th style={{padding:'8px 4px'}}>Produit</th>
            <th style={{padding:'8px 4px', textAlign:'right'}}>Qté</th>
            <th style={{padding:'8px 4px', textAlign:'right'}}>Prix</th>
            <th style={{padding:'8px 4px', textAlign:'right'}}>Total</th>
          </tr>
        </thead>
        <tbody>
          {currentLines.map(l => {
            const p = PRODUCTS.find(x => x.id === l.pid);
            if (!p) return null;
            return (
              <tr key={l.pid} style={{borderTop:'1px solid var(--line-2)'}}>
                <td style={{padding:'10px 4px'}}>
                  <div style={{fontWeight:600}}>{p.name}</div>
                  <div className="muted" style={{fontSize:12}}>{p.format}</div>
                </td>
                <td style={{padding:'10px 4px', textAlign:'right', fontVariantNumeric:'tabular-nums'}}>
                  {editing
                    ? <QtyControl value={l.qty} onChange={q => setEditLines(prev => prev.map(x => x.pid === l.pid ? { ...x, qty: q } : x))} step={p.byWeight ? 0.1 : 1} byWeight={p.byWeight} />
                    : (p.byWeight ? `${l.qty.toFixed(2)} kg` : l.qty)}
                </td>
                <td style={{padding:'10px 4px', textAlign:'right', fontVariantNumeric:'tabular-nums'}}>{formatPrice(p.price).full}</td>
                <td style={{padding:'10px 4px', textAlign:'right', fontWeight:700, fontVariantNumeric:'tabular-nums'}}>{formatPrice(p.price * l.qty).full}</td>
              </tr>
            );
          })}
        </tbody>
      </table>
      <div style={{marginTop:16, paddingTop:16, borderTop:'2px solid var(--line-1)', display:'flex', justifyContent:'space-between', alignItems:'baseline'}}>
        <span className="muted">Paiement : <strong style={{color:'var(--ink-1)'}}>{o.payment}</strong>{o.cashierName ? ` · Encaissé par ${o.cashierName}` : ''}{o.discountPct ? ` · Remise ${o.discountPct}%` : ''}</span>
        <span style={{fontSize:22, fontWeight:800}}>{formatPrice(total).full}</span>
      </div>
      {o.notes && <div style={{marginTop:12, padding:10, background:'#fff7ed', border:'1px solid #fed7aa', borderRadius:6, fontSize:13}}><strong>Note :</strong> {o.notes}</div>}
      {o.signature && (
        <div style={{marginTop:16, padding:12, background:'var(--surface-1)', borderRadius:'var(--r-md)'}}>
          <div className="kpi-label" style={{marginBottom:6}}>Preuve de livraison {o.deliveredAt && <>· {new Date(o.deliveredAt).toLocaleString('fr-FR')}</>}</div>
          <img src={o.signature} alt="Signature" style={{maxHeight:100, background:'#fff', borderRadius:6, padding:4, border:'1px solid var(--line-2)'}} />
        </div>
      )}
    </Modal>
  );
}

// ===== Customers screen =====
function CustomersScreen() {
  const { setCustomer, setRoute, showToast } = useApp();
  const [query, setQuery] = useState('');
  const [editing, setEditing] = useState(null);
  const list = CUSTOMERS.filter(c => !query || c.name.toLowerCase().includes(query.toLowerCase()) || (c.phone || '').includes(query));
  return (
    <>
      <div className="page-header">
        <div className="page-header-grow">
          <h1 className="page-title">Clients</h1>
          <p className="page-subtitle">{CUSTOMERS.length} clients enregistrés</p>
        </div>
        <Button variant="primary" icon="plus" onClick={() => setEditing({ name:'', phone:'', address:'', notes:'' })}>Nouveau client</Button>
      </div>
      {editing && <CustomerEditModal customer={editing} onClose={() => setEditing(null)} onSaved={() => { setEditing(null); showToast('Client enregistré'); }} />}
      <div style={{padding:'0 32px 16px', maxWidth:640}}>
        <Input icon="search" placeholder="Rechercher par nom ou téléphone…" value={query} onChange={e => setQuery(e.target.value)} />
      </div>
      <div className="orderlist">
        {list.map(c => (
          <div key={c.id} className="customer-row" onClick={() => { setCustomer(c); setRoute('catalog'); }}>
            <div className="customer-avatar">{(c.name.split(' ').slice(-1)[0] || '?')[0]}</div>
            <div style={{flex:1, minWidth:0}}>
              <div style={{fontWeight:700}}>{c.name}</div>
              <div className="muted" style={{fontSize:12}}>{c.phone || '—'}{c.address ? ' · ' + c.address : ''}</div>
            </div>
            {c.loyaltyPoints > 0 && <span className="pill" style={{background:'#fef3c7', color:'#92400e'}}>{c.loyaltyPoints} pts</span>}
            <span className="pill">{c.orderCount || 0} cmdes</span>
            <button onClick={(e) => { e.stopPropagation(); setEditing(c); }}
              style={{background:'transparent', border:'1px solid var(--line)', borderRadius:6, padding:'6px 8px', cursor:'pointer'}}
              aria-label="Modifier">
              <Icon name="edit" />
            </button>
          </div>
        ))}
        {list.length === 0 && <div className="muted" style={{padding:40, textAlign:'center'}}>Aucun client.</div>}
      </div>
    </>
  );
}

function CustomerEditModal({ customer, onClose, onSaved }) {
  const [name, setName] = useState(customer.name || '');
  const [phone, setPhone] = useState(customer.phone || '');
  const [streetNumber, setStreetNumber] = useState(customer.streetNumber || '');
  const [street, setStreet] = useState(customer.street || '');
  const [postalCode, setPostalCode] = useState(customer.postalCode || '');
  const [city, setCity] = useState(customer.city || '');
  const [notes, setNotes] = useState(customer.notes || '');
  const [saving, setSaving] = useState(false);
  const isNew = !customer.id;

  const save = async () => {
    if (!name.trim()) return;
    setSaving(true);
    try {
      await window.saveCustomer({
        id: customer.id,
        name: name.trim(),
        phone: phone.trim(),
        streetNumber: streetNumber.trim(),
        street: street.trim(),
        postalCode: postalCode.trim(),
        city: city.trim(),
        notes: notes.trim(),
      });
      onSaved();
    } catch (e) {
      alert('Erreur : ' + (e.message || e));
      setSaving(false);
    }
  };

  const remove = async () => {
    if (!confirm(`Supprimer "${customer.name}" ?`)) return;
    setSaving(true);
    try {
      const { error } = await window.sb.from('customers').delete().eq('id', customer.id);
      if (error) throw error;
      const idx = window.VivalData.CUSTOMERS.findIndex(c => c.id === customer.id);
      if (idx !== -1) window.VivalData.CUSTOMERS.splice(idx, 1);
      onSaved();
    } catch (e) {
      alert('Erreur : ' + (e.message || e));
      setSaving(false);
    }
  };

  const inputStyle = { width:'100%', padding:'10px 12px', border:'1px solid var(--line)', borderRadius:8, fontSize:14 };
  return (
    <Modal open={true} onClose={onClose} title={isNew ? 'Nouveau client' : `Modifier : ${customer.name}`}
      footer={<>
        {!isNew && <Button variant="danger" icon="trash" onClick={remove} disabled={saving}>Supprimer</Button>}
        <div style={{flex:1}}></div>
        <Button variant="ghost" onClick={onClose} disabled={saving}>Annuler</Button>
        <Button variant="primary" icon="check-circle" onClick={save} disabled={saving || !name.trim()}>
          {saving ? 'Enregistrement…' : (isNew ? 'Créer le client' : 'Enregistrer')}
        </Button>
      </>}
    >
      <div style={{display:'grid', gap:12}}>
        <label style={{fontSize:13}}>
          <div style={{fontWeight:700, marginBottom:4}}>Nom *</div>
          <input type="text" value={name} onChange={e => setName(e.target.value)} autoFocus style={inputStyle} placeholder="ex: Mme Dupont" />
        </label>
        <label style={{fontSize:13}}>
          <div style={{fontWeight:700, marginBottom:4}}>Téléphone</div>
          <input type="tel" value={phone} onChange={e => setPhone(e.target.value)} style={inputStyle} placeholder="06 12 34 56 78" />
        </label>
        <div style={{display:'grid', gridTemplateColumns:'100px 1fr', gap:10}}>
          <label style={{fontSize:13}}>
            <div style={{fontWeight:700, marginBottom:4}}>N°</div>
            <input type="text" value={streetNumber} onChange={e => setStreetNumber(e.target.value)} style={inputStyle} placeholder="12 bis" />
          </label>
          <label style={{fontSize:13}}>
            <div style={{fontWeight:700, marginBottom:4}}>Rue</div>
            <input type="text" value={street} onChange={e => setStreet(e.target.value)} style={inputStyle} placeholder="Rue des Lilas" />
          </label>
        </div>
        <div style={{display:'grid', gridTemplateColumns:'120px 1fr', gap:10}}>
          <label style={{fontSize:13}}>
            <div style={{fontWeight:700, marginBottom:4}}>Code postal</div>
            <input type="text" value={postalCode} onChange={e => setPostalCode(e.target.value)} style={inputStyle} placeholder="38690" inputMode="numeric" maxLength={10} />
          </label>
          <label style={{fontSize:13}}>
            <div style={{fontWeight:700, marginBottom:4}}>Ville</div>
            <input type="text" value={city} onChange={e => setCity(e.target.value)} style={inputStyle} placeholder="Le Grand-Lemps" />
          </label>
        </div>
        <label style={{fontSize:13}}>
          <div style={{fontWeight:700, marginBottom:4}}>Notes</div>
          <textarea value={notes} onChange={e => setNotes(e.target.value)} rows={3} style={{...inputStyle, fontFamily:'inherit', resize:'vertical'}} placeholder="Préférences, allergies, horaires de livraison…" />
        </label>
      </div>
    </Modal>
  );
}

// ===== Ajout produit inconnu (fallback Open Food Facts) =====
function cleanQty(q) {
  if (!q) return '';
  const s = String(q).trim();
  if (/^0\s*(kg|g|ml|l|cl)?$/i.test(s)) return '';
  return s;
}
function extractQtyFromName(name) {
  if (!name) return '';
  const m = name.match(/(\d+(?:[.,]\d+)?)\s*(kg|g|ml|cl|l|oz|lb)\b/i);
  return m ? `${m[1].replace(',', '.')} ${m[2].toLowerCase()}` : '';
}

async function fetchFromUPCitemdb(ean) {
  try {
    const r = await fetch(`https://api.upcitemdb.com/prod/trial/lookup?upc=${ean}`);
    const d = await r.json();
    if (d.code === 'OK' && d.items && d.items.length) {
      const it = d.items[0];
      return {
        name: it.title || '',
        brand: it.brand || '',
        image: (it.images && it.images[0]) || null,
        format: cleanQty(it.size) || cleanQty(it.weight),
        categoriesText: it.category || '',
        countries: [],
        source: 'upcitemdb',
      };
    }
  } catch (_) {}
  return null;
}

// Calcule la clé de contrôle EAN-13 pour un préfixe de 12 chiffres
function ean13Checksum(twelveDigits) {
  if (!/^\d{12}$/.test(twelveDigits)) return null;
  let sum = 0;
  for (let i = 0; i < 12; i++) {
    sum += parseInt(twelveDigits[i], 10) * (i % 2 === 0 ? 1 : 3);
  }
  return (10 - (sum % 10)) % 10;
}

// Normalise un EAN : si c'est 12 chiffres, ajoute la clef de controle pour obtenir un EAN-13.
// Si deja 13 chiffres ou autre longueur, retourne tel quel.
// Utilise partout où un EAN entre dans l'app (scan, imports, lookup).
function normalizeEan(rawEan) {
  if (rawEan == null) return rawEan;
  const s = String(rawEan).trim();
  if (/^\d{12}$/.test(s)) {
    const cs = ean13Checksum(s);
    if (cs !== null) return s + cs;
  }
  return s;
}
// Expose globalement pour data.js et autres scripts
if (typeof window !== 'undefined') { window.normalizeEan = normalizeEan; window.ean13Checksum = ean13Checksum; }

async function fetchFromOpenFacts(ean) {
  const bases = [
    'https://world.openfoodfacts.org',
    'https://world.openbeautyfacts.org',
    'https://world.openpetfoodfacts.org',
    'https://world.openproductsfacts.org',
  ];
  const headers = { 'User-Agent': 'MonVival/1.0 (https://monvival-main.vercel.app)' };
  for (const base of bases) {
    try {
      const r = await fetch(`${base}/api/v2/product/${ean}.json`, { headers });
      if (r.status === 429) { /* rate-limited : on abandonne pour ce produit */ return null; }
      const d = await r.json();
      if (d.status === 1 && d.product) {
        const p = d.product;
        const url = p.image_front_url || p.image_url;
        const img = url ? url.replace(/\.(100|200|400)\.jpg$/, '.full.jpg') : null;
        const imgIngredients = (p.image_ingredients_url || '').replace(/\.(100|200|400)\.jpg$/, '.full.jpg') || null;
        const imgNutrition = (p.image_nutrition_url || '').replace(/\.(100|200|400)\.jpg$/, '.full.jpg') || null;
        const imgPackaging = (p.image_packaging_url || '').replace(/\.(100|200|400)\.jpg$/, '.full.jpg') || null;
        const qty = cleanQty(p.quantity) || extractQtyFromName(p.product_name_fr || p.product_name);
        const clean = (arr) => (arr || []).map(t => t.replace(/^[a-z]{2}:/, '').replace(/-/g, ' '));
        const n = p.nutriments || {};
        return {
          name: p.product_name_fr || p.product_name || 'Sans nom',
          genericName: p.generic_name_fr || p.generic_name || '',
          brand: p.brands || '',
          image: img,
          imageIngredients: imgIngredients,
          imageNutrition: imgNutrition,
          imagePackaging: imgPackaging,
          format: qty,
          categories: p.categories_tags_fr || p.categories_tags || [],
          categoriesText: p.categories || '',
          ingredients: p.ingredients_text_fr || p.ingredients_text || '',
          allergens: clean(p.allergens_tags),
          traces: clean(p.traces_tags),
          labels: clean(p.labels_tags),
          origins: p.origins || p.manufacturing_places || '',
          countries: clean(p.countries_tags),
          packaging: clean(p.packaging_tags),
          nutriscore: (p.nutriscore_grade || '').toUpperCase(),
          ecoscore: (p.ecoscore_grade || '').toUpperCase(),
          nova: p.nova_group || null,
          novaGroupText: p.nova_groups_tags ? clean(p.nova_groups_tags)[0] : '',
          servingSize: p.serving_size || '',
          nutriments: {
            energyKcal: n['energy-kcal_100g'] ?? n.energy_100g,
            fat: n.fat_100g,
            satFat: n['saturated-fat_100g'],
            carbs: n.carbohydrates_100g,
            sugars: n.sugars_100g,
            fiber: n.fiber_100g,
            proteins: n.proteins_100g,
            salt: n.salt_100g,
            sodium: n.sodium_100g,
          },
          stores: p.stores || '',
          link: `${base}/product/${ean}`,
          source: base.replace('https://world.', '').replace('.org', ''),
        };
      }
    } catch (_) {}
  }
  return null;
}

async function fetchAllSources(ean) {
  const [off, upc] = await Promise.all([
    fetchFromOpenFacts(ean),
    fetchFromUPCitemdb(ean),
  ]);
  if (!off && !upc) return null;

  const sources = [];
  if (off) sources.push(off.source);
  if (upc) sources.push('upcitemdb');

  const pick = (field) => {
    const vals = [];
    if (off && off[field]) vals.push({ v: off[field], s: off.source });
    if (upc && upc[field]) vals.push({ v: upc[field], s: 'upcitemdb' });
    return vals;
  };

  const merge = (field, prefer) => {
    const vals = pick(field);
    if (!vals.length) return { v: '', s: null, conflict: false };
    const preferred = prefer ? vals.find(x => x.s === prefer) : null;
    const chosen = preferred || vals[0];
    const conflict = vals.length > 1 && vals.some(x => String(x.v).trim().toLowerCase() !== String(chosen.v).trim().toLowerCase());
    return { v: chosen.v, s: chosen.s, conflict, all: vals };
  };

  const base = off || {};
  const name = merge('name');
  const brand = merge('brand');
  const image = merge('image', off?.source);
  const formatRaw = merge('format');
  const format = formatRaw.v || extractQtyFromName(name.v) || '';

  return {
    ...base,
    name: name.v,
    brand: brand.v,
    image: image.v,
    format,
    upcitemdb: upc,
    sources,
    conflicts: {
      name: name.conflict,
      brand: brand.conflict,
      format: formatRaw.conflict,
    },
    _nameAll: name.all,
    _brandAll: brand.all,
    _formatAll: formatRaw.all,
  };
}

function guessRayonFromCategories(cats) {
  const joined = (cats || []).join(' ').toLowerCase();
  const map = [
    ['produits-bio', /bio\b|organique/],
    ['vins', /vin|wine|bordeaux|champagne|bourgogne/],
    ['boissons-et-alcool', /boisson|drink|soda|bière|beer|jus|juice|eau\b|water/],
    ['fruits-et-legumes', /fruit|légume|legume|vegetable/],
    ['viandes-et-poissons', /viande|meat|poisson|fish|volaille|poultry/],
    ['boulangerie-et-patisserie', /pain|bread|pâtisser|patisser|viennois|croissant/],
    ['produits-frais', /lait|milk|yaourt|yogurt|fromage|cheese|beurre|butter|charcuter/],
    ['epicerie-sucree', /chocolat|chocolate|biscuit|céréale|cereal|confiser|candy|sucre/],
    ['epicerie-salee', /pâtes|pasta|riz|rice|conserv|sauce|condiment|huile|oil/],
    ['surgeles', /surgelé|frozen|glace|ice-cream/],
    ['hygiene-et-beaute', /hygiène|hygiene|beauty|shampoo|soap|cosmet/],
    ['entretien-bazar', /entretien|cleaning|lessive|détergent|detergent|ménage/],
    ['univers-bebe', /bébé|baby|infant|nourrisson|couche/],
    ['animaux', /animal|pet|chien|dog|chat|cat|croquette/],
  ];
  for (const [rayon, re] of map) if (re.test(joined)) return rayon;
  return 'epicerie-salee';
}

function UnknownProductModal() {
  const app = useApp();
  const ean = app.unknownBarcode;
  const [status, setStatus] = useState('loading'); // loading | found | not-found | saving | done
  const [data, setData] = useState(null);
  const [price, setPrice] = useState('');
  const [rayon, setRayon] = useState('epicerie-salee');
  const [name, setName] = useState('');
  const [brand, setBrand] = useState('');
  const [format, setFormat] = useState('');

  useEffect(() => {
    if (!ean) return;
    setStatus('loading'); setData(null); setPrice(''); setName(''); setBrand(''); setFormat('');
    (async () => {
      const found = await fetchAllSources(ean);
      if (found) {
        setData(found);
        setName(found.name);
        setBrand(found.brand);
        setFormat(found.format);
        setRayon(guessRayonFromCategories(found.categories));
        setStatus('found');
      } else {
        setStatus('not-found');
      }
    })();
  }, [ean]);

  if (!ean) return null;

  const save = () => {
    const product = {
      id: 'custom-' + ean,
      rayon,
      name: name.trim() || 'Produit ' + ean,
      format: format.trim(),
      price: Number(price.replace(',', '.')) || 0,
      badges: [],
      sub: 'Tout',
      byWeight: false,
      stock: 20,
      pinned: false,
      barcode: ean,
      image: data?.image || null,
      brand: brand.trim(),
    };
    window.addCustomProduct(product);
    setStatus('done');
    setTimeout(() => {
      app.setUnknownBarcode(null);
      app.addToCart(product.id);
    }, 800);
  };

  const close = () => app.setUnknownBarcode(null);

  return (
    <div className="modal-backdrop" onClick={close}>
      <div className="modal" onClick={e => e.stopPropagation()} style={{maxWidth: 560}}>
        <div className="modal-header">
          <h2 className="modal-title">Produit inconnu</h2>
          <button className="modal-close" onClick={close}><Icon name="close" /></button>
        </div>
        <div className="modal-body" style={{padding: 20}}>
          <div className="muted" style={{fontSize:13, marginBottom:12}}>
            EAN scanné : <code style={{fontFamily:'var(--font-mono)', fontWeight:700}}>{ean}</code>
          </div>
          {status === 'loading' && (
            <div style={{textAlign:'center', padding:40}}>
              <div className="muted">Recherche sur Open Food Facts…</div>
            </div>
          )}
          {status === 'not-found' && (
            <>
              <div style={{padding:12, background:'#fff7ed', border:'1px solid #fed7aa', borderRadius:8, marginBottom:16, fontSize:13}}>
                Ce code-barres n'existe ni dans le catalogue Vival ni sur Open Food Facts. Saisis les infos manuellement pour l'ajouter.
              </div>
              <FormFields {...{name,setName,brand,setBrand,format,setFormat,price,setPrice,rayon,setRayon}} />
              <div style={{marginTop:20, display:'flex', gap:8, justifyContent:'flex-end'}}>
                <Button variant="ghost" onClick={close}>Annuler</Button>
                <Button variant="primary" onClick={save} disabled={!name.trim() || !price}>Ajouter au catalogue</Button>
              </div>
            </>
          )}
          {(status === 'found' || status === 'saving' || status === 'done') && data && (
            <>
              <div style={{display:'flex', gap:16, marginBottom:16, alignItems:'flex-start'}}>
                {data.image && <img src={data.image} alt="" style={{width:120, height:120, objectFit:'contain', background:'var(--surface-2)', borderRadius:8}} />}
                <div style={{flex:1, minWidth:0}}>
                  <div style={{fontWeight:800, fontSize:16}}>{data.name}</div>
                  {data.genericName && <div style={{fontSize:13, marginTop:2}}>{data.genericName}</div>}
                  <div className="muted" style={{fontSize:13, marginTop:4}}>{data.brand}{data.format ? ' · ' + data.format : ''}</div>
                  <div style={{display:'flex', gap:6, flexWrap:'wrap', marginTop:8}}>
                    {data.nutriscore && <NutriscoreBadge grade={data.nutriscore} />}
                    {data.ecoscore && <span className="status-pill" style={{background:'#eef7ee', color:'#1b5e20'}}>Eco-Score {data.ecoscore}</span>}
                    {data.nova && <span className="status-pill" style={{background:'#fff3e0', color:'#e65100'}}>NOVA {data.nova}</span>}
                    {data.labels.slice(0,4).map((l,i) => <span key={i} className="status-pill" style={{background:'#e8f5e9', color:'#1b5e20', textTransform:'capitalize'}}>{l}</span>)}
                  </div>
                  <div className="muted" style={{fontSize:12, marginTop:8}}>
                    Sources croisées : {(data.sources || [data.source]).map((s,i) => (
                      <span key={i} className="status-pill" style={{marginLeft:4, background:'#eef2ff', color:'#3730a3', fontSize:11}}>{s}</span>
                    ))}
                    {data.link && <> · <a href={data.link} target="_blank" rel="noreferrer" style={{color:'var(--vival-red)'}}>voir fiche OFF</a></>}
                  </div>
                </div>
              </div>

              {(data.conflicts?.name || data.conflicts?.brand || data.conflicts?.format) && (
                <div style={{padding:12, background:'#fff7ed', border:'1px solid #fed7aa', borderRadius:8, marginBottom:12, fontSize:13}}>
                  <div style={{fontWeight:700, marginBottom:6}}>⚠ Divergence entre sources — vérifie avant d'ajouter</div>
                  {['name','brand','format'].filter(f => data.conflicts[f]).map(f => (
                    <div key={f} style={{marginTop:4}}>
                      <strong style={{textTransform:'capitalize'}}>{f === 'name' ? 'Nom' : f === 'brand' ? 'Marque' : 'Format'} :</strong>{' '}
                      {data[`_${f}All`].map((x,i) => (
                        <span key={i} style={{marginRight:6}}>
                          <code style={{background:'#fff', padding:'1px 6px', borderRadius:4, fontSize:12}}>{x.v}</code>
                          <span className="muted" style={{fontSize:11}}> ({x.s})</span>
                        </span>
                      ))}
                    </div>
                  ))}
                </div>
              )}

              <OffDetails data={data} />

              <div style={{marginTop:16, paddingTop:16, borderTop:'1px solid var(--line)'}}>
                <div style={{fontWeight:700, fontSize:14, marginBottom:10}}>Infos boutique (obligatoires)</div>
                <FormFields {...{name,setName,brand,setBrand,format,setFormat,price,setPrice,rayon,setRayon}} />
              </div>
              {status === 'done' && (
                <div style={{marginTop:16, padding:10, background:'var(--success-soft)', color:'var(--success)', borderRadius:8, fontSize:14, fontWeight:600, textAlign:'center'}}>
                  ✓ Produit ajouté au catalogue et au panier
                </div>
              )}
              {status !== 'done' && (
                <div style={{marginTop:20, display:'flex', gap:8, justifyContent:'flex-end'}}>
                  <Button variant="ghost" onClick={close}>Annuler</Button>
                  <Button variant="primary" onClick={save} disabled={!name.trim() || !price}>Ajouter au catalogue</Button>
                </div>
              )}
            </>
          )}
        </div>
      </div>
    </div>
  );
}

function NutriscoreBadge({ grade }) {
  const colors = { A: '#038141', B: '#85bb2f', C: '#fecb02', D: '#ee8100', E: '#e63e11' };
  const bg = colors[grade] || '#888';
  const fg = (grade === 'C') ? '#000' : '#fff';
  return <span className="status-pill" style={{background:bg, color:fg}}>Nutri-Score {grade}</span>;
}

function OffDetails({ data }) {
  const [tab, setTab] = useState('general');
  const tabs = [
    { id: 'general', label: 'Général' },
    { id: 'ingredients', label: 'Ingrédients' },
    { id: 'nutrition', label: 'Nutrition' },
    { id: 'images', label: 'Images' },
  ];
  const hasNutrition = Object.values(data.nutriments || {}).some(v => v != null);
  const hasIngredients = !!data.ingredients || data.allergens.length > 0 || data.traces.length > 0;
  const hasExtraImages = data.imageIngredients || data.imageNutrition || data.imagePackaging;

  const row = (k, v, unit = '') => v == null ? null : (
    <div style={{display:'flex', justifyContent:'space-between', padding:'6px 0', borderBottom:'1px solid var(--line)', fontSize:13}}>
      <span className="muted">{k}</span>
      <span style={{fontWeight:600}}>{typeof v === 'number' ? v.toFixed(2).replace(/\.?0+$/,'') : v} {unit}</span>
    </div>
  );

  return (
    <div style={{background:'var(--surface-2)', borderRadius:10, padding:14, marginBottom:4}}>
      <div style={{display:'flex', gap:4, marginBottom:12, background:'#fff', padding:4, borderRadius:8}}>
        {tabs.map(t => (
          <button key={t.id} onClick={() => setTab(t.id)}
            style={{flex:1, padding:'6px 8px', border:0, borderRadius:6, fontSize:12, fontWeight:600, cursor:'pointer',
                    background: tab===t.id?'var(--vival-red)':'transparent', color: tab===t.id?'#fff':'var(--ink-2)'}}>
            {t.label}
          </button>
        ))}
      </div>

      {tab === 'general' && (
        <div>
          {row('Catégories', data.categoriesText ? data.categoriesText.split(',').slice(0,3).join(', ') : null)}
          {row('Origine', data.origins || null)}
          {row('Pays de vente', data.countries.slice(0,3).join(', ') || null)}
          {row('Emballage', data.packaging.slice(0,3).join(', ') || null)}
          {row('Magasins', data.stores || null)}
          {row('Portion', data.servingSize || null)}
          {data.labels.length > 3 && row('Labels', data.labels.slice(0,6).join(', '))}
        </div>
      )}

      {tab === 'ingredients' && (
        hasIngredients ? (
          <div>
            {data.ingredients && (
              <div style={{marginBottom:10}}>
                <div className="muted" style={{fontSize:12, fontWeight:700, marginBottom:4}}>INGRÉDIENTS</div>
                <div style={{fontSize:13, lineHeight:1.5}}>{data.ingredients}</div>
              </div>
            )}
            {data.allergens.length > 0 && (
              <div style={{marginBottom:10}}>
                <div className="muted" style={{fontSize:12, fontWeight:700, marginBottom:4}}>ALLERGÈNES</div>
                <div style={{display:'flex', gap:4, flexWrap:'wrap'}}>
                  {data.allergens.map((a,i) => <span key={i} className="status-pill" style={{background:'#fee2e2', color:'#dc2626', textTransform:'capitalize'}}>{a}</span>)}
                </div>
              </div>
            )}
            {data.traces.length > 0 && (
              <div>
                <div className="muted" style={{fontSize:12, fontWeight:700, marginBottom:4}}>TRACES POSSIBLES</div>
                <div style={{display:'flex', gap:4, flexWrap:'wrap'}}>
                  {data.traces.map((a,i) => <span key={i} className="status-pill" style={{background:'#fff7ed', color:'#9a3412', textTransform:'capitalize'}}>{a}</span>)}
                </div>
              </div>
            )}
          </div>
        ) : <div className="muted" style={{fontSize:13}}>Aucune info ingrédients.</div>
      )}

      {tab === 'nutrition' && (
        hasNutrition ? (
          <div>
            <div className="muted" style={{fontSize:12, fontWeight:700, marginBottom:6}}>VALEURS POUR 100 G / ML</div>
            {row('Énergie', data.nutriments.energyKcal, 'kcal')}
            {row('Matières grasses', data.nutriments.fat, 'g')}
            {row('dont acides gras saturés', data.nutriments.satFat, 'g')}
            {row('Glucides', data.nutriments.carbs, 'g')}
            {row('dont sucres', data.nutriments.sugars, 'g')}
            {row('Fibres', data.nutriments.fiber, 'g')}
            {row('Protéines', data.nutriments.proteins, 'g')}
            {row('Sel', data.nutriments.salt, 'g')}
            {row('Sodium', data.nutriments.sodium, 'g')}
          </div>
        ) : <div className="muted" style={{fontSize:13}}>Aucune info nutritionnelle.</div>
      )}

      {tab === 'images' && (
        hasExtraImages ? (
          <div style={{display:'grid', gap:10, gridTemplateColumns:'repeat(auto-fill, minmax(140px, 1fr))'}}>
            {data.image && <OffImage src={data.image} label="Face avant" />}
            {data.imageIngredients && <OffImage src={data.imageIngredients} label="Ingrédients" />}
            {data.imageNutrition && <OffImage src={data.imageNutrition} label="Nutrition" />}
            {data.imagePackaging && <OffImage src={data.imagePackaging} label="Emballage" />}
          </div>
        ) : <div className="muted" style={{fontSize:13}}>Une seule photo disponible.</div>
      )}
    </div>
  );
}

function OffImage({ src, label }) {
  return (
    <div style={{background:'#fff', padding:8, borderRadius:8, textAlign:'center'}}>
      <img src={src} alt={label} loading="lazy" style={{width:'100%', height:100, objectFit:'contain'}} />
      <div className="muted" style={{fontSize:11, marginTop:4, fontWeight:600}}>{label}</div>
    </div>
  );
}

function FormFields({name,setName,brand,setBrand,format,setFormat,price,setPrice,rayon,setRayon}) {
  return (
    <div style={{display:'grid', gap:10}}>
      <label style={{fontSize:13}}>
        <div style={{fontWeight:600, marginBottom:4}}>Nom du produit</div>
        <input type="text" value={name} onChange={e => setName(e.target.value)} className="input" style={{width:'100%', padding:'10px 12px', border:'1px solid var(--line)', borderRadius:8, fontSize:14}} />
      </label>
      <div style={{display:'grid', gridTemplateColumns:'1fr 1fr', gap:10}}>
        <label style={{fontSize:13}}>
          <div style={{fontWeight:600, marginBottom:4}}>Marque</div>
          <input type="text" value={brand} onChange={e => setBrand(e.target.value)} style={{width:'100%', padding:'10px 12px', border:'1px solid var(--line)', borderRadius:8, fontSize:14}} />
        </label>
        <label style={{fontSize:13}}>
          <div style={{fontWeight:600, marginBottom:4}}>Format</div>
          <input type="text" value={format} onChange={e => setFormat(e.target.value)} placeholder="ex: 500 g" style={{width:'100%', padding:'10px 12px', border:'1px solid var(--line)', borderRadius:8, fontSize:14}} />
        </label>
      </div>
      <div style={{display:'grid', gridTemplateColumns:'1fr 1fr', gap:10}}>
        <label style={{fontSize:13}}>
          <div style={{fontWeight:600, marginBottom:4}}>Prix €</div>
          <input type="text" value={price} onChange={e => setPrice(e.target.value)} placeholder="0,00" style={{width:'100%', padding:'10px 12px', border:'1px solid var(--line)', borderRadius:8, fontSize:14}} />
        </label>
        <label style={{fontSize:13}}>
          <div style={{fontWeight:600, marginBottom:4}}>Rayon</div>
          <select value={rayon} onChange={e => setRayon(e.target.value)} style={{width:'100%', padding:'10px 12px', border:'1px solid var(--line)', borderRadius:8, fontSize:14, background:'#fff'}}>
            {VivalData.RAYONS.map(r => <option key={r.id} value={r.id}>{r.name}</option>)}
          </select>
        </label>
      </div>
    </div>
  );
}

// ===== Pairing wizard (test douchette) =====
function PairingWizard({ open, onClose }) {
  const app = useApp();
  const [phase, setPhase] = useState('instructions'); // instructions | testing | success | failure
  const [osTab, setOsTab] = useState('windows');
  const [testCode, setTestCode] = useState('');
  const [received, setReceived] = useState([]);
  const [countdown, setCountdown] = useState(30);
  const timerRef = useRef(null);

  useEffect(() => {
    if (!open) {
      setPhase('instructions'); setReceived([]); setCountdown(30);
      app.setPendingScanCallback(null);
      if (timerRef.current) clearInterval(timerRef.current);
    }
  }, [open]);

  const generateCode = () => {
    let base = '978';
    for (let i = 0; i < 9; i++) base += Math.floor(Math.random() * 10);
    let sum = 0;
    for (let i = 0; i < 12; i++) sum += parseInt(base[i]) * (i % 2 === 0 ? 1 : 3);
    const check = (10 - (sum % 10)) % 10;
    return base + check;
  };

  const startTest = () => {
    const code = generateCode();
    setTestCode(code);
    setReceived([]);
    setPhase('testing');
    setCountdown(30);
    app.setPendingScanCallback((scanned) => {
      setReceived(prev => [...prev, { code: scanned, at: Date.now() }]);
      if (scanned === code) {
        setPhase('success');
        app.setPendingScanCallback(null);
        if (timerRef.current) clearInterval(timerRef.current);
      }
    });
    if (timerRef.current) clearInterval(timerRef.current);
    timerRef.current = setInterval(() => {
      setCountdown(c => {
        if (c <= 1) {
          clearInterval(timerRef.current);
          setPhase(prev => prev === 'testing' ? 'failure' : prev);
          app.setPendingScanCallback(null);
          return 0;
        }
        return c - 1;
      });
    }, 1000);
  };

  const retry = () => { setPhase('instructions'); setReceived([]); };

  if (!open) return null;

  const instructions = {
    windows: [
      "Ouvre Paramètres → Bluetooth et appareils",
      "Active le Bluetooth",
      "Mets la douchette en mode appairage (voir sa notice — souvent bouton maintenu 3–5 s)",
      "Clique « Ajouter un appareil » puis choisis ta douchette dans la liste",
      "Valide le code PIN si demandé (souvent 0000 ou 1234)",
    ],
    android: [
      "Paramètres → Connexions → Bluetooth",
      "Active le Bluetooth",
      "Mets la douchette en mode appairage",
      "Touche la douchette dans la liste des appareils détectés",
      "Confirme l'appairage",
    ],
    ipad: [
      "Réglages → Bluetooth",
      "Active le Bluetooth",
      "Mets la douchette en mode appairage",
      "Touche la douchette dans la liste",
      "Confirme l'appairage (sur iPad, masque le clavier virtuel depuis Réglages → Général → Clavier)",
    ],
  };

  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()} style={{maxWidth: 540}}>
        <div className="modal-header">
          <h2 className="modal-title">Assistant d'appairage de la douchette</h2>
          <button className="modal-close" onClick={onClose}><Icon name="close" /></button>
        </div>
        <div className="modal-body" style={{padding: 20}}>
          {phase === 'instructions' && (
            <>
              <p style={{marginBottom: 16}}>
                La douchette s'appaire au niveau du système, pas de l'app. Suis les étapes correspondant à ton appareil, puis lance le test.
              </p>
              <div style={{display:'flex', gap:6, marginBottom:16, background:'var(--surface-2)', padding:4, borderRadius:8}}>
                {['windows','android','ipad'].map(k => (
                  <button key={k} onClick={() => setOsTab(k)}
                    style={{flex:1, padding:'8px 12px', border:0, borderRadius:6, cursor:'pointer',
                            background: osTab===k?'#fff':'transparent', fontWeight: osTab===k?700:500,
                            boxShadow: osTab===k?'0 1px 3px rgba(0,0,0,.08)':'none'}}>
                    {k === 'windows' ? 'Windows' : k === 'android' ? 'Android' : 'iPad'}
                  </button>
                ))}
              </div>
              <ol style={{paddingLeft: 20, lineHeight: 1.7, fontSize: 14}}>
                {instructions[osTab].map((step, i) => <li key={i}>{step}</li>)}
              </ol>
              <div style={{marginTop:20, padding:12, background:'var(--surface-2)', borderRadius:8, fontSize:13}} className="muted">
                <strong>Note :</strong> la douchette doit être configurée en mode <em>HID clavier</em> (réglage usine par défaut pour la majorité des modèles). Si elle envoie des codes sans Entrée final, scanne les codes de configuration livrés avec la notice pour activer le "suffix CR/LF".
              </div>
              <div style={{marginTop: 20, display:'flex', gap:8, justifyContent:'flex-end'}}>
                <Button variant="ghost" onClick={onClose}>Annuler</Button>
                <Button variant="primary" onClick={startTest} icon="scan-barcode">Lancer le test</Button>
              </div>
            </>
          )}
          {phase === 'testing' && (
            <>
              <p style={{marginBottom: 16}}>Scanne le code-barres ci-dessous avec la douchette :</p>
              <div style={{textAlign:'center', padding:24, background:'var(--surface-2)', borderRadius:12, marginBottom:16}}>
                <div style={{fontFamily:'var(--font-mono)', fontSize:28, fontWeight:700, letterSpacing:2}}>{testCode}</div>
                <div className="muted" style={{marginTop:8, fontSize:13}}>
                  Code-barres de test unique — pas un vrai produit
                </div>
              </div>
              <Barcode128 code={testCode} />
              <div style={{marginTop:16, padding:12, background:'var(--surface-2)', borderRadius:8, textAlign:'center'}}>
                <div style={{fontSize:32, fontWeight:800, color:'var(--vival-red)'}}>{countdown}s</div>
                <div className="muted" style={{fontSize:13}}>temps restant</div>
              </div>
              {received.length > 0 && (
                <div style={{marginTop:12, padding:12, background:'#fff7ed', border:'1px solid #fed7aa', borderRadius:8, fontSize:13}}>
                  <strong>⚠ Reçu :</strong> {received[received.length-1].code}
                  <div className="muted" style={{marginTop:4, fontSize:12}}>Ne correspond pas au code attendu — vérifie que tu scannes bien le bon.</div>
                </div>
              )}
              <div style={{marginTop: 20, display:'flex', gap:8, justifyContent:'flex-end'}}>
                <Button variant="ghost" onClick={retry}>Annuler</Button>
              </div>
            </>
          )}
          {phase === 'success' && (
            <div style={{textAlign:'center', padding:20}}>
              <div style={{width:72, height:72, margin:'0 auto 16px', borderRadius:'50%', background:'var(--success-soft)', color:'var(--success)', display:'flex', alignItems:'center', justifyContent:'center'}}>
                <Icon name="check-circle" />
              </div>
              <h3 style={{marginBottom:8}}>Douchette reconnue</h3>
              <p className="muted" style={{marginBottom:20}}>
                L'app a bien reçu le code de test. La douchette fonctionne correctement.
              </p>
              <Button variant="primary" onClick={onClose}>Terminer</Button>
            </div>
          )}
          {phase === 'failure' && (
            <div style={{padding:20}}>
              <div style={{textAlign:'center', marginBottom:20}}>
                <div style={{width:72, height:72, margin:'0 auto 16px', borderRadius:'50%', background:'#fee2e2', color:'#dc2626', display:'flex', alignItems:'center', justifyContent:'center', fontSize:32, fontWeight:800}}>!</div>
                <h3>Pas de scan détecté</h3>
              </div>
              <div style={{fontSize:14, lineHeight:1.7}}>
                <strong>Points à vérifier :</strong>
                <ul style={{paddingLeft:20, marginTop:8}}>
                  <li>La douchette est bien appairée au niveau système (icône Bluetooth "connecté")</li>
                  <li>Elle est en mode <em>HID clavier</em> (pas SPP ou autre)</li>
                  <li>Elle ajoute bien un <em>Entrée</em> (CR/LF) à la fin — sinon l'app ne détecte pas la fin du code</li>
                  <li>Sur iPad : masquer le clavier virtuel (<code>Maj + flèche bas</code> sur le clavier Bluetooth)</li>
                </ul>
                {received.length > 0 && (
                  <div style={{marginTop:12, padding:10, background:'#fff7ed', borderRadius:8, fontSize:13}}>
                    <strong>Codes reçus pendant le test :</strong>
                    <ul style={{margin:'4px 0 0 16px'}}>{received.map((r,i) => <li key={i}><code>{r.code}</code></li>)}</ul>
                  </div>
                )}
              </div>
              <div style={{marginTop: 20, display:'flex', gap:8, justifyContent:'flex-end'}}>
                <Button variant="ghost" onClick={onClose}>Fermer</Button>
                <Button variant="primary" onClick={retry}>Réessayer</Button>
              </div>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

// Simple CODE128 visual (not scannable — just representation); most scanners read EAN-13 we'd normally render
// For reliable test, we use the numeric string which any numeric-capable scanner can enter via manual keyboard too.
function Barcode128({ code }) {
  return (
    <div style={{background:'#fff', padding:12, border:'1px solid var(--line)', borderRadius:8, display:'flex', justifyContent:'center'}}>
      <img alt={code} src={`https://barcode.tec-it.com/barcode.ashx?data=${code}&code=EAN13&translate-esc=on&dpi=96`}
           onError={(e) => { e.target.style.display = 'none'; }}
           style={{maxWidth:'100%', height:80}} />
    </div>
  );
}

function PrintSettingsCard() {
  const [format, setFormat] = useState(() => localStorage.getItem('vival_print_format') || 'A4');
  const labels = { A4: 'Bon de livraison A4', '80mm': 'Ticket thermique 80mm', '58mm': 'Ticket thermique 58mm' };
  const change = (f) => { setFormat(f); localStorage.setItem('vival_print_format', f); };
  return (
    <div className="card" style={{padding:20}}>
      <h3 style={{margin:'0 0 12px', fontSize:16, fontWeight:800}}>Impression</h3>
      <div style={{display:'flex', justifyContent:'space-between', alignItems:'center', gap:12, flexWrap:'wrap'}}>
        <div>
          <div style={{fontWeight:600}}>Format par défaut</div>
          <div className="muted" style={{fontSize:13}}>{labels[format]}</div>
        </div>
        <div className="segmented">
          {['A4', '80mm', '58mm'].map(f => (
            <button key={f} aria-pressed={format === f} onClick={() => change(f)}>{f}</button>
          ))}
        </div>
      </div>
    </div>
  );
}

// ===== Scanner status (douchette BT) =====
function ScannerStatusCard() {
  const app = useApp();
  const [wizardOpen, setWizardOpen] = useState(false);
  const [now, setNow] = useState(Date.now());
  useEffect(() => {
    const t = setInterval(() => setNow(Date.now()), 1000);
    return () => clearInterval(t);
  }, []);
  const last = app.lastScanAt;
  const seconds = last ? Math.floor((now - last) / 1000) : null;
  const active = seconds !== null && seconds < 30;
  let status, label;
  if (last === null) { status = 'idle'; label = 'En attente'; }
  else if (active) { status = 'active'; label = 'Actif'; }
  else { status = 'idle'; label = 'Inactive'; }
  let subtitle;
  if (last === null) subtitle = "Aucun scan détecté — scanne un code-barres pour l'activer.";
  else if (seconds < 5) subtitle = "Dernier scan à l'instant";
  else if (seconds < 60) subtitle = `Dernier scan il y a ${seconds} s`;
  else if (seconds < 3600) subtitle = `Dernier scan il y a ${Math.floor(seconds/60)} min`;
  else subtitle = `Dernier scan il y a ${Math.floor(seconds/3600)} h`;
  return (
    <div className="card" style={{padding:20}}>
      <div style={{display:'flex', alignItems:'center', gap:12, marginBottom:12}}>
        <div style={{width:40, height:40, borderRadius:8, background:'var(--surface-2)', color:'var(--ink-2)', display:'flex', alignItems:'center', justifyContent:'center'}}>
          <Icon name="scan-barcode" />
        </div>
        <div style={{flex:1}}>
          <h3 style={{margin:0, fontSize:16, fontWeight:800}}>Douchette / scanner code-barres</h3>
          <div className="muted" style={{fontSize:13}}>{subtitle}</div>
        </div>
        <span className={"status-pill " + (status === 'active' ? 'status-livree' : 'status-payee')}>{label}</span>
      </div>
      <p className="muted" style={{fontSize:13, margin:'8px 0 16px'}}>
        Une douchette Bluetooth ou USB se comporte comme un clavier : elle tape le code-barres puis envoie Entrée. L'app écoute ces frappes rapides et ajoute automatiquement le produit au panier. Aucun appairage applicatif n'est nécessaire — le couplage se fait au niveau de l'OS (tablette/ordinateur).
      </p>
      <Button variant="outline" icon="scan-barcode" onClick={() => setWizardOpen(true)}>Tester l'appairage</Button>
      <PairingWizard open={wizardOpen} onClose={() => setWizardOpen(false)} />
    </div>
  );
}

// ===== Températures HACCP =====
function getCurrentUserName() {
  try {
    const c = JSON.parse(sessionStorage.getItem('vival_cashier') || 'null');
    if (c && c.name) return { name: c.name, id: c.id || null, role: c.role || null };
  } catch {}
  try {
    const s = JSON.parse(sessionStorage.getItem('supabase.auth.token') || 'null');
    const email = s?.currentSession?.user?.email || null;
    if (email) return { name: email.split('@')[0], id: null, role: null };
  } catch {}
  return { name: '', id: null, role: null };
}

function isoDate(d) {
  const x = d instanceof Date ? d : new Date(d);
  return x.getFullYear() + '-' + String(x.getMonth() + 1).padStart(2, '0') + '-' + String(x.getDate()).padStart(2, '0');
}

function SignaturePad({ value, onChange, height = 90, label }) {
  const canvasRef = useRef(null);
  const [drawing, setDrawing] = useState(false);

  useEffect(() => {
    const c = canvasRef.current;
    if (!c) return;
    const ctx = c.getContext('2d');
    ctx.fillStyle = '#fff';
    ctx.fillRect(0, 0, c.width, c.height);
    if (value) {
      const img = new Image();
      img.onload = () => ctx.drawImage(img, 0, 0, c.width, c.height);
      img.src = value;
    }
  }, []);

  const getPos = (e) => {
    const c = canvasRef.current;
    const r = c.getBoundingClientRect();
    const t = e.touches?.[0] || e;
    return { x: (t.clientX - r.left) * (c.width / r.width), y: (t.clientY - r.top) * (c.height / r.height) };
  };
  const start = (e) => {
    e.preventDefault();
    setDrawing(true);
    const p = getPos(e);
    const ctx = canvasRef.current.getContext('2d');
    ctx.beginPath();
    ctx.moveTo(p.x, p.y);
    ctx.lineWidth = 2;
    ctx.lineCap = 'round';
    ctx.strokeStyle = '#0a0f19';
  };
  const move = (e) => {
    if (!drawing) return;
    e.preventDefault();
    const p = getPos(e);
    const ctx = canvasRef.current.getContext('2d');
    ctx.lineTo(p.x, p.y);
    ctx.stroke();
  };
  const end = () => {
    if (!drawing) return;
    setDrawing(false);
    const data = canvasRef.current.toDataURL('image/png');
    onChange && onChange(data);
  };
  const clear = () => {
    const c = canvasRef.current;
    const ctx = c.getContext('2d');
    ctx.fillStyle = '#fff';
    ctx.fillRect(0, 0, c.width, c.height);
    onChange && onChange('');
  };

  return (
    <div>
      {label && <div style={{fontSize:12, color:'var(--ink-3)', marginBottom:4, fontWeight:600}}>{label}</div>}
      <div style={{border:'1px solid var(--line-1)', borderRadius:8, background:'#fff', position:'relative'}}>
        <canvas ref={canvasRef} width={400} height={height}
          style={{width:'100%', height, touchAction:'none', display:'block'}}
          onMouseDown={start} onMouseMove={move} onMouseUp={end} onMouseLeave={end}
          onTouchStart={start} onTouchMove={move} onTouchEnd={end} />
        <button type="button" onClick={clear}
          style={{position:'absolute', top:4, right:4, background:'rgba(255,255,255,0.9)', border:'1px solid var(--line-1)', borderRadius:4, padding:'2px 6px', fontSize:11, cursor:'pointer'}}>
          Effacer
        </button>
      </div>
    </div>
  );
}

function TouchNumpad({ onPress, onClose }) {
  const key = (k) => ({
    fontSize: 28, fontWeight: 700, padding: '14px 0', background: '#fff',
    border: '1px solid var(--line-1)', borderRadius: 10, cursor: 'pointer',
    touchAction: 'manipulation', userSelect: 'none',
  });
  return (
    <div style={{
      position:'fixed', left:0, right:0, bottom:0, zIndex:80,
      background:'linear-gradient(180deg, #f4f6fa, #e8ecf2)',
      padding:'8px 8px 12px', borderTop:'1px solid var(--line-1)',
      boxShadow:'0 -6px 20px rgba(0,0,0,0.08)',
    }}>
      <div style={{display:'grid', gridTemplateColumns:'repeat(4, 1fr)', gap:6, maxWidth:520, margin:'0 auto'}}>
        {['7','8','9','⌫','4','5','6','-','1','2','3',',','0','.','✓','→'].map(k => {
          const label = k;
          const isAction = ['⌫','✓','→','-',','].includes(k);
          return (
            <button key={k} onClick={() => onPress(k)}
              style={{...key(k),
                background: k === '✓' ? 'var(--vival-red)' : k === '→' ? '#0a7' : isAction ? '#f1f3f7' : '#fff',
                color: k === '✓' || k === '→' ? '#fff' : 'var(--ink-1)',
                gridColumn: 'auto',
              }}>
              {label}
            </button>
          );
        })}
      </div>
      <div style={{textAlign:'center', marginTop:6}}>
        <button onClick={onClose} style={{background:'transparent', border:0, color:'var(--ink-3)', fontSize:12, cursor:'pointer'}}>Fermer le pavé</button>
      </div>
    </div>
  );
}

function TemperatureTodayTab({ equipments, moment, setMoment, onReload }) {
  const { showToast } = useApp();
  const today = isoDate(new Date());
  const [yesterdayByEquip, setYesterdayByEquip] = useState({});
  const [todayByEquip, setTodayByEquip] = useState({}); // { equipId: {temperature_c, corrective_action, ...} }
  const [values, setValues] = useState({}); // { equipId: "1.8" } en cours de saisie
  const [focusIdx, setFocusIdx] = useState(0);
  const [correctives, setCorrectives] = useState({});
  const [takenName, setTakenName] = useState('');
  const [takenSig, setTakenSig] = useState('');
  const [validatedName, setValidatedName] = useState('');
  const [validatedSig, setValidatedSig] = useState('');
  const [saving, setSaving] = useState(false);

  // Reset complet quand on change de moment (matin <-> aprem)
  // Evite de garder les valeurs saisies dans l'autre moment en memoire
  useEffect(() => {
    setValues({});
    setCorrectives({});
    setTakenSig('');
    setValidatedSig('');
    setValidatedName('');
    setFocusIdx(0);
  }, [moment]);

  // Charge valeurs hier + aujourd'hui (pour edit si déjà saisi)
  useEffect(() => {
    (async () => {
      const yesterday = isoDate(new Date(Date.now() - 86400000));
      const equipIds = equipments.map(e => e.id);
      if (!equipIds.length) return;
      const { data: rY } = await window.sb.from('temperature_readings')
        .select('equipment_id, temperature_c, moment').in('equipment_id', equipIds)
        .eq('reading_date', yesterday).eq('moment', moment);
      const mY = {}; for (const r of rY || []) mY[r.equipment_id] = r.temperature_c;
      setYesterdayByEquip(mY);

      const { data: rT } = await window.sb.from('temperature_readings')
        .select('*').in('equipment_id', equipIds)
        .eq('reading_date', today).eq('moment', moment);
      const mT = {}; const mV = {}; const mC = {};
      for (const r of rT || []) {
        mT[r.equipment_id] = r;
        mV[r.equipment_id] = String(r.temperature_c).replace('.', ',');
        if (r.corrective_action) mC[r.equipment_id] = r.corrective_action;
      }
      setTodayByEquip(mT);
      // Remplace complètement : on affiche UNIQUEMENT les valeurs du moment courant
      setValues(mV);
      setCorrectives(mC);
      // Si une signature existe déjà pour aujourd'hui CE moment, récupère-la
      const firstR = Object.values(mT)[0];
      if (firstR) {
        if (firstR.taken_by_name) setTakenName(firstR.taken_by_name);
        if (firstR.taken_signature) setTakenSig(firstR.taken_signature);
        if (firstR.validated_by_name) setValidatedName(firstR.validated_by_name);
        if (firstR.validated_signature) setValidatedSig(firstR.validated_signature);
      } else {
        // Par défaut, récupère l'utilisateur courant comme preneur
        const u = getCurrentUserName();
        if (u.name && !takenName) setTakenName(u.name);
      }
    })();
  }, [moment, equipments]);

  const parseVal = (s) => {
    if (s == null || s === '' || s === '-') return null;
    const n = Number(String(s).replace(',', '.'));
    return Number.isFinite(n) ? n : null;
  };
  const isOutOfRange = (eq, v) => {
    if (v == null) return false;
    if (eq.min_c != null && v < eq.min_c) return true;
    if (eq.max_c != null && v > eq.max_c) return true;
    return false;
  };

  const current = equipments[focusIdx];

  const advanceOrClose = () => {
    // Cherche le prochain équipement sans valeur
    const nextIdx = equipments.findIndex((eq, i) => i > focusIdx && parseVal(values[eq.id]) == null);
    if (nextIdx >= 0) {
      setFocusIdx(nextIdx);
    } else {
      // Vérifie s'il reste des trous AVANT la position actuelle
      const backIdx = equipments.findIndex(eq => parseVal(values[eq.id]) == null);
      if (backIdx >= 0 && backIdx !== focusIdx) {
        setFocusIdx(backIdx);
      } else {
        // Tout est saisi, ferme le pavé
        setFocusIdx(-1);
      }
    }
  };

  const press = (k) => {
    if (!current) return;
    const id = current.id;
    const cur = values[id] ?? '';
    if (k === '⌫') { setValues(v => ({ ...v, [id]: cur.slice(0, -1) })); return; }
    if (k === '✓') {
      // valide la case actuelle et avance (ou ferme si terminé)
      setTimeout(() => advanceOrClose(), 100);
      return;
    }
    if (k === '→') {
      // valide valeur d'hier si vide, ou valeur tapée, puis passe au suivant
      if (!cur && yesterdayByEquip[id] != null) {
        setValues(v => ({ ...v, [id]: String(yesterdayByEquip[id]).replace('.', ',') }));
      }
      setTimeout(() => advanceOrClose(), 100);
      return;
    }
    if (k === ',' || k === '.') {
      if (cur.includes(',')) return;
      setValues(v => ({ ...v, [id]: (cur === '' ? '0,' : cur + ',') }));
      return;
    }
    if (k === '-') {
      setValues(v => ({ ...v, [id]: cur.startsWith('-') ? cur.slice(1) : '-' + cur }));
      return;
    }
    // chiffre
    let next = cur + k;
    // Auto-signe pour surgelés (si pas déjà signé)
    if (current.kind === 'surgele' && !cur && next && !next.startsWith('-')) next = '-' + next;
    setValues(v => ({ ...v, [id]: next }));
    // Auto-advance intelligent selon le type d'équipement
    const nn = next.replace('-', '');
    const advance = () => setTimeout(() => advanceOrClose(), 220);
    if (nn.includes(',')) {
      const dec = nn.split(',')[1] || '';
      if (dec.length >= 1) advance();
    } else {
      if (current.kind === 'surgele' && nn.length >= 2) advance();
    }
  };

  const save = async () => {
    if (!takenName.trim()) { showToast('Nom du preneur obligatoire'); return; }
    if (!takenSig) { showToast('Signature du preneur obligatoire'); return; }
    const storeId = window.VivalData?._storeId || null;
    const rows = [];
    const missing = [];
    const needCorrective = [];
    for (const eq of equipments) {
      const v = parseVal(values[eq.id]);
      if (v == null) { missing.push(eq.name); continue; }
      const oor = isOutOfRange(eq, v);
      if (oor && !(correctives[eq.id] || '').trim()) needCorrective.push(eq.name);
      rows.push({
        store_id: storeId, equipment_id: eq.id, reading_date: today, moment,
        temperature_c: v, out_of_range: oor,
        corrective_action: oor ? (correctives[eq.id] || '').trim() : null,
        taken_by_name: takenName.trim(), taken_by_user_id: getCurrentUserName().id,
        taken_signature: takenSig, taken_at: new Date().toISOString(),
        validated_by_name: validatedName.trim() || null,
        validated_signature: validatedSig || null,
        validated_at: validatedSig ? new Date().toISOString() : null,
      });
    }
    if (missing.length) { showToast('Températures manquantes : ' + missing.join(', ')); return; }
    if (needCorrective.length) { showToast('Action corrective requise pour : ' + needCorrective.join(', ')); return; }
    setSaving(true);
    const { error } = await window.sb.from('temperature_readings')
      .upsert(rows, { onConflict: 'equipment_id,reading_date,moment' });
    setSaving(false);
    if (error) { showToast('Erreur : ' + error.message); return; }
    showToast(`Relevé du ${moment === 'matin' ? 'matin' : 'après-midi'} enregistré ✓`);
    onReload && onReload();
  };

  const filled = equipments.filter(e => parseVal(values[e.id]) != null).length;
  const total = equipments.length;

  return (
    <div style={{padding: focusIdx >= 0 ? '0 16px 260px' : '0 16px 24px'}}>
      {/* Bandeau moment actif — gros, pour eviter toute confusion */}
      <div style={{
        padding:'10px 14px', marginBottom:10, borderRadius:10,
        background: moment === 'matin' ? '#fff8e1' : '#e8eaff',
        border: `2px solid ${moment === 'matin' ? '#f59e0b' : '#6366f1'}`,
        display:'flex', alignItems:'center', gap:10,
      }}>
        <span style={{fontSize:22}}>{moment === 'matin' ? '☀' : '🌙'}</span>
        <div style={{flex:1}}>
          <div style={{fontSize:11, color:'var(--ink-3)', fontWeight:600, textTransform:'uppercase'}}>
            {Object.keys(todayByEquip).length >= equipments.length ? 'Déjà enregistré ✓ — modifie puis re-enregistre' : 'Tu saisis le relevé'}
          </div>
          <div style={{fontSize:16, fontWeight:800, color:'var(--ink-1)'}}>
            {moment === 'matin' ? 'DU MATIN' : 'DE L\u2019APRÈS-MIDI'}
          </div>
        </div>
      </div>

      {/* Selecteur moment */}
      <div style={{display:'flex', gap:8, marginBottom:16}}>
        {[{id:'matin', label:'☀ Matin'},{id:'aprem', label:'🌙 Après-midi'}].map(m => (
          <button key={m.id} onClick={() => setMoment(m.id)}
            style={{
              flex:1, padding:'12px', border:0, borderRadius:10, cursor:'pointer',
              background: moment === m.id ? 'var(--vival-red)' : '#fff',
              color: moment === m.id ? '#fff' : 'var(--ink-1)',
              fontWeight:700, fontSize:15, border:'1px solid var(--line-1)',
            }}>{m.label}</button>
        ))}
      </div>

      {/* Progression */}
      <div style={{fontSize:12, color:'var(--ink-3)', marginBottom:8}}>
        {filled} / {total} relevés saisis {filled === total && '✓'}
      </div>
      <div style={{height:4, background:'#e5e7eb', borderRadius:2, marginBottom:16, overflow:'hidden'}}>
        <div style={{height:'100%', width: `${(filled/Math.max(1,total))*100}%`, background:'var(--vival-red)', transition:'width .25s'}} />
      </div>

      {/* Liste equipements */}
      <div style={{display:'flex', flexDirection:'column', gap:6}}>
        {equipments.map((eq, idx) => {
          const v = parseVal(values[eq.id]);
          const oor = isOutOfRange(eq, v);
          const isFocus = idx === focusIdx;
          const rangeLabel = eq.min_c != null && eq.max_c != null ? `${eq.min_c}°C à ${eq.max_c}°C`
            : eq.max_c != null ? `≤ ${eq.max_c}°C` : eq.min_c != null ? `≥ ${eq.min_c}°C` : '';
          return (
            <div key={eq.id} onClick={() => setFocusIdx(idx)}
              style={{
                display:'grid', gridTemplateColumns:'1fr auto', gap:8,
                padding:'10px 12px', borderRadius:10, cursor:'pointer',
                background: isFocus ? '#fff' : '#fafbfd',
                border: isFocus ? '2px solid var(--vival-red)' : '1px solid var(--line-1)',
                alignItems:'center',
              }}>
              <div>
                <div style={{fontWeight:700, fontSize:15}}>{eq.name}</div>
                <div style={{fontSize:11, color:'var(--ink-3)'}}>
                  Norme {rangeLabel}
                  {yesterdayByEquip[eq.id] != null && <> · Hier : {String(yesterdayByEquip[eq.id]).replace('.', ',')}°C</>}
                </div>
                {oor && (
                  <input type="text" placeholder="Action corrective (obligatoire)"
                    value={correctives[eq.id] || ''}
                    onChange={e => setCorrectives(c => ({...c, [eq.id]: e.target.value}))}
                    onClick={e => e.stopPropagation()}
                    style={{width:'100%', marginTop:6, padding:'6px 8px', border:'1px solid #ef4444', borderRadius:6, fontSize:12}} />
                )}
              </div>
              <div style={{textAlign:'right', minWidth:90}}>
                <div style={{
                  fontSize:24, fontWeight:800,
                  color: oor ? '#ef4444' : v != null ? '#0a7' : 'var(--ink-4)',
                  fontFamily:'ui-monospace, monospace',
                }}>
                  {values[eq.id] || '—'}{v != null && '°C'}
                </div>
              </div>
            </div>
          );
        })}
      </div>

      {/* Nom + signatures */}
      <div style={{marginTop:20, padding:12, background:'#fff', borderRadius:10, border:'1px solid var(--line-1)'}}>
        <div style={{fontSize:13, fontWeight:700, marginBottom:10}}>Signature du relevé</div>
        <div style={{marginBottom:10}}>
          <label style={{fontSize:12, color:'var(--ink-3)', fontWeight:600}}>Pris par *</label>
          <input type="text" value={takenName} onChange={e => setTakenName(e.target.value)}
            placeholder="Nom du preneur"
            style={{width:'100%', padding:'8px 10px', border:'1px solid var(--line-1)', borderRadius:6, marginTop:4, fontSize:14}} />
        </div>
        <SignaturePad value={takenSig} onChange={setTakenSig} label="Signature du preneur *" />

        <div style={{marginTop:14, marginBottom:10}}>
          <label style={{fontSize:12, color:'var(--ink-3)', fontWeight:600}}>Validé par (gérant, optionnel)</label>
          <input type="text" value={validatedName} onChange={e => setValidatedName(e.target.value)}
            placeholder="Nom du gérant"
            style={{width:'100%', padding:'8px 10px', border:'1px solid var(--line-1)', borderRadius:6, marginTop:4, fontSize:14}} />
        </div>
        <SignaturePad value={validatedSig} onChange={setValidatedSig} label="Signature du gérant" />
      </div>

      <div style={{marginTop:20, textAlign:'center'}}>
        <Button variant="default" onClick={save} block size="lg" icon="check"
          style={{background:'var(--vival-red)', color:'#fff', fontSize:16, fontWeight:800}}
          disabled={saving}>
          {saving ? 'Enregistrement…' : `Enregistrer le relevé du ${moment === 'matin' ? 'matin' : 'après-midi'}`}
        </Button>
      </div>

      {/* Bouton flottant "Enregistrer" visible même pavé ouvert */}
      {focusIdx >= 0 && (
        <button onClick={save} disabled={saving}
          style={{
            position:'fixed', bottom: 270, right: 12, zIndex: 81,
            background: filled === total ? 'var(--vival-red)' : '#0a7',
            color:'#fff', border:0, borderRadius: 30, padding: '12px 18px',
            fontSize: 14, fontWeight: 800, cursor:'pointer',
            boxShadow: '0 6px 16px rgba(0,0,0,0.25)',
            display:'flex', alignItems:'center', gap:6,
          }}>
          {filled === total ? `✓ Enregistrer (${moment === 'matin' ? 'matin' : 'aprem'})` : `${filled}/${total} — ${moment === 'matin' ? 'matin' : 'aprem'}`}
        </button>
      )}

      {focusIdx >= 0 && <TouchNumpad onPress={press} onClose={() => setFocusIdx(-1)} />}
    </div>
  );
}

function TemperatureHistoryTab({ equipments, onExport }) {
  const [month, setMonth] = useState(() => {
    const d = new Date();
    return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0');
  });
  const [readings, setReadings] = useState([]);

  useEffect(() => {
    (async () => {
      const [y, m] = month.split('-').map(Number);
      const from = `${month}-01`;
      const lastDay = new Date(y, m, 0).getDate();
      const to = `${month}-${String(lastDay).padStart(2, '0')}`;
      const equipIds = equipments.map(e => e.id);
      if (!equipIds.length) { setReadings([]); return; }
      const { data } = await window.sb.from('temperature_readings')
        .select('*').in('equipment_id', equipIds)
        .gte('reading_date', from).lte('reading_date', to);
      setReadings(data || []);
    })();
  }, [month, equipments]);

  const [y, m] = month.split('-').map(Number);
  const daysInMonth = new Date(y, m, 0).getDate();
  const monthLabel = new Date(y, m - 1, 1).toLocaleDateString('fr-FR', { month: 'long', year: 'numeric' });

  // Index readings par day+equip+moment
  const idx = new Map();
  for (const r of readings) idx.set(r.equipment_id + '|' + r.reading_date + '|' + r.moment, r);

  const fmt = (v) => v == null ? '' : String(v).replace('.', ',');

  return (
    <div style={{padding:'0 16px 24px'}}>
      <div style={{display:'flex', gap:8, alignItems:'center', marginBottom:14, flexWrap:'wrap'}}>
        <label style={{fontSize:12, fontWeight:600}}>Mois</label>
        <input type="month" value={month} onChange={e => setMonth(e.target.value)}
          style={{padding:'6px 10px', border:'1px solid var(--line-1)', borderRadius:6}} />
        <Button variant="outline" size="sm" icon="printer" onClick={() => onExport(month, readings)}>Exporter PDF</Button>
      </div>

      <div style={{overflowX:'auto', background:'#fff', border:'1px solid var(--line-1)', borderRadius:10}}>
        <table style={{borderCollapse:'collapse', width:'100%', fontSize:12, minWidth: 60 + equipments.length * 120}}>
          <thead>
            <tr>
              <th rowSpan={2} style={{background:'#f4f6fa', padding:6, border:'1px solid var(--line-1)', position:'sticky', left:0, zIndex:2}}>Jour</th>
              {equipments.map(eq => (
                <th key={eq.id} colSpan={2} style={{background:'#f4f6fa', padding:6, border:'1px solid var(--line-1)', textAlign:'center'}}>{eq.name}</th>
              ))}
            </tr>
            <tr>
              {equipments.map(eq => (
                <React.Fragment key={eq.id}>
                  <th style={{background:'#eef0f5', padding:4, border:'1px solid var(--line-1)', fontSize:11}}>Matin</th>
                  <th style={{background:'#eef0f5', padding:4, border:'1px solid var(--line-1)', fontSize:11}}>Aprem</th>
                </React.Fragment>
              ))}
            </tr>
          </thead>
          <tbody>
            {Array.from({length: daysInMonth}, (_, i) => i + 1).map(day => {
              const date = `${month}-${String(day).padStart(2, '0')}`;
              return (
                <tr key={day}>
                  <td style={{background:'#f4f6fa', padding:6, border:'1px solid var(--line-1)', fontWeight:700, position:'sticky', left:0}}>
                    {String(day).padStart(2, '0')}/{String(m).padStart(2, '0')}
                  </td>
                  {equipments.map(eq => {
                    const rM = idx.get(eq.id + '|' + date + '|matin');
                    const rA = idx.get(eq.id + '|' + date + '|aprem');
                    const tipM = rM?.corrective_action ? `Action : ${rM.corrective_action}` : '';
                    const tipA = rA?.corrective_action ? `Action : ${rA.corrective_action}` : '';
                    return (
                      <React.Fragment key={eq.id}>
                        <td title={tipM}
                          style={{padding:4, border:'1px solid var(--line-1)', textAlign:'center', color: rM?.out_of_range ? '#ef4444' : 'var(--ink-1)', fontWeight: rM?.out_of_range ? 700 : 400, position:'relative'}}>
                          {rM ? fmt(rM.temperature_c) : ''}
                          {rM?.corrective_action && <span style={{color:'#059669', marginLeft:3, fontSize:10}} title={tipM}>✓</span>}
                        </td>
                        <td title={tipA}
                          style={{padding:4, border:'1px solid var(--line-1)', textAlign:'center', color: rA?.out_of_range ? '#ef4444' : 'var(--ink-1)', fontWeight: rA?.out_of_range ? 700 : 400, position:'relative'}}>
                          {rA ? fmt(rA.temperature_c) : ''}
                          {rA?.corrective_action && <span style={{color:'#059669', marginLeft:3, fontSize:10}} title={tipA}>✓</span>}
                        </td>
                      </React.Fragment>
                    );
                  })}
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>

      <p style={{fontSize:11, color:'var(--ink-3)', marginTop:8}}>
        Mois : <strong>{monthLabel}</strong> · Cellules en rouge : hors plage HACCP · Coche verte ✓ = action corrective renseignée (survol pour voir).
      </p>

    </div>
  );
}

// Loader des relevés du mois courant pour les onglets Actions correctives & Rapport
function useMonthReadings() {
  const [readings, setReadings] = useState([]);
  const [loading, setLoading] = useState(true);
  const month = new Date().toISOString().slice(0, 7);
  useEffect(() => {
    (async () => {
      setLoading(true);
      try {
        const sid = window.VivalData?._storeId;
        const { data } = await window.sb.from('temperature_readings').select('*')
          .eq('store_id', sid)
          .gte('reading_date', month + '-01').lte('reading_date', month + '-31')
          .order('reading_date', { ascending: false });
        setReadings(data || []);
      } catch (e) { console.warn('readings', e); }
      setLoading(false);
    })();
  }, []);
  return { readings, loading, month };
}

function TemperatureCorrectionsTab({ equipments }) {
  const { readings, loading } = useMonthReadings();
  if (loading) return <div style={{padding:20, textAlign:'center', color:'var(--ink-3)'}}>Chargement…</div>;
  return <TemperatureCorrectivesList readings={readings} equipments={equipments} />;
}

function TemperatureReportTab({ equipments }) {
  const { readings, loading, month } = useMonthReadings();
  if (loading) return <div style={{padding:20, textAlign:'center', color:'var(--ink-3)'}}>Chargement…</div>;
  return <TemperatureEmailSender month={month} readings={readings} equipments={equipments} />;
}

// Liste synthétique des actions correctives du mois
function TemperatureCorrectivesList({ readings, equipments }) {
  const withAction = readings.filter(r => r.corrective_action && r.corrective_action.trim());
  const oor = readings.filter(r => r.out_of_range);
  const oorSansAction = oor.filter(r => !r.corrective_action || !r.corrective_action.trim());
  const byEquip = new Map(equipments.map(e => [e.id, e.name]));

  return (
    <div style={{marginTop:20, padding:'14px 16px', background:'#fff', border:'1px solid var(--line-1)', borderRadius:10}}>
      <div style={{display:'flex', alignItems:'center', gap:12, flexWrap:'wrap', marginBottom:10}}>
        <h3 style={{margin:0, fontSize:14, fontWeight:800}}>📋 Actions correctives du mois</h3>
        <span style={{padding:'2px 8px', background:'#d1fae5', color:'#065f46', borderRadius:10, fontSize:11, fontWeight:700}}>
          {withAction.length} action{withAction.length > 1 ? 's' : ''} enregistrée{withAction.length > 1 ? 's' : ''}
        </span>
        {oorSansAction.length > 0 && (
          <span style={{padding:'2px 8px', background:'#fee2e2', color:'#991b1b', borderRadius:10, fontSize:11, fontWeight:700}}>
            ⚠ {oorSansAction.length} hors plage sans action
          </span>
        )}
      </div>
      {withAction.length === 0 ? (
        <div style={{fontSize:12, color:'var(--ink-3)', padding:'10px 0'}}>
          Aucune action corrective enregistrée ce mois (tant mieux — les températures sont OK ou les cas hors plage ont été oubliés).
        </div>
      ) : (
        <table style={{width:'100%', fontSize:12, borderCollapse:'collapse'}}>
          <thead>
            <tr style={{textAlign:'left', color:'var(--ink-3)', borderBottom:'1px solid var(--line-1)'}}>
              <th style={{padding:'6px 8px'}}>Date</th>
              <th style={{padding:'6px 8px'}}>Équipement</th>
              <th style={{padding:'6px 8px'}}>Moment</th>
              <th style={{padding:'6px 8px', textAlign:'right'}}>Temp.</th>
              <th style={{padding:'6px 8px'}}>Action corrective</th>
              <th style={{padding:'6px 8px'}}>Préleveur</th>
            </tr>
          </thead>
          <tbody>
            {withAction
              .sort((a, b) => (b.reading_date + (b.moment || '')).localeCompare(a.reading_date + (a.moment || '')))
              .map((r, i) => (
                <tr key={r.id || i} style={{borderTop:'1px solid var(--line-2)'}}>
                  <td style={{padding:'6px 8px', fontWeight:600}}>{new Date(r.reading_date).toLocaleDateString('fr-FR')}</td>
                  <td style={{padding:'6px 8px'}}>{byEquip.get(r.equipment_id) || r.equipment_id}</td>
                  <td style={{padding:'6px 8px'}}>{r.moment === 'matin' ? 'Matin' : 'Après-midi'}</td>
                  <td style={{padding:'6px 8px', textAlign:'right', color: r.out_of_range ? '#dc2626' : 'inherit', fontWeight: r.out_of_range ? 700 : 400}}>
                    {String(r.temperature_c).replace('.', ',')} °C
                  </td>
                  <td style={{padding:'6px 8px', fontStyle:'italic', color:'#065f46'}}>{r.corrective_action}</td>
                  <td style={{padding:'6px 8px', fontSize:11, color:'var(--ink-3)'}}>{r.taken_by_name || '—'}</td>
                </tr>
              ))}
          </tbody>
        </table>
      )}
    </div>
  );
}

// Envoi PDF HACCP par email au contrôleur
function TemperatureEmailSender({ month, readings, equipments }) {
  const { showToast } = useApp();
  const [open, setOpen] = useState(false);
  const [email, setEmail] = useState('');
  const [name, setName] = useState('');
  const [fromDate, setFromDate] = useState(() => `${month}-01`);
  const [toDate, setToDate] = useState(() => {
    const [y, m] = month.split('-').map(Number);
    const last = new Date(y, m, 0).getDate();
    return `${month}-${String(last).padStart(2, '0')}`;
  });
  const [sending, setSending] = useState(false);

  // Charge jsPDF + autoTable via CDN (une seule fois)
  const ensureJsPDF = async () => {
    if (window.jspdf && window.jspdf.jsPDF && window.jspdf.jsPDF.API?.autoTable) return window.jspdf.jsPDF;
    const loadScript = (src) => new Promise((res, rej) => {
      const s = document.createElement('script'); s.src = src;
      s.onload = () => res(); s.onerror = () => rej(new Error('load failed ' + src));
      document.head.appendChild(s);
    });
    if (!window.jspdf || !window.jspdf.jsPDF) {
      await loadScript('https://cdn.jsdelivr.net/npm/jspdf@2.5.2/dist/jspdf.umd.min.js');
    }
    if (!window.jspdf.jsPDF.API?.autoTable) {
      await loadScript('https://cdn.jsdelivr.net/npm/jspdf-autotable@3.8.4/dist/jspdf.plugin.autotable.min.js');
    }
    return window.jspdf.jsPDF;
  };

  const buildPdfBase64 = async () => {
    const jsPDF = await ensureJsPDF();
    // Filtre les relevés dans la période demandée
    const inRange = (readings || []).filter(r =>
      r.reading_date >= fromDate && r.reading_date <= toDate
    );
    const byEquip = new Map(equipments.map(e => [e.id, e]));

    const doc = new jsPDF({ orientation: 'landscape', unit: 'mm', format: 'a4' });
    const storeName = window.VivalData?._storeName || 'Magasin Vival';
    const store = {
      name: storeName,
    };

    // Header
    doc.setFontSize(16);
    doc.setFont('helvetica', 'bold');
    doc.text('Rapport HACCP - Releves de temperature', 14, 14);
    doc.setFontSize(10);
    doc.setFont('helvetica', 'normal');
    doc.text(`Document destine a : ${name || '-'}`, 14, 20);

    // Bloc info
    doc.setFontSize(9);
    const periodStr = new Date(fromDate).toLocaleDateString('fr-FR') + ' au ' + new Date(toDate).toLocaleDateString('fr-FR');
    doc.text(`Etablissement : ${storeName}`, 14, 28);
    doc.text(`Periode : ${periodStr}  |  Relevés : ${inRange.length}`, 14, 33);
    doc.text(`Genere le : ${new Date().toLocaleString('fr-FR')}`, 14, 38);

    // Synthèse
    const oor = inRange.filter(r => r.out_of_range);
    const corr = oor.filter(r => r.corrective_action && r.corrective_action.trim());
    doc.setFont('helvetica', 'bold');
    doc.text('Synthese :', 14, 46);
    doc.setFont('helvetica', 'normal');
    doc.text(`  Conformes : ${inRange.length - oor.length}   |   Hors plage : ${oor.length}   |   Avec action corrective : ${corr.length}`, 14, 51);

    // Tableau des relevés (triés par date + moment + équipement)
    const sorted = [...inRange].sort((a, b) => {
      if (a.reading_date !== b.reading_date) return a.reading_date < b.reading_date ? -1 : 1;
      if (a.moment !== b.moment) return a.moment < b.moment ? -1 : 1;
      const na = byEquip.get(a.equipment_id)?.name || ''; const nb = byEquip.get(b.equipment_id)?.name || '';
      return na.localeCompare(nb);
    });

    const kindLabel = (k) => ({ frigo:'Frigo', surgele:'Surgele', meuble_coupe:'Meuble coupe', chambre_froide:'Chambre froide', autre:'Autre' }[k] || k || '');
    const rows = sorted.map(r => {
      const eq = byEquip.get(r.equipment_id) || {};
      const range = eq.min_c != null && eq.max_c != null ? `${eq.min_c} a ${eq.max_c}°C` : '';
      const takenAt = r.taken_at ? new Date(r.taken_at).toLocaleString('fr-FR') : '';
      return [
        new Date(r.reading_date).toLocaleDateString('fr-FR'),
        r.moment === 'matin' ? 'Matin' : 'Apres-midi',
        `${eq.name || r.equipment_id}\n${kindLabel(eq.kind)} (${range})`,
        `${String(r.temperature_c).replace('.', ',')} °C${r.out_of_range ? ' HP' : ''}`,
        r.corrective_action || (r.out_of_range ? '(Non renseignee)' : '-'),
        `${r.taken_by_name || '-'}\n${takenAt}`,
      ];
    });

    window.jspdf.jsPDF.API.autoTable.call(doc, {
      startY: 56,
      head: [['Date', 'Moment', 'Equipement', 'Temp.', 'Action corrective', 'Preleveur / Horodatage']],
      body: rows,
      theme: 'grid',
      headStyles: { fillColor: [30, 58, 138], textColor: 255, fontSize: 9 },
      styles: { fontSize: 8, cellPadding: 2, overflow: 'linebreak' },
      columnStyles: {
        0: { cellWidth: 22 },
        1: { cellWidth: 22 },
        2: { cellWidth: 50 },
        3: { cellWidth: 28, halign: 'right' },
        4: { cellWidth: 70 },
        5: { cellWidth: 70 },
      },
      didParseCell: (data) => {
        // Lignes hors plage en rouge
        if (data.section === 'body' && sorted[data.row.index]?.out_of_range) {
          data.cell.styles.fillColor = [254, 226, 226];
          if (data.column.index === 3) data.cell.styles.textColor = [220, 38, 38];
        }
      },
    });

    // Certification
    const finalY = (doc).lastAutoTable?.finalY || 56;
    doc.setFontSize(8);
    doc.setFont('helvetica', 'italic');
    const certY = Math.min(finalY + 8, doc.internal.pageSize.getHeight() - 20);
    doc.text('Ce rapport a ete genere automatiquement par MonVival. Chaque releve comporte un horodatage precis (date + heure', 14, certY);
    doc.text('de saisie) et le nom du preleveur. Les actions correctives ont ete saisies au moment du releve ou ulterieurement.', 14, certY + 4);

    // Retourne base64
    const dataUri = doc.output('datauristring');
    // Format: data:application/pdf;base64,<...>
    const base64 = dataUri.split(',')[1] || '';
    return { base64, inRange };
  };

  const handleSend = async () => {
    if (!email || !/^\S+@\S+\.\S+$/.test(email)) { showToast('Email invalide'); return; }
    if (!name.trim()) { showToast('Nom du contrôleur requis'); return; }
    setSending(true);
    try {
      const { base64, inRange } = await buildPdfBase64();
      const periodLabel = `${fromDate}_${toDate}`;
      const filename = `HACCP_${(window.VivalData?._storeName || 'magasin').replace(/[^a-z0-9-]+/gi, '_')}_${periodLabel}.pdf`;
      const { data, error } = await window.sb.functions.invoke('send-haccp-report', {
        body: {
          store_id: window.VivalData?._storeId,
          from_date: fromDate,
          to_date: toDate,
          controleur_email: email.trim(),
          controleur_name: name.trim(),
          pdf_base64: base64,
          pdf_filename: filename,
          readings_count: inRange.length,
        }
      });
      if (error) throw error;
      showToast(`✓ Rapport HACCP (PDF) envoyé à ${email}`);
      setOpen(false);
    } catch (e) {
      console.error('send-haccp-report', e);
      showToast('Erreur : ' + (e.message || e));
    }
    setSending(false);
  };

  const downloadPdf = async () => {
    setSending(true);
    try {
      const { base64 } = await buildPdfBase64();
      // Download via data URI
      const a = document.createElement('a');
      a.href = 'data:application/pdf;base64,' + base64;
      a.download = `HACCP_${fromDate}_${toDate}.pdf`;
      a.click();
    } catch (e) { showToast('Erreur PDF : ' + (e.message || e)); }
    setSending(false);
  };

  if (!open) {
    return (
      <div style={{marginTop:16}}>
        <Button variant="outline" icon="envelope" onClick={() => setOpen(true)}>
          📧 Envoyer le rapport HACCP au contrôleur
        </Button>
      </div>
    );
  }

  return (
    <div style={{marginTop:16, padding:16, background:'#eff6ff', border:'1px solid #bfdbfe', borderRadius:10}}>
      <h3 style={{margin:'0 0 10px', fontSize:14, fontWeight:800, color:'#1e3a8a'}}>📧 Envoyer le rapport HACCP</h3>
      <p style={{fontSize:12, color:'#1e3a8a', margin:'0 0 12px'}}>
        Génère un rapport PDF des relevés de température (avec actions correctives) et l'envoie par email au contrôleur qualité ou vétérinaire.
      </p>
      <div style={{display:'grid', gridTemplateColumns:'repeat(auto-fit, minmax(180px, 1fr))', gap:10, marginBottom:12}}>
        <div>
          <label style={{fontSize:11, fontWeight:600, color:'var(--ink-2)'}}>Nom du contrôleur</label>
          <Input value={name} onChange={e => setName(e.target.value)} placeholder="Ex. Mme Dupont (DGCCRF)" />
        </div>
        <div>
          <label style={{fontSize:11, fontWeight:600, color:'var(--ink-2)'}}>Email du contrôleur</label>
          <Input type="email" value={email} onChange={e => setEmail(e.target.value)} placeholder="controleur@exemple.fr" />
        </div>
        <div>
          <label style={{fontSize:11, fontWeight:600, color:'var(--ink-2)'}}>Du</label>
          <Input type="date" value={fromDate} onChange={e => setFromDate(e.target.value)} />
        </div>
        <div>
          <label style={{fontSize:11, fontWeight:600, color:'var(--ink-2)'}}>Au</label>
          <Input type="date" value={toDate} onChange={e => setToDate(e.target.value)} />
        </div>
      </div>
      <div style={{display:'flex', gap:8, justifyContent:'flex-end', flexWrap:'wrap'}}>
        <Button variant="ghost" onClick={() => setOpen(false)} disabled={sending}>Annuler</Button>
        <Button variant="outline" icon="download" onClick={downloadPdf} disabled={sending}>
          {sending ? '…' : 'Télécharger le PDF'}
        </Button>
        <Button variant="primary" icon="envelope" onClick={handleSend} disabled={sending || !email || !name}>
          {sending ? 'Envoi…' : 'Envoyer par email'}
        </Button>
      </div>
      <p style={{fontSize:11, color:'var(--ink-3)', marginTop:8, lineHeight:1.4}}>
        💡 Le PDF contient le détail ligne par ligne des relevés (date, heure exacte, équipement, température, action corrective, nom du préleveur) + la synthèse conformité.
      </p>
    </div>
  );
}

function TemperatureEquipmentsTab({ equipments, onReload }) {
  const { showToast } = useApp();
  const [editing, setEditing] = useState({});
  const [newEq, setNewEq] = useState(null);

  const save = async (eq) => {
    const payload = {
      name: (editing.name || '').trim() || eq.name,
      kind: editing.kind || eq.kind,
      min_c: editing.min_c === '' ? null : (editing.min_c != null ? Number(editing.min_c) : eq.min_c),
      max_c: editing.max_c === '' ? null : (editing.max_c != null ? Number(editing.max_c) : eq.max_c),
      active: editing.active != null ? editing.active : eq.active,
    };
    const { error } = await window.sb.from('temperature_equipments').update(payload).eq('id', eq.id);
    if (error) { showToast('Erreur : ' + error.message); return; }
    setEditing({});
    showToast('Équipement mis à jour');
    onReload && onReload();
  };

  const addNew = async () => {
    if (!newEq || !newEq.name?.trim()) return;
    const storeId = window.VivalData?._storeId || null;
    const { error } = await window.sb.from('temperature_equipments').insert({
      store_id: storeId,
      name: newEq.name.trim(),
      kind: newEq.kind || 'frigo',
      min_c: newEq.min_c === '' ? null : Number(newEq.min_c),
      max_c: newEq.max_c === '' ? null : Number(newEq.max_c),
      position: (equipments[equipments.length - 1]?.position || 0) + 1,
      active: true,
    });
    if (error) { showToast('Erreur : ' + error.message); return; }
    setNewEq(null);
    showToast('Équipement ajouté');
    onReload && onReload();
  };

  const remove = async (eq) => {
    if (!confirm(`Supprimer ${eq.name} ? (les relevés passés seront aussi supprimés)`)) return;
    const { error } = await window.sb.from('temperature_equipments').delete().eq('id', eq.id);
    if (error) { showToast('Erreur : ' + error.message); return; }
    showToast('Équipement supprimé');
    onReload && onReload();
  };

  return (
    <div style={{padding:'0 16px 24px'}}>
      <p style={{fontSize:13, color:'var(--ink-3)', marginBottom:14}}>
        Paramètre les équipements surveillés et leurs seuils HACCP. Un relevé hors plage déclenche une alerte et demande une action corrective.
      </p>

      <div style={{display:'flex', flexDirection:'column', gap:8}}>
        {equipments.map(eq => (
          <div key={eq.id} style={{padding:12, background:'#fff', border:'1px solid var(--line-1)', borderRadius:10}}>
            <div style={{display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom:8}}>
              <div style={{fontWeight:700}}>{eq.name}</div>
              <button onClick={() => remove(eq)} title="Supprimer"
                style={{background:'transparent', border:0, color:'#ef4444', cursor:'pointer', fontSize:13}}>× supprimer</button>
            </div>
            <div style={{display:'grid', gridTemplateColumns:'1fr 1fr 1fr 1fr', gap:8, fontSize:12}}>
              <div>
                <label style={{color:'var(--ink-3)'}}>Nom</label>
                <input defaultValue={eq.name} onChange={e => setEditing(x => ({...x, name: e.target.value}))}
                  onBlur={(e) => { if (e.target.value.trim() && e.target.value !== eq.name) save(eq); }}
                  style={{width:'100%', padding:'6px 8px', border:'1px solid var(--line-1)', borderRadius:6}} />
              </div>
              <div>
                <label style={{color:'var(--ink-3)'}}>Type</label>
                <select defaultValue={eq.kind} onChange={e => { setEditing(x => ({...x, kind: e.target.value})); setTimeout(() => save(eq), 0); }}
                  style={{width:'100%', padding:'6px 8px', border:'1px solid var(--line-1)', borderRadius:6}}>
                  <option value="frigo">Frigo</option>
                  <option value="surgele">Surgelé</option>
                  <option value="meuble_coupe">Meuble coupe</option>
                  <option value="chambre_froide">Chambre froide</option>
                  <option value="autre">Autre</option>
                </select>
              </div>
              <div>
                <label style={{color:'var(--ink-3)'}}>Min °C</label>
                <input type="number" step="0.1" defaultValue={eq.min_c ?? ''}
                  onChange={e => setEditing(x => ({...x, min_c: e.target.value}))}
                  onBlur={() => save(eq)}
                  style={{width:'100%', padding:'6px 8px', border:'1px solid var(--line-1)', borderRadius:6}} />
              </div>
              <div>
                <label style={{color:'var(--ink-3)'}}>Max °C</label>
                <input type="number" step="0.1" defaultValue={eq.max_c ?? ''}
                  onChange={e => setEditing(x => ({...x, max_c: e.target.value}))}
                  onBlur={() => save(eq)}
                  style={{width:'100%', padding:'6px 8px', border:'1px solid var(--line-1)', borderRadius:6}} />
              </div>
            </div>
          </div>
        ))}
      </div>

      {newEq ? (
        <div style={{marginTop:14, padding:12, background:'#fff', border:'2px solid var(--vival-red)', borderRadius:10}}>
          <div style={{fontWeight:700, marginBottom:8}}>Nouvel équipement</div>
          <div style={{display:'grid', gridTemplateColumns:'1fr 1fr 1fr 1fr', gap:8, fontSize:12}}>
            <input placeholder="Nom" value={newEq.name || ''} onChange={e => setNewEq({...newEq, name: e.target.value})}
              style={{padding:'6px 8px', border:'1px solid var(--line-1)', borderRadius:6}} />
            <select value={newEq.kind || 'frigo'} onChange={e => {
              const k = e.target.value;
              // Pre-remplit les seuils HACCP selon le type
              const preset = {
                frigo: { min_c: -1, max_c: 5 },
                meuble_coupe: { min_c: -1, max_c: 5 },
                chambre_froide: { min_c: -1, max_c: 5 },
                surgele: { min_c: -26, max_c: -18 },
                autre: { min_c: null, max_c: null },
              }[k] || {};
              setNewEq({...newEq, kind: k, min_c: preset.min_c, max_c: preset.max_c});
            }}
              style={{padding:'6px 8px', border:'1px solid var(--line-1)', borderRadius:6}}>
              <option value="frigo">Frigo (−1 à 5 °C)</option>
              <option value="surgele">Surgelé (−26 à −18 °C)</option>
              <option value="meuble_coupe">Meuble coupe (−1 à 5 °C)</option>
              <option value="chambre_froide">Chambre froide (−1 à 5 °C)</option>
              <option value="autre">Autre (saisie libre)</option>
            </select>
            <input type="number" step="0.1" placeholder="Min °C" value={newEq.min_c ?? ''} onChange={e => setNewEq({...newEq, min_c: e.target.value})}
              style={{padding:'6px 8px', border:'1px solid var(--line-1)', borderRadius:6}} />
            <input type="number" step="0.1" placeholder="Max °C" value={newEq.max_c ?? ''} onChange={e => setNewEq({...newEq, max_c: e.target.value})}
              style={{padding:'6px 8px', border:'1px solid var(--line-1)', borderRadius:6}} />
          </div>
          <div style={{display:'flex', gap:8, marginTop:10}}>
            <Button variant="default" size="sm" onClick={addNew}>Ajouter</Button>
            <Button variant="ghost" size="sm" onClick={() => setNewEq(null)}>Annuler</Button>
          </div>
        </div>
      ) : (
        <Button variant="outline" icon="plus" onClick={() => setNewEq({kind:'frigo', min_c: -1, max_c: 5})} style={{marginTop:14}}>Ajouter un équipement</Button>
      )}
    </div>
  );
}

function TemperaturesScreen() {
  const { showToast } = useApp();
  const [tab, setTab] = useState('today');
  const [moment, setMoment] = useState(() => new Date().getHours() < 14 ? 'matin' : 'aprem');
  const [equipments, setEquipments] = useState([]);
  const [loading, setLoading] = useState(true);

  const load = async () => {
    setLoading(true);
    const storeId = window.VivalData?._storeId || null;
    let q = window.sb.from('temperature_equipments').select('*').eq('active', true).order('position');
    if (storeId) q = q.eq('store_id', storeId);
    const { data } = await q;
    setEquipments(data || []);
    setLoading(false);
  };
  useEffect(() => { load(); }, []);

  const exportPdf = (monthStr, readings) => {
    const [y, m] = monthStr.split('-').map(Number);
    const daysInMonth = new Date(y, m, 0).getDate();
    const monthLabel = new Date(y, m - 1, 1).toLocaleDateString('fr-FR', { month: 'long', year: 'numeric' });
    const idx = new Map();
    for (const r of readings) idx.set(r.equipment_id + '|' + r.reading_date + '|' + r.moment, r);
    const fmt = (v) => v == null ? '' : String(v).replace('.', ',');
    const storeName = (window.VivalData?._storeName) || 'Vival';
    let thead1 = '<th rowspan="2">Jour</th>';
    let thead2 = '';
    for (const eq of equipments) {
      const range = eq.min_c != null && eq.max_c != null ? `${eq.min_c}°C à ${eq.max_c}°C` : eq.max_c != null ? `≤${eq.max_c}°C` : '';
      thead1 += `<th colspan="2">${eq.name}<br><small>${range}</small></th>`;
      thead2 += '<th>Matin</th><th>Aprem</th>';
    }
    let rows = '';
    for (let day = 1; day <= daysInMonth; day++) {
      const date = `${monthStr}-${String(day).padStart(2, '0')}`;
      let tr = `<td class="day">${String(day).padStart(2,'0')}/${String(m).padStart(2,'0')}</td>`;
      for (const eq of equipments) {
        const rM = idx.get(eq.id + '|' + date + '|matin');
        const rA = idx.get(eq.id + '|' + date + '|aprem');
        tr += `<td class="${rM?.out_of_range ? 'oor' : ''}">${rM ? fmt(rM.temperature_c) : ''}</td>`;
        tr += `<td class="${rA?.out_of_range ? 'oor' : ''}">${rA ? fmt(rA.temperature_c) : ''}</td>`;
      }
      rows += `<tr>${tr}</tr>`;
    }
    // Signature : prend la 1ère signature disponible du mois
    const firstSig = readings.find(r => r.taken_signature);
    const firstValidSig = readings.find(r => r.validated_signature);
    const html = `<!doctype html><html><head><meta charset="utf-8"><title>Relevés températures ${monthLabel}</title>
      <style>
        @page { size: A4 landscape; margin: 6mm 8mm; }
        html, body { margin:0; padding:0; }
        body { font-family: Arial, sans-serif; font-size: 8px; color: #0a0f19; }
        .wrap { display:flex; flex-direction:column; height: calc(210mm - 12mm); }
        h1 { margin:0 0 2px; font-size: 13px; }
        .sub { color:#666; margin-bottom: 4px; font-size:9px; }
        table { border-collapse: collapse; width: 100%; table-layout: fixed; }
        th, td { border: 1px solid #888; padding: 1px 2px; text-align: center; line-height: 1.15; overflow: hidden; }
        th { background: #eef0f5; font-size: 8px; font-weight: 700; }
        th small { font-weight: 400; font-size: 7px; color: #555; }
        td.day { background:#f4f6fa; font-weight:700; width: 48px; }
        td.oor { color:#c00; font-weight:700; background:#fff0f0; }
        .sig-block { display: flex; justify-content: space-between; margin-top: auto; padding-top: 4px; gap:8px; page-break-inside: avoid; }
        .sig-box { flex:1; border:1px solid #888; padding:4px 6px; border-radius:3px; }
        .sig-box img { max-width: 100%; max-height: 32px; display:block; margin-top: 2px; }
        .sig-box .label { font-size:8px; color:#666; }
        .sig-box .name { font-size:9px; font-weight:700; }
        @media print { body { -webkit-print-color-adjust: exact; print-color-adjust: exact; } }
      </style></head>
      <body>
        <div class="wrap">
          <h1>Relevés de températures — ${storeName}</h1>
          <div class="sub">Mois : ${monthLabel} · Normes HACCP · Cellules rouges = hors plage</div>
          <table>
            <thead><tr>${thead1}</tr><tr>${thead2}</tr></thead>
            <tbody>${rows}</tbody>
          </table>
          <div class="sig-block">
            <div class="sig-box">
              <div class="label">Pris par</div>
              <div class="name">${firstSig?.taken_by_name || ''}</div>
              ${firstSig?.taken_signature ? `<img src="${firstSig.taken_signature}">` : ''}
            </div>
            <div class="sig-box">
              <div class="label">Validé par (gérant)</div>
              <div class="name">${firstValidSig?.validated_by_name || ''}</div>
              ${firstValidSig?.validated_signature ? `<img src="${firstValidSig.validated_signature}">` : ''}
            </div>
          </div>
        </div>
        <script>setTimeout(() => window.print(), 300);</script>
      </body></html>`;
    const w = window.open('', '_blank');
    w.document.write(html);
    w.document.close();
  };

  return (
    <>
      <div className="page-header">
        <div className="page-header-grow">
          <h1 className="page-title">Températures</h1>
          <p className="page-subtitle">Relevés HACCP quotidiens matin + après-midi</p>
        </div>
      </div>

      <div style={{padding:'0 16px 12px', display:'flex', gap:4, borderBottom:'1px solid var(--line-1)', marginBottom:14, overflowX:'auto'}}>
        {[
          {id:'today', label:'Aujourd\u2019hui'},
          {id:'history', label:'Historique'},
          {id:'equipments', label:'Équipements'},
          {id:'corrections', label:'\u{1F4CB} Actions correctives'},
          {id:'report', label:'\u{1F4E7} Rapport HACCP'},
        ].map(t => (
          <button key={t.id} onClick={() => setTab(t.id)}
            style={{
              padding:'10px 16px', border:0, background:'transparent', cursor:'pointer',
              fontSize:14, fontWeight:700, whiteSpace:'nowrap',
              borderBottom: tab === t.id ? '3px solid var(--vival-red)' : '3px solid transparent',
              color: tab === t.id ? 'var(--ink-1)' : 'var(--ink-3)',
              marginBottom:-1,
            }}>
            {t.label}
          </button>
        ))}
      </div>

      {loading ? <div style={{padding:20, textAlign:'center', color:'var(--ink-3)'}}>Chargement…</div>
        : tab === 'today' ? <TemperatureTodayTab equipments={equipments} moment={moment} setMoment={setMoment} onReload={load} />
        : tab === 'history' ? <TemperatureHistoryTab equipments={equipments} onExport={exportPdf} />
        : tab === 'corrections' ? <TemperatureCorrectionsTab equipments={equipments} />
        : tab === 'report' ? <TemperatureReportTab equipments={equipments} />
        : <TemperatureEquipmentsTab equipments={equipments} onReload={load} />}
    </>
  );
}

// ===== Settings =====
const DEFAULT_STORE = { name: 'Vival Le Grand-Lemps', address: '37-39 Grand Rue, 38690 Le Grand-Lemps', phone: '04 76 55 80 80', siret: '', tvaNumber: '' };
function getStoreInfoCache() {
  try { return { ...DEFAULT_STORE, ...JSON.parse(localStorage.getItem('vival_store') || '{}') }; }
  catch { return DEFAULT_STORE; }
}
function saveStoreInfoCache(info) { localStorage.setItem('vival_store', JSON.stringify(info)); }

// ===== Modules : activation/desactivation de fonctionnalites par magasin =====
const DEFAULT_MODULES = {
  stock: true,         // Gestion des stocks (Stock theorique, Ruptures, colonne Stock)
  haccp: true,         // Releves de temperature HACCP
  caisse_app: true,    // Caisse integree dans MonVival (Vente/Commandes/Livraison/Clients)
};
function getModules() {
  try { return { ...DEFAULT_MODULES, ...JSON.parse(localStorage.getItem('vival_modules') || '{}') }; }
  catch { return DEFAULT_MODULES; }
}
function setModule(key, value) {
  const cur = getModules();
  cur[key] = value;
  try { localStorage.setItem('vival_modules', JSON.stringify(cur)); } catch (_) {}
  try { window.dispatchEvent(new CustomEvent('vival-modules-changed', { detail: cur })); } catch (_) {}
}
function useModule(key) {
  const [val, setVal] = useState(() => getModules()[key]);
  useEffect(() => {
    const sync = () => setVal(getModules()[key]);
    window.addEventListener('vival-modules-changed', sync);
    return () => window.removeEventListener('vival-modules-changed', sync);
  }, [key]);
  return val;
}

function ModulesSettingsTab({ showToast }) {
  const [modules, setModulesState] = useState(getModules());
  const toggle = (key) => {
    const next = !modules[key];
    setModule(key, next);
    setModulesState(getModules());
    showToast(`Module ${key} ${next ? 'activé' : 'désactivé'}`);
  };
  const items = [
    {
      key: 'stock',
      label: 'Gestion des stocks',
      desc: 'Active la colonne Stock du catalogue, le Stock théorique dans Rapports, l\'alerte Ruptures et le Stock dormant.',
      icon: '📦',
    },
    {
      key: 'haccp',
      label: 'HACCP - Relevés de température',
      desc: 'Active l\'écran Températures, les rappels et l\'envoi du rapport mensuel au contrôleur.',
      icon: '🌡️',
    },
    {
      key: 'caisse_app',
      label: 'Caisse intégrée MonVival',
      desc: 'Active l\'écran Caisse et la prise de commandes directement dans l\'app. Désactive si tu utilises uniquement la caisse Casino + imports PDF.',
      icon: '🛒',
    },
  ];
  return (
    <div style={{display:'grid', gap:12}}>
      <div style={{padding:'14px 16px', background:'#eff6ff', border:'1px solid #bfdbfe', borderRadius:10, fontSize:13, color:'#1e3a8a', lineHeight:1.5}}>
        Active ou désactive des fonctionnalités selon ton activité. Les modules désactivés sont masqués partout dans l'application — les données existantes ne sont pas supprimées.
      </div>
      {items.map(it => (
        <div key={it.key} className="card" style={{padding:16, display:'flex', alignItems:'center', gap:14}}>
          <div style={{fontSize:28, lineHeight:1}}>{it.icon}</div>
          <div style={{flex:1}}>
            <div style={{fontSize:15, fontWeight:700}}>{it.label}</div>
            <div style={{fontSize:12, color:'var(--ink-3)', marginTop:3, lineHeight:1.4}}>{it.desc}</div>
          </div>
          <button onClick={() => toggle(it.key)}
            aria-pressed={modules[it.key]}
            style={{
              position:'relative', width:52, height:30, borderRadius:15, border:0, cursor:'pointer', flexShrink:0,
              background: modules[it.key] ? 'var(--vival-red)' : '#cbd5e1',
              transition: 'background 0.2s',
            }}>
            <span style={{
              position:'absolute', top:3, left: modules[it.key] ? 25 : 3,
              width:24, height:24, borderRadius:'50%', background:'white',
              transition:'left 0.2s',
            }} />
          </button>
        </div>
      ))}
    </div>
  );
}

function SettingsScreen() {
  const { orders, showToast, startTour } = useApp();
  const [store, setStore] = useState(getStoreInfoCache());
  const [editing, setEditing] = useState(false);
  const [savingStore, setSavingStore] = useState(false);
  const [pinModal, setPinModal] = useState(false);
  const [registerMode, setRegisterMode] = useState(null);
  const [tab, setTab] = useState('magasin'); // magasin | caisse | equipe | compte | donnees

  // Charge les vraies infos du magasin depuis Supabase au montage
  useEffect(() => {
    (async () => {
      const storeId = window.VivalData?._storeId;
      if (!storeId) return;
      const { data } = await window.sb.from('stores')
        .select('name, address, phone, siret, tva_number, casino_customer_code').eq('id', storeId).single();
      if (data) {
        const merged = {
          name: data.name || DEFAULT_STORE.name,
          address: data.address || DEFAULT_STORE.address,
          phone: data.phone || DEFAULT_STORE.phone,
          siret: data.siret || '',
          tvaNumber: data.tva_number || '',
          casinoCustomerCode: data.casino_customer_code || '',
        };
        setStore(merged);
        saveStoreInfoCache(merged);
      }
    })();
  }, []);

  const onSaveStore = async () => {
    const casinoCleaned = (store.casinoCustomerCode || '').trim().toUpperCase();
    if (casinoCleaned && !/^E\d{3,6}$/.test(casinoCleaned)) {
      showToast('Code client Casino invalide (ex : E2783)');
      return;
    }
    setSavingStore(true);
    const storeId = window.VivalData?._storeId;
    if (!storeId) { setSavingStore(false); showToast('Erreur : magasin non identifié'); return; }
    const { data: updated, error } = await window.sb.from('stores').update({
      name: (store.name || '').trim() || null,
      address: (store.address || '').trim() || null,
      phone: (store.phone || '').trim() || null,
      siret: (store.siret || '').trim() || null,
      tva_number: (store.tvaNumber || '').trim() || null,
      casino_customer_code: casinoCleaned || null,
    }).eq('id', storeId).select();
    setSavingStore(false);
    if (error) { showToast('Erreur : ' + error.message); return; }
    if (!updated || !updated.length) { showToast('Aucune ligne mise à jour (droits ?)'); return; }
    saveStoreInfoCache(store);
    setEditing(false);
    showToast('Informations magasin enregistrées');
  };

  const tabs = [
    { id: 'magasin',  label: 'Magasin',      desc: 'Informations, abonnement, charte graphique, boutique en ligne' },
    { id: 'modules',  label: 'Modules',      desc: 'Active ou désactive les fonctionnalités selon ton activité' },
    { id: 'caisse',   label: 'Caisse',       desc: 'Ouverture/clôture de caisse, historique des sessions' },
    { id: 'equipe',   label: 'Équipe',       desc: 'Utilisateurs, rôles, scanner code-barres' },
    { id: 'compte',   label: 'Compte',       desc: 'Ton compte, sécurité PIN, tour guidé' },
    { id: 'donnees',  label: 'Données',      desc: 'Impression, export, suppression (RGPD)' },
  ];
  const activeTab = tabs.find(t => t.id === tab) || tabs[0];

  return (
    <>
      <div className="page-header">
        <div className="page-header-grow">
          <h1 className="page-title">Réglages</h1>
          <p className="page-subtitle">{activeTab.desc}</p>
        </div>
      </div>

      {/* Onglets */}
      <div style={{padding:'0 32px 16px', display:'flex', gap:6, borderBottom:'1px solid var(--line-1)', marginBottom:16, flexWrap:'wrap'}}>
        {tabs.map(t => (
          <button key={t.id} onClick={() => setTab(t.id)}
            style={{
              padding:'10px 18px', border:0, background:'transparent', cursor:'pointer',
              fontSize:14, fontWeight:700,
              borderBottom: tab === t.id ? '3px solid var(--vival-red)' : '3px solid transparent',
              color: tab === t.id ? 'var(--ink-1)' : 'var(--ink-3)',
              marginBottom:-1,
            }}>
            {t.label}
          </button>
        ))}
      </div>

      <div className="settings-container" style={{padding:'0 32px 32px', display:'grid', gap:16, maxWidth:720}}>

        {tab === 'magasin' && <>
          <div className="card" style={{padding:20}}>
            <div style={{display:'flex', alignItems:'center', marginBottom:12}}>
              <h3 style={{margin:0, fontSize:16, fontWeight:800, flex:1}}>Informations magasin</h3>
              {!editing
                ? <Button variant="outline" size="sm" icon="edit" onClick={() => setEditing(true)}>Modifier</Button>
                : <div style={{display:'flex', gap:8}}>
                    <Button variant="ghost" size="sm" onClick={() => { setStore(getStoreInfoCache()); setEditing(false); }} disabled={savingStore}>Annuler</Button>
                    <Button variant="primary" size="sm" icon="check" onClick={onSaveStore} disabled={savingStore}>{savingStore ? 'Enregistrement…' : 'Enregistrer'}</Button>
                  </div>}
            </div>
            {!editing ? (
              <div style={{display:'grid', gap:12}}>
                <div><div className="kpi-label">Nom</div><div style={{fontWeight:600, marginTop:4}}>{store.name}</div></div>
                <div><div className="kpi-label">Adresse</div><div style={{fontWeight:600, marginTop:4}}>{store.address}</div></div>
                <div><div className="kpi-label">Téléphone</div><div style={{fontWeight:600, marginTop:4}}>{store.phone}</div></div>
                {store.siret && <div><div className="kpi-label">SIRET</div><div style={{fontWeight:600, marginTop:4}}>{store.siret}</div></div>}
                {store.tvaNumber && <div><div className="kpi-label">N° TVA intracom.</div><div style={{fontWeight:600, marginTop:4}}>{store.tvaNumber}</div></div>}
                <div>
                  <div className="kpi-label">Code client Casino</div>
                  <div style={{fontWeight:600, marginTop:4}}>
                    {store.casinoCustomerCode || <span className="muted" style={{fontWeight:400, fontStyle:'italic'}}>Non renseigné — requis pour la synchro factures/BL</span>}
                  </div>
                </div>
              </div>
            ) : (
              <div style={{display:'grid', gap:10}}>
                <div><div className="kpi-label" style={{marginBottom:4}}>Nom</div><Input value={store.name} onChange={e => setStore({...store, name: e.target.value})} /></div>
                <div><div className="kpi-label" style={{marginBottom:4}}>Adresse</div><Input value={store.address} onChange={e => setStore({...store, address: e.target.value})} /></div>
                <div><div className="kpi-label" style={{marginBottom:4}}>Téléphone</div><Input value={store.phone} onChange={e => setStore({...store, phone: e.target.value})} /></div>
                <div><div className="kpi-label" style={{marginBottom:4}}>SIRET</div><Input value={store.siret || ''} onChange={e => setStore({...store, siret: e.target.value})} placeholder="14 chiffres" /></div>
                <div><div className="kpi-label" style={{marginBottom:4}}>N° TVA intracom.</div><Input value={store.tvaNumber || ''} onChange={e => setStore({...store, tvaNumber: e.target.value})} placeholder="FR12345678901" /></div>
                <div>
                  <div className="kpi-label" style={{marginBottom:4}}>Code client Casino</div>
                  <Input
                    value={store.casinoCustomerCode || ''}
                    onChange={e => setStore({...store, casinoCustomerCode: e.target.value.toUpperCase()})}
                    placeholder="E2783"
                  />
                  <div style={{fontSize:11, color:'var(--ink-3)', marginTop:4}}>
                    Requis pour la synchro des factures et bordereaux Casino. Format : E suivi de 3 à 6 chiffres (ex : E2783).
                  </div>
                </div>
              </div>
            )}
          </div>
          <SubscriptionCard />
          <BrandingCard />
          <OnlineShopCard />
          <TempEmailsCard />
          <CasinoSyncCard />
        </>}

        {tab === 'modules' && <ModulesSettingsTab showToast={showToast} />}

        {tab === 'caisse' && <>
          <div className="card" style={{padding:20}}>
            <h3 style={{margin:'0 0 12px', fontSize:16, fontWeight:800}}>Ouverture / Clôture de caisse</h3>
            <div className="muted" style={{fontSize:13, marginBottom:12}}>Déclare le fond de caisse le matin, fais le Z en fin de journée pour contrôler les écarts.</div>
            <div style={{display:'grid', gap:10}}>
              <Button variant="outline" icon="box-check" block onClick={() => setRegisterMode('open')}>Ouverture de caisse</Button>
              <Button variant="outline" icon="check" block onClick={() => setRegisterMode('close')}>Clôture de caisse (Z)</Button>
            </div>
            <RegisterHistory />
          </div>
        </>}

        {tab === 'equipe' && <>
          <UsersCard />
          <ScannerStatusCard />
        </>}

        {tab === 'compte' && <>
          <div className="card" style={{padding:20}}>
            {(() => { const c = (() => { try { return JSON.parse(sessionStorage.getItem('vival_cashier') || 'null'); } catch { return null; } })(); return <>
              <h3 style={{margin:'0 0 12px', fontSize:16, fontWeight:800}}>Ton compte</h3>
              <div style={{display:'flex', alignItems:'center', gap:12, flexWrap:'wrap'}}>
                <div className="customer-avatar" style={{width:48, height:48}}>{(c?.name || 'V')[0].toUpperCase()}</div>
                <div style={{flex:1, minWidth:160}}>
                  <div style={{fontWeight:700}}>{c?.name || 'Gérant'}</div>
                  <div className="muted" style={{fontSize:13}}>{c?.role === 'admin' ? 'Administrateur' : c?.role === 'livreur' ? 'Livreur' : 'Caissier'}</div>
                </div>
                <Button variant="outline" icon="logout" onClick={() => { sessionStorage.removeItem('vival-auth'); sessionStorage.removeItem('vival_cashier'); location.reload(); }}>Déconnexion PIN</Button>
                <Button variant="ghost" size="sm" onClick={async () => {
                  if (!confirm('Délier cet appareil ? La prochaine ouverture demandera à nouveau email+mot de passe.')) return;
                  await window.sb.auth.signOut();
                  sessionStorage.clear();
                  localStorage.removeItem('vival_cache_v1');
                  location.reload();
                }}>Délier appareil</Button>
              </div>
            </>; })()}
          </div>

          <div className="card" style={{padding:20}}>
            <h3 style={{margin:'0 0 12px', fontSize:16, fontWeight:800}}>Sécurité</h3>
            <div style={{display:'flex', alignItems:'center', gap:12}}>
              <div style={{flex:1}}>
                <div style={{fontWeight:700}}>Code PIN d'accès</div>
                <div className="muted" style={{fontSize:13}}>Code à 4 chiffres demandé à l'ouverture de l'app</div>
              </div>
              <Button variant="outline" icon="lock" onClick={() => setPinModal(true)}>Modifier</Button>
            </div>
          </div>

          <div className="card" style={{padding:20}}>
            <h3 style={{margin:'0 0 12px', fontSize:16, fontWeight:800}}>Aide</h3>
            <div style={{display:'flex', alignItems:'center', gap:12, flexWrap:'wrap'}}>
              <div style={{flex:1, minWidth:180}}>
                <div style={{fontSize:13, fontWeight:700}}>Tour guidé</div>
                <div className="muted" style={{fontSize:12}}>Revoir la présentation des écrans en 2 minutes.</div>
              </div>
              <Button variant="outline" icon="chevron-right" onClick={() => { try { localStorage.removeItem('vival_tour_done_v1'); } catch {} ; startTour(); }}>
                Relancer le tour guidé
              </Button>
            </div>
          </div>
        </>}

        {tab === 'donnees' && <>
          <PrintSettingsCard />
          <DataRgpdCard />
        </>}

      </div>
      {pinModal && <PinChangeModal onClose={() => setPinModal(false)} showToast={showToast} />}
      {registerMode && <RegisterModal open={true} onClose={() => setRegisterMode(null)} mode={registerMode} orders={orders} />}
    </>
  );
}

function DataRgpdCard() {
  const { showToast } = useApp();
  const [busy, setBusy] = useState(false);

  const exportAll = async () => {
    setBusy(true);
    try {
      const sid = VivalData._storeId;
      const [store, customers, orders, orderLines, products, sessions, movements, invitations, users] = await Promise.all([
        window.sb.from('stores').select('*').eq('id', sid).single(),
        window.sb.from('customers').select('*').eq('store_id', sid),
        window.sb.from('orders').select('*').eq('store_id', sid),
        window.sb.from('order_lines').select('*').eq('store_id', sid),
        window.sb.from('products').select('*').eq('store_id', sid),
        window.sb.from('register_sessions').select('*').eq('store_id', sid),
        window.sb.from('stock_movements').select('*').eq('store_id', sid),
        window.sb.from('invitations').select('*').eq('store_id', sid),
        window.sb.from('users').select('*').eq('store_id', sid),
      ]);
      const archive = {
        exported_at: new Date().toISOString(),
        store: store.data,
        customers: customers.data || [],
        orders: orders.data || [],
        order_lines: orderLines.data || [],
        products: products.data || [],
        register_sessions: sessions.data || [],
        stock_movements: movements.data || [],
        invitations: invitations.data || [],
        users: users.data || [],
      };
      const blob = new Blob([JSON.stringify(archive, null, 2)], { type: 'application/json' });
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = `export-monvival-${store.data?.code || 'store'}-${new Date().toISOString().slice(0,10)}.json`;
      a.click();
      URL.revokeObjectURL(url);
      showToast('Export téléchargé');
    } catch (e) {
      showToast('Erreur export : ' + (e.message || e));
    }
    setBusy(false);
  };

  const deleteAccount = async () => {
    const confirm1 = prompt('Cette action supprime définitivement toutes les données de votre magasin (commandes, clients, produits, utilisateurs). Tapez SUPPRIMER pour confirmer :');
    if (confirm1 !== 'SUPPRIMER') { showToast('Suppression annulée'); return; }
    setBusy(true);
    try {
      const { error } = await window.sb.functions.invoke('delete-account', { body: { store_id: VivalData._storeId } });
      if (error) throw new Error(error.message);
      showToast('Compte supprimé. Redirection…');
      await window.sb.auth.signOut();
      setTimeout(() => location.href = '/', 1500);
    } catch (e) {
      showToast('Erreur : ' + (e.message || e));
      setBusy(false);
    }
  };

  return (
    <div className="card" style={{padding:20}}>
      <h3 style={{margin:'0 0 12px', fontSize:16, fontWeight:800}}>Données et vie privée</h3>
      <p className="muted" style={{fontSize:12, margin:'0 0 16px'}}>Conformément au RGPD, vous disposez d'un droit d'accès, de portabilité et d'effacement sur les données de votre magasin.</p>
      <div style={{display:'grid', gap:10}}>
        <Button variant="outline" icon="download" block onClick={exportAll} disabled={busy}>Exporter toutes mes données (JSON)</Button>
        <Button variant="ghost" block onClick={deleteAccount} disabled={busy} style={{color:'var(--danger, #c02424)', borderColor:'var(--danger, #c02424)', border:'1px solid', background:'transparent'}}>
          Supprimer mon compte et toutes les données
        </Button>
      </div>
      <p className="muted" style={{fontSize:11, margin:'12px 0 0'}}>
        Consultez notre <a href="/legal/confidentialite" style={{color:'var(--vival-red)'}}>politique de confidentialité</a> pour plus d'informations.
      </p>
    </div>
  );
}

function BrandingCard() {
  const { showToast } = useApp();
  const [store, setStore] = useState(null);
  const [color, setColor] = useState('#d10a11');
  const [uploading, setUploading] = useState(false);

  const load = async () => {
    const { data } = await window.sb.from('stores').select('logo_url, primary_color, name').eq('id', VivalData._storeId).single();
    setStore(data);
    if (data?.primary_color) setColor(data.primary_color);
  };
  useEffect(() => { load(); }, []);

  const uploadLogo = async (file) => {
    if (!file || !file.type.startsWith('image/')) { showToast('Image requise'); return; }
    if (file.size > 1 * 1024 * 1024) { showToast('Logo trop volumineux (max 1 Mo)'); return; }
    setUploading(true);
    const ext = file.name.split('.').pop() || 'png';
    const path = `${VivalData._storeId}/logo.${ext}`;
    const { error: upErr } = await window.sb.storage.from('branding').upload(path, file, { upsert: true, cacheControl: '3600' });
    if (upErr) { showToast('Upload: ' + upErr.message); setUploading(false); return; }
    const { data: { publicUrl } } = window.sb.storage.from('branding').getPublicUrl(path);
    // Bust le cache avec ?v=
    const busted = `${publicUrl}?v=${Date.now()}`;
    await window.sb.from('stores').update({ logo_url: busted }).eq('id', VivalData._storeId);
    setUploading(false);
    showToast('Logo mis à jour');
    load();
  };

  const removeLogo = async () => {
    await window.sb.from('stores').update({ logo_url: null }).eq('id', VivalData._storeId);
    showToast('Logo retiré');
    load();
  };

  const saveColor = async () => {
    await window.sb.from('stores').update({ primary_color: color }).eq('id', VivalData._storeId);
    document.documentElement.style.setProperty('--vival-red', color);
    showToast('Couleur enregistrée');
    load();
  };

  if (!store) return null;

  return (
    <div className="card" style={{padding:20}}>
      <h3 style={{margin:'0 0 12px', fontSize:16, fontWeight:800}}>Apparence</h3>
      <p className="muted" style={{fontSize:12, margin:'0 0 16px'}}>Personnalisez votre logo et votre couleur. Affichés sur la boutique en ligne et les bons de livraison.</p>
      <div style={{display:'grid', gap:16}}>
        <div>
          <div className="kpi-label" style={{marginBottom:8}}>Logo du magasin</div>
          <div style={{display:'flex', gap:14, alignItems:'center'}}>
            <div style={{width:96, height:96, borderRadius:12, background:'#fff', border:'1px solid var(--line-2)', display:'flex', alignItems:'center', justifyContent:'center', overflow:'hidden', padding:8}}>
              {store.logo_url
                ? <img src={store.logo_url} alt="" style={{maxWidth:'100%', maxHeight:'100%', objectFit:'contain'}} />
                : <img src="/assets/vival-logo.png" alt="" style={{maxWidth:'100%', maxHeight:'100%', objectFit:'contain', opacity:0.5}} />}
            </div>
            <div style={{flex:1}}>
              <label style={{display:'inline-block'}}>
                <input type="file" accept="image/*" style={{display:'none'}}
                  onChange={e => { const f = e.target.files?.[0]; if (f) uploadLogo(f); }} />
                <span style={{display:'inline-block', padding:'8px 14px', border:'1px solid var(--line)', borderRadius:8, cursor:'pointer', fontWeight:600, fontSize:13}}>
                  {uploading ? 'Envoi…' : (store.logo_url ? 'Changer' : 'Choisir un logo')}
                </span>
              </label>
              {store.logo_url && <Button variant="ghost" size="sm" onClick={removeLogo} style={{marginLeft:6}}>Retirer</Button>}
              <div className="muted" style={{fontSize:11, marginTop:6}}>PNG ou JPG, 1 Mo max. Fond blanc recommandé.</div>
            </div>
          </div>
        </div>
        <div>
          <div className="kpi-label" style={{marginBottom:8}}>Couleur principale</div>
          <div style={{display:'flex', gap:10, alignItems:'center'}}>
            <input type="color" value={color} onChange={e => setColor(e.target.value)}
              style={{width:48, height:48, border:'1px solid var(--line-2)', borderRadius:8, padding:0, cursor:'pointer'}} />
            <Input type="text" value={color} onChange={e => setColor(e.target.value)} style={{width:120}} />
            <div style={{flex:1}} />
            {color !== store.primary_color && <Button variant="primary" size="sm" icon="check" onClick={saveColor}>Enregistrer</Button>}
          </div>
          <div className="muted" style={{fontSize:11, marginTop:6}}>Cette couleur est utilisée pour les boutons et accents. Par défaut : rouge Vival.</div>
        </div>
      </div>
    </div>
  );
}

function CookieBanner() {
  const [dismissed, setDismissed] = useState(() => localStorage.getItem('cookies_accepted') === '1');
  if (dismissed) return null;
  return (
    <div style={{position:'fixed', bottom:16, left:16, right:16, maxWidth:640, margin:'0 auto', zIndex:200, background:'#fff', border:'1px solid var(--line-1)', borderRadius:12, padding:'16px 20px', boxShadow:'0 8px 24px rgba(0,0,0,0.12)', display:'flex', gap:16, alignItems:'center', flexWrap:'wrap'}}>
      <div style={{flex:'1 1 300px', fontSize:13, color:'var(--ink-2)', lineHeight:1.5}}>
        MonVival utilise uniquement des cookies strictement nécessaires au fonctionnement du service (authentification, préférences). Aucun cookie de tracking publicitaire. <a href="/legal/confidentialite" style={{color:'var(--vival-red)'}}>En savoir plus</a>.
      </div>
      <Button variant="primary" size="sm" onClick={() => { localStorage.setItem('cookies_accepted', '1'); setDismissed(true); }}>J'ai compris</Button>
    </div>
  );
}

function SubscriptionBanner() {
  const { setRoute } = useApp();
  const [store, setStore] = useState(null);
  useEffect(() => {
    (async () => {
      if (!VivalData._storeId) return;
      const { data } = await window.sb.from('stores').select('plan, subscription_status, trial_ends_at').eq('id', VivalData._storeId).maybeSingle();
      setStore(data);
    })();
  }, []);
  if (!store) return null;
  const trialDaysLeft = store.trial_ends_at ? Math.ceil((new Date(store.trial_ends_at) - new Date()) / 86400000) : 0;
  let banner = null;
  if (store.subscription_status === 'past_due') {
    banner = { text: 'Paiement échoué — régularisez votre moyen de paiement', bg: '#fee2e2', color: '#991b1b', border: '#dc2626' };
  } else if (store.subscription_status === 'canceled') {
    banner = { text: 'Abonnement annulé — certaines fonctionnalités sont désactivées', bg: '#fee2e2', color: '#991b1b', border: '#dc2626' };
  } else if (store.plan === 'trial' && trialDaysLeft > 0 && trialDaysLeft <= 7) {
    banner = { text: `Votre essai gratuit se termine dans ${trialDaysLeft} jour${trialDaysLeft>1?'s':''} — choisissez un plan pour continuer`, bg: '#fef3c7', color: '#92400e', border: '#d97706' };
  } else if (store.plan === 'trial' && trialDaysLeft <= 0) {
    banner = { text: 'Votre essai gratuit est terminé — choisissez un plan pour continuer', bg: '#fee2e2', color: '#991b1b', border: '#dc2626' };
  }
  if (!banner) return null;
  return (
    <div style={{position:'sticky', top:0, zIndex:100, padding:'8px 16px', background: banner.bg, borderBottom: '1px solid ' + banner.border, fontSize:13, textAlign:'center', color: banner.color, fontWeight:600, cursor:'pointer'}}
      onClick={() => setRoute('settings')}>
      {banner.text} · <span style={{textDecoration:'underline'}}>Voir les plans</span>
    </div>
  );
}

function SubscriptionCard() {
  const { showToast } = useApp();
  const [store, setStore] = useState(null);
  const [plans, setPlans] = useState([]);
  const [busy, setBusy] = useState(false);

  const load = async () => {
    const [s, p] = await Promise.all([
      window.sb.from('stores').select('*').eq('id', VivalData._storeId).single(),
      window.sb.from('pricing_plans').select('*').order('monthly_amount'),
    ]);
    setStore(s.data);
    setPlans(p.data || []);
  };
  useEffect(() => { load(); }, []);

  const subscribe = async (priceId) => {
    setBusy(true);
    const { data, error } = await window.sb.functions.invoke('stripe-checkout', {
      body: { price_id: priceId, store_id: VivalData._storeId, success_url: window.location.origin + '/?subscription=success', cancel_url: window.location.origin + '/' }
    });
    setBusy(false);
    if (error) { showToast('Erreur: ' + error.message); return; }
    if (data?.url) window.location.href = data.url;
    else showToast('Erreur: pas d\'URL de paiement');
  };
  const openPortal = async () => {
    setBusy(true);
    const { data, error } = await window.sb.functions.invoke('stripe-portal', {
      body: { store_id: VivalData._storeId, return_url: window.location.origin + '/' }
    });
    setBusy(false);
    if (error) { showToast('Erreur: ' + error.message); return; }
    if (data?.url) window.location.href = data.url;
  };

  if (!store) return null;

  const isTrial = store.plan === 'trial';
  const isActive = window.isSubscriptionActive(store);
  const trialDaysLeft = store.trial_ends_at ? Math.max(0, Math.ceil((new Date(store.trial_ends_at) - new Date()) / 86400000)) : 0;
  const currentPlan = plans.find(p => p.id === store.plan);

  return (
    <div className="card" style={{padding:20}}>
      <div style={{display:'flex', alignItems:'center', marginBottom:12}}>
        <h3 style={{margin:0, fontSize:16, fontWeight:800, flex:1}}>Abonnement</h3>
        {currentPlan && !isTrial && <Button variant="outline" size="sm" icon="settings" onClick={openPortal} disabled={busy}>Gérer / Factures</Button>}
      </div>
      {isTrial ? (
        <div style={{padding:'12px 14px', background:'linear-gradient(135deg, #fef3c7, #fde68a)', borderRadius:8, marginBottom:12}}>
          <div style={{fontWeight:700, fontSize:14}}>Essai gratuit actif</div>
          <div className="muted" style={{fontSize:12, marginTop:2}}>Il vous reste <strong>{trialDaysLeft} jour{trialDaysLeft>1?'s':''}</strong>. Choisissez un plan pour continuer après.</div>
        </div>
      ) : (
        <div style={{padding:'12px 14px', background:'var(--surface-1)', borderRadius:8, marginBottom:12, display:'flex', alignItems:'center', gap:12}}>
          <div style={{flex:1}}>
            <div style={{fontWeight:700}}>Plan {currentPlan?.name || store.plan}</div>
            <div className="muted" style={{fontSize:12}}>
              {store.subscription_status === 'active' && <>Actif · prochain paiement {store.current_period_end ? new Date(store.current_period_end).toLocaleDateString('fr-FR') : '—'}</>}
              {store.subscription_status === 'past_due' && <span style={{color:'var(--danger, #c02424)'}}>Paiement échoué — régularisez</span>}
              {store.subscription_status === 'canceled' && <span style={{color:'var(--danger, #c02424)'}}>Annulé</span>}
              {store.subscription_status === 'trialing' && 'En période d\'essai'}
            </div>
          </div>
          {currentPlan && <div style={{fontSize:18, fontWeight:800}}>{(currentPlan.monthly_amount/100).toFixed(0)}€<span className="muted" style={{fontSize:11, fontWeight:400}}>/mois</span></div>}
        </div>
      )}
      <div className="plans-grid" style={{display:'grid', gridTemplateColumns:'repeat(3, 1fr)', gap:10}}>
        {plans.map(p => {
          const isCurrent = p.id === store.plan;
          return (
            <div key={p.id} style={{padding:14, border: '2px solid ' + (isCurrent ? 'var(--vival-red)' : 'var(--line-2)'), borderRadius:10, display:'flex', flexDirection:'column', gap:6}}>
              <div style={{fontWeight:800, fontSize:14}}>{p.name}{isCurrent && <span style={{marginLeft:6, background:'var(--vival-red)', color:'#fff', fontSize:9, padding:'2px 6px', borderRadius:10, fontWeight:700}}>ACTUEL</span>}</div>
              <div style={{fontSize:22, fontWeight:800}}>{(p.monthly_amount/100).toFixed(0)}€<span className="muted" style={{fontSize:11, fontWeight:400}}>/mois HT</span></div>
              <ul style={{margin:0, padding:'0 0 0 16px', fontSize:11, color:'var(--ink-2)', lineHeight:1.5}}>
                {p.id === 'interne' && <>
                  <li>App interne complète</li>
                  <li>Livraison + fidélité</li>
                  <li>Marketplace réseau</li>
                  <li>500 produits locaux</li>
                  <li>Sans boutique en ligne</li>
                </>}
                {p.id === 'boutique' && <>
                  <li>Tout Interne +</li>
                  <li><strong>Boutique en ligne publique</strong></li>
                  <li>Commandes illimitées</li>
                  <li>Produits locaux illimités</li>
                  <li>Paiement sur place</li>
                </>}
                {p.id === 'boutique_plus' && <>
                  <li>Tout Boutique +</li>
                  <li><strong>Paiement CB en ligne</strong></li>
                  <li>Domaine custom</li>
                  <li>Multi-magasins</li>
                  <li>Support prioritaire</li>
                </>}
              </ul>
              {!isCurrent && (
                <Button variant={p.id === 'boutique' ? 'primary' : 'outline'} size="sm" block onClick={() => subscribe(p.stripe_price_id)} disabled={busy}>
                  {isTrial ? 'Choisir' : 'Changer'}
                </Button>
              )}
            </div>
          );
        })}
      </div>
    </div>
  );
}

function OnlineShopCard() {
  const { showToast } = useApp();
  const [store, setStore] = useState(null);
  const [editing, setEditing] = useState(false);
  const [form, setForm] = useState({ online_enabled: false, delivery_fee: '', min_order: '', welcome_message: '', opening_hours: '' });
  const [saving, setSaving] = useState(false);

  const load = async () => {
    const { data } = await window.sb.from('stores').select('*').eq('id', VivalData._storeId).single();
    setStore(data);
    if (data) setForm({
      online_enabled: !!data.online_enabled,
      delivery_fee: String(data.delivery_fee ?? ''),
      min_order: String(data.min_order ?? ''),
      welcome_message: data.welcome_message || '',
      opening_hours: data.opening_hours || '',
    });
  };
  useEffect(() => { load(); }, []);

  const save = async () => {
    setSaving(true);
    const { error } = await window.sb.from('stores').update({
      online_enabled: form.online_enabled,
      delivery_fee: Number(String(form.delivery_fee).replace(',', '.')) || 0,
      min_order: Number(String(form.min_order).replace(',', '.')) || 0,
      welcome_message: form.welcome_message.trim() || null,
      opening_hours: form.opening_hours.trim() || null,
    }).eq('id', VivalData._storeId);
    setSaving(false);
    if (error) { showToast('Erreur: ' + error.message); return; }
    showToast('Boutique en ligne enregistrée');
    setEditing(false);
    load();
  };

  if (!store) return null;
  const shopUrl = `${window.location.origin}/shop/${store.code}`;

  return (
    <div className="card" style={{padding:20}}>
      <div style={{display:'flex', alignItems:'center', marginBottom:12}}>
        <h3 style={{margin:0, fontSize:16, fontWeight:800, flex:1}}>Boutique en ligne</h3>
        {!editing
          ? <Button variant="outline" size="sm" icon="edit" onClick={() => setEditing(true)}>Modifier</Button>
          : <div style={{display:'flex', gap:8}}>
              <Button variant="ghost" size="sm" onClick={() => { setEditing(false); load(); }}>Annuler</Button>
              <Button variant="primary" size="sm" icon="check" onClick={save} disabled={saving}>{saving ? '…' : 'Enregistrer'}</Button>
            </div>}
      </div>
      {!window.hasFeature(store, 'online_shop') && (
        <div style={{padding:'10px 12px', background:'#fef3c7', border:'1px solid #d97706', color:'#92400e', borderRadius:8, fontSize:13, marginBottom:10}}>
          La boutique en ligne nécessite le plan <strong>Boutique</strong> (79€/mois) ou <strong>Boutique+</strong>. Votre plan actuel : <strong>{store.plan === 'interne' ? 'Interne' : store.plan}</strong>.
        </div>
      )}
      {!editing ? (
        <div style={{display:'grid', gap:10}}>
          <div style={{display:'flex', justifyContent:'space-between', alignItems:'center'}}>
            <span>Statut</span>
            <span className="status-pill" style={{background: store.online_enabled ? 'var(--success-soft)' : 'var(--surface-3)', color: store.online_enabled ? 'var(--success)' : 'var(--ink-3)'}}>
              {store.online_enabled ? '● Active' : '○ Inactive'}
            </span>
          </div>
          {store.online_enabled && (
            <>
              <div style={{padding:'10px 12px', background:'var(--surface-1)', borderRadius:8, wordBreak:'break-all'}}>
                <div className="kpi-label">Lien public à partager</div>
                <a href={shopUrl} target="_blank" style={{color:'var(--vival-red)', fontWeight:600, textDecoration:'none', fontSize:14}}>{shopUrl}</a>
                <div style={{marginTop:6}}>
                  <Button variant="outline" size="sm" icon="copy" onClick={() => { navigator.clipboard.writeText(shopUrl); showToast('Lien copié'); }}>Copier</Button>
                  <a href={`https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(shopUrl)}`} target="_blank" style={{marginLeft:8, fontSize:13, color:'var(--ink-3)', textDecoration:'underline'}}>🖨 QR code</a>
                </div>
              </div>
              <div style={{display:'grid', gridTemplateColumns:'1fr 1fr', gap:10}}>
                <div><div className="kpi-label">Frais livraison</div><div style={{fontWeight:600}}>{Number(store.delivery_fee||0).toFixed(2)} €</div></div>
                <div><div className="kpi-label">Commande min.</div><div style={{fontWeight:600}}>{Number(store.min_order||0).toFixed(2)} €</div></div>
              </div>
              {store.welcome_message && <div><div className="kpi-label">Message d'accueil</div><div style={{fontSize:13}}>{store.welcome_message}</div></div>}
              {store.opening_hours && <div><div className="kpi-label">Horaires</div><div style={{fontSize:13}}>{store.opening_hours}</div></div>}
            </>
          )}
          {!store.online_enabled && <p className="muted" style={{fontSize:13, margin:'4px 0 0'}}>Activez votre boutique pour permettre aux clients de passer commande en ligne (retrait magasin ou livraison). Paiement sur place.</p>}
        </div>
      ) : (
        <div style={{display:'grid', gap:12}}>
          <label style={{display:'flex', alignItems:'center', gap:10, padding:'10px 12px', background:'var(--surface-1)', borderRadius:8, cursor:'pointer'}}>
            <input type="checkbox" checked={form.online_enabled} onChange={e => setForm({...form, online_enabled: e.target.checked})} />
            <span style={{fontWeight:600}}>Activer la boutique en ligne</span>
          </label>
          <div style={{display:'grid', gridTemplateColumns:'1fr 1fr', gap:10}}>
            <div><div className="kpi-label" style={{marginBottom:4}}>Frais livraison (€)</div>
              <Input type="text" value={form.delivery_fee} onChange={e => setForm({...form, delivery_fee: e.target.value})} placeholder="5.00" /></div>
            <div><div className="kpi-label" style={{marginBottom:4}}>Commande min. (€)</div>
              <Input type="text" value={form.min_order} onChange={e => setForm({...form, min_order: e.target.value})} placeholder="20.00" /></div>
          </div>
          <div><div className="kpi-label" style={{marginBottom:4}}>Message d'accueil (visible sur la boutique)</div>
            <Input type="text" value={form.welcome_message} onChange={e => setForm({...form, welcome_message: e.target.value})} placeholder="Livraison à domicile dans un rayon de 5 km" /></div>
          <div><div className="kpi-label" style={{marginBottom:4}}>Horaires</div>
            <Input type="text" value={form.opening_hours} onChange={e => setForm({...form, opening_hours: e.target.value})} placeholder="Lun-Sam 8h-20h · Dim 9h-12h30" /></div>
        </div>
      )}
    </div>
  );
}

function TempEmailsCard() {
  const { showToast } = useApp();
  const [store, setStore] = useState(null);
  const [editing, setEditing] = useState(false);
  const [form, setForm] = useState({ temp_taker_email: '', temp_manager_email: '' });
  const [saving, setSaving] = useState(false);

  const load = async () => {
    const { data } = await window.sb.from('stores').select('temp_taker_email, temp_manager_email').eq('id', VivalData._storeId).single();
    setStore(data);
    if (data) setForm({
      temp_taker_email: data.temp_taker_email || '',
      temp_manager_email: data.temp_manager_email || '',
    });
  };
  useEffect(() => { load(); }, []);

  const save = async () => {
    setSaving(true);
    const storeId = window.VivalData?._storeId;
    if (!storeId) { setSaving(false); showToast('Erreur : magasin non identifié'); return; }
    const { data: updated, error } = await window.sb.from('stores').update({
      temp_taker_email: form.temp_taker_email.trim() || null,
      temp_manager_email: form.temp_manager_email.trim() || null,
    }).eq('id', storeId).select();
    setSaving(false);
    if (error) { showToast('Erreur : ' + error.message); console.error('TempEmailsCard save error:', error); return; }
    if (!updated || !updated.length) { showToast('Aucune ligne mise à jour (vérifie les droits)'); console.warn('TempEmailsCard update returned nothing for storeId=', storeId); return; }
    showToast('Emails enregistrés');
    setEditing(false);
    load();
  };

  if (!store) return null;
  return (
    <div className="card" style={{padding:20}}>
      <div style={{display:'flex', alignItems:'center', marginBottom:12}}>
        <h3 style={{margin:0, fontSize:16, fontWeight:800, flex:1}}>Rappels HACCP par email</h3>
        {!editing
          ? <Button variant="outline" size="sm" icon="edit" onClick={() => setEditing(true)}>Modifier</Button>
          : <div style={{display:'flex', gap:8}}>
              <Button variant="ghost" size="sm" onClick={() => { load(); setEditing(false); }}>Annuler</Button>
              <Button variant="primary" size="sm" icon="check" onClick={save} disabled={saving}>Enregistrer</Button>
            </div>}
      </div>
      <p className="muted" style={{fontSize:13, margin:'0 0 12px'}}>
        Envoi automatique d'un email si les relevés de température ne sont pas faits. Horaires (Paris) :
        <br/>• Matin : 12h rappel · 13h retard · 14h urgent
        <br/>• Après-midi : 19h rappel · 20h retard · 21h urgent
      </p>
      {!editing ? (
        <div style={{display:'grid', gap:12}}>
          <div><div className="kpi-label">Email du preneur (personne qui fait les relevés)</div>
            <div style={{fontWeight:600, marginTop:4}}>{store.temp_taker_email || <span className="muted">— non configuré —</span>}</div></div>
          <div><div className="kpi-label">Email du gérant (escalade)</div>
            <div style={{fontWeight:600, marginTop:4}}>{store.temp_manager_email || <span className="muted">— non configuré —</span>}</div></div>
        </div>
      ) : (
        <div style={{display:'grid', gap:10}}>
          <div><div className="kpi-label" style={{marginBottom:4}}>Email du preneur</div>
            <Input type="email" value={form.temp_taker_email} onChange={e => setForm({...form, temp_taker_email: e.target.value})} placeholder="preneur@exemple.fr" /></div>
          <div><div className="kpi-label" style={{marginBottom:4}}>Email du gérant</div>
            <Input type="email" value={form.temp_manager_email} onChange={e => setForm({...form, temp_manager_email: e.target.value})} placeholder="gerant@exemple.fr" /></div>
          <p className="muted" style={{fontSize:12, margin:0}}>Laisse vide pour désactiver les rappels email. Les alertes visuelles dans l'app restent actives.</p>
        </div>
      )}
    </div>
  );
}

function CasinoSyncCard() {
  const { showToast } = useApp();
  const [store, setStore] = useState(null);
  const [syncs, setSyncs] = useState([]);
  const [showKey, setShowKey] = useState(false);
  const [generating, setGenerating] = useState(false);

  const load = async () => {
    const sid = window.VivalData?._storeId;
    if (!sid) return;
    const { data } = await window.sb.from('stores')
      .select('sync_api_key, casino_store_code, last_casino_sync_at, last_casino_sync_count')
      .eq('id', sid).single();
    setStore(data);
    const { data: logs } = await window.sb.from('casino_sync_logs')
      .select('started_at, ended_at, products_updated, products_failed, status, casino_store_code')
      .eq('store_id', sid).order('started_at', { ascending: false }).limit(5);
    setSyncs(logs || []);
  };
  useEffect(() => { load(); }, []);

  const generate = async () => {
    if (store?.sync_api_key && !confirm('Régénérer la clé ? L’ancienne deviendra invalide — tu devras re-coller la nouvelle clé dans l’extension Chrome.')) return;
    setGenerating(true);
    const { data: keyRow } = await window.sb.rpc('generate_sync_api_key');
    const key = keyRow;
    if (!key) { setGenerating(false); showToast('Erreur génération clé'); return; }
    const { error } = await window.sb.from('stores').update({ sync_api_key: key }).eq('id', window.VivalData._storeId);
    setGenerating(false);
    if (error) { showToast('Erreur : ' + error.message); return; }
    showToast('Nouvelle clé générée');
    setShowKey(true);
    load();
  };

  const copy = async () => {
    if (!store?.sync_api_key) return;
    try { await navigator.clipboard.writeText(store.sync_api_key); showToast('Clé copiée ✓'); } catch { showToast('Copie impossible'); }
  };

  const lastSyncLabel = store?.last_casino_sync_at
    ? new Date(store.last_casino_sync_at).toLocaleString('fr-FR', { dateStyle: 'short', timeStyle: 'short' })
    : 'Jamais';

  if (!store) return null;

  return (
    <div className="card" style={{padding:20}}>
      <div style={{display:'flex', alignItems:'center', marginBottom:12}}>
        <h3 style={{margin:0, fontSize:16, fontWeight:800, flex:1}}>Synchro Casino Pro</h3>
      </div>

      <p className="muted" style={{fontSize:13, margin:'0 0 14px'}}>
        Récupère automatiquement le catalogue Casino (prix d’achat, prix conseillés, marges, images) depuis ta centrale d’achat, via l’extension Chrome <strong>MonVival Sync</strong>.
      </p>

      {/* Cle API */}
      <div style={{padding:12, background:'var(--surface-1)', borderRadius:8, marginBottom:12}}>
        <div className="kpi-label" style={{marginBottom:6}}>Ta clé de synchro MonVival</div>
        {store.sync_api_key ? (
          <div style={{display:'flex', gap:6, alignItems:'center', flexWrap:'wrap'}}>
            <code style={{
              flex:1, minWidth:200, padding:'8px 10px', background:'#fff',
              border:'1px solid var(--line-1)', borderRadius:6, fontFamily:'ui-monospace, monospace',
              fontSize:12, wordBreak:'break-all',
            }}>
              {showKey ? store.sync_api_key : store.sync_api_key.slice(0, 16) + '•'.repeat(20)}
            </code>
            <Button variant="ghost" size="sm" onClick={() => setShowKey(s => !s)}>{showKey ? 'Cacher' : 'Afficher'}</Button>
            <Button variant="outline" size="sm" icon="copy" onClick={copy}>Copier</Button>
          </div>
        ) : (
          <p style={{fontSize:13, margin:'0 0 8px', color:'var(--ink-2)'}}>Aucune clé générée pour l’instant. Clique ci-dessous pour en créer une.</p>
        )}
        <div style={{marginTop:10}}>
          <Button variant="outline" size="sm" icon="refresh" onClick={generate} disabled={generating}>
            {store.sync_api_key ? (generating ? 'Régénération…' : 'Régénérer la clé') : (generating ? 'Génération…' : 'Générer ma clé')}
          </Button>
        </div>
      </div>

      {/* Derniere synchro + historique */}
      <div style={{display:'grid', gridTemplateColumns:'1fr 1fr', gap:10, marginBottom:12}}>
        <div><div className="kpi-label">Dernière synchro</div><div style={{fontWeight:700, marginTop:4}}>{lastSyncLabel}</div></div>
        <div><div className="kpi-label">Produits importés</div><div style={{fontWeight:700, marginTop:4}}>{store.last_casino_sync_count || 0}</div></div>
      </div>

      {syncs.length > 0 && (
        <details style={{fontSize:12, color:'var(--ink-3)'}}>
          <summary style={{cursor:'pointer', fontWeight:600}}>Historique des synchros ({syncs.length})</summary>
          <div style={{marginTop:8, display:'grid', gap:4}}>
            {syncs.map((s, i) => (
              <div key={i} style={{display:'flex', gap:8, padding:'4px 0', borderBottom:'1px solid var(--line-1)'}}>
                <span>{new Date(s.started_at).toLocaleString('fr-FR', { dateStyle:'short', timeStyle:'short' })}</span>
                <span style={{flex:1}}>·</span>
                <span style={{color: s.status === 'success' ? '#0a7' : s.status === 'error' ? '#c00' : '#c80'}}>{s.status}</span>
                <span>·</span>
                <span>{s.products_updated} prod.</span>
              </div>
            ))}
          </div>
        </details>
      )}

      <div style={{marginTop:14, padding:10, background:'#eff6ff', border:'1px solid #bfdbfe', borderRadius:8, fontSize:12, color:'#1e40af'}}>
        <strong>Comment ça marche :</strong>
        <ol style={{margin:'6px 0 0 20px', padding:0, lineHeight:1.6}}>
          <li>Génère ta clé ci-dessus et copie-la</li>
          <li>Installe l’extension Chrome <em>MonVival Sync</em> (lien envoyé par PC Relais)</li>
          <li>Colle ta clé dans l’extension (une seule fois)</li>
          <li>Connecte-toi sur <a href="https://casino-pro.groupe-casino.fr" target="_blank" rel="noopener">casino-pro.groupe-casino.fr</a></li>
          <li>Clique le bouton <em>Synchroniser MonVival</em> qui apparaît en haut à droite</li>
        </ol>
      </div>

      <CasinoErpSyncSection />
    </div>
  );
}

function CasinoErpSyncSection() {
  const { showToast } = useApp();
  const [store, setStore] = useState(null);
  const [recent, setRecent] = useState({ deliveries: 0, invoices: 0 });
  const [preview, setPreview] = useState(null);
  const [applying, setApplying] = useState(false);

  const load = async () => {
    const sid = window.VivalData?._storeId;
    if (!sid) return;
    const { data } = await window.sb.from('stores')
      .select('casino_customer_code, last_erp_sync_at, last_erp_deliveries_count, last_erp_invoices_count')
      .eq('id', sid).single();
    setStore(data);
    const [{ count: delC }, { count: invC }] = await Promise.all([
      window.sb.from('casino_deliveries').select('*', { count: 'exact', head: true }).eq('store_id', sid),
      window.sb.from('casino_invoices').select('*', { count: 'exact', head: true }).eq('store_id', sid),
    ]);
    setRecent({ deliveries: delC || 0, invoices: invC || 0 });
    // Preview du stock a appliquer
    const { data: prev } = await window.sb.rpc('casino_preview_apply_stock', { p_store_id: sid });
    if (prev && prev.length) setPreview(prev[0]);
  };
  useEffect(() => { load(); }, []);

  const applyToStock = async () => {
    const sid = window.VivalData?._storeId;
    if (!sid || !preview) return;
    if (!confirm(`Appliquer ${preview.nb_deliveries_to_apply} livraisons au stock ?\n\n${preview.nb_distinct_products} produits seront mis à jour (+${Math.round(preview.total_units_to_add)} unités).\n\n${preview.nb_lines_without_ean} lignes sans EAN seront ignorées.`)) return;
    setApplying(true);
    const { data, error } = await window.sb.rpc('casino_apply_deliveries_to_stock', { p_store_id: sid });
    setApplying(false);
    if (error) { showToast('Erreur : ' + error.message); return; }
    const r = data?.[0];
    showToast(`✅ ${r?.products_updated || 0} produits mis à jour (+${Math.round(Number(r?.units_added || 0))} unités)`);
    load();
  };

  const rematch = async () => {
    const sid = window.VivalData?._storeId;
    if (!sid) return;
    if (!confirm('Re-résoudre les EAN des livraisons et factures existantes via le catalogue Casino à jour ?')) return;
    setApplying(true);
    const [del, inv] = await Promise.all([
      window.sb.rpc('casino_rematch_delivery_lines', { p_store_id: sid }),
      window.sb.rpc('casino_rematch_invoice_lines', { p_store_id: sid }),
    ]);
    setApplying(false);
    const d = del.data?.[0]; const i = inv.data?.[0];
    showToast(`Re-match : ${d?.updated || 0} lignes BL + ${i?.updated || 0} lignes facture résolues (sur ${(d?.total || 0) + (i?.total || 0)} au total)`);
    load();
  };

  if (!store) return null;
  const hasCode = !!store.casino_customer_code;
  const lastLabel = store.last_erp_sync_at
    ? new Date(store.last_erp_sync_at).toLocaleString('fr-FR', { dateStyle:'short', timeStyle:'short' })
    : 'Jamais';

  return (
    <div style={{marginTop:20, paddingTop:16, borderTop:'1px solid var(--line-1)'}}>
      <h4 style={{margin:'0 0 10px', fontSize:14, fontWeight:800}}>Synchro factures & livraisons (ERP)</h4>
      <p className="muted" style={{fontSize:13, margin:'0 0 12px'}}>
        Récupère automatiquement tes <strong>bordereaux de livraison</strong> et <strong>factures Casino</strong> depuis la GED (ged.groupe-casino.fr) pour calculer la marge rayon exacte et détecter les ruptures.
      </p>

      {!hasCode && (
        <div style={{padding:'10px 12px', background:'#fef3c7', border:'1px solid #d97706', color:'#92400e', borderRadius:8, fontSize:13, marginBottom:12}}>
          ⚠ Renseigne d'abord ton <strong>code client Casino</strong> dans Informations magasin ci-dessus.
        </div>
      )}

      <div style={{display:'grid', gridTemplateColumns:'1fr 1fr 1fr', gap:10, marginBottom:12}}>
        <div><div className="kpi-label">Dernière synchro ERP</div><div style={{fontWeight:700, marginTop:4, fontSize:13}}>{lastLabel}</div></div>
        <div><div className="kpi-label">Livraisons</div><div style={{fontWeight:700, marginTop:4}}>{recent.deliveries}</div></div>
        <div><div className="kpi-label">Factures</div><div style={{fontWeight:700, marginTop:4}}>{recent.invoices}</div></div>
      </div>

      <div style={{display:'flex', gap:8, flexWrap:'wrap', marginBottom:12}}>
        <Button
          variant="primary"
          icon="refresh"
          disabled={!hasCode}
          onClick={() => {
            window.open('https://ged.groupe-casino.fr/otcs/cs.exe?func=llworkspace&mv_autosync=1', '_blank');
            showToast('Ouvre un nouvel onglet GED. Si besoin, connecte-toi puis laisse tourner.');
          }}>
          Lancer la synchro ERP
        </Button>
        <Button
          variant="outline"
          icon="sync"
          disabled={!hasCode}
          onClick={() => {
            window.open('https://casino-pro.groupe-casino.fr/?mv_autosync=1', '_blank');
            setTimeout(() => {
              window.open('https://ged.groupe-casino.fr/otcs/cs.exe?func=llworkspace&mv_autosync=1', '_blank');
            }, 1500);
            showToast('Synchro complète : 2 onglets ouverts (Casino Pro + GED).');
          }}>
          Synchro complète (catalogue + ERP)
        </Button>
      </div>

      {/* Application au stock */}
      {preview && preview.nb_deliveries_to_apply > 0 && (
        <div style={{padding:14, background:'#f0fdf4', border:'1px solid #86efac', borderRadius:10, marginTop:12}}>
          <div style={{fontWeight:700, fontSize:13, marginBottom:8, color:'#166534'}}>📦 Livraisons à appliquer au stock</div>
          <div style={{display:'grid', gridTemplateColumns:'repeat(auto-fit, minmax(140px, 1fr))', gap:8, marginBottom:10, fontSize:12}}>
            <div><div style={{color:'var(--ink-3)'}}>Livraisons</div><div style={{fontWeight:700, fontSize:16}}>{preview.nb_deliveries_to_apply}</div></div>
            <div><div style={{color:'var(--ink-3)'}}>Produits impactés</div><div style={{fontWeight:700, fontSize:16}}>{preview.nb_distinct_products}</div></div>
            <div><div style={{color:'var(--ink-3)'}}>Unités à ajouter</div><div style={{fontWeight:700, fontSize:16, color:'#059669'}}>+{Math.round(Number(preview.total_units_to_add))}</div></div>
            <div><div style={{color:'var(--ink-3)'}}>Sans EAN (ignorées)</div><div style={{fontWeight:700, fontSize:16, color: preview.nb_lines_without_ean > 0 ? '#d97706' : 'inherit'}}>{preview.nb_lines_without_ean}</div></div>
          </div>
          <div style={{display:'flex', gap:8, flexWrap:'wrap'}}>
            <Button variant="primary" icon="box-check" disabled={applying || preview.nb_lines_with_ean === 0} onClick={applyToStock}>
              {applying ? 'Application…' : 'Appliquer au stock'}
            </Button>
            {preview.nb_lines_without_ean > 0 && (
              <Button variant="outline" icon="refresh" disabled={applying} onClick={rematch}>
                Re-résoudre les EAN manquants
              </Button>
            )}
          </div>
          {preview.nb_lines_without_ean > 0 && (
            <div style={{fontSize:11, color:'var(--ink-3)', marginTop:8, lineHeight:1.5}}>
              💡 {preview.nb_lines_without_ean} ligne(s) sans correspondance EAN. Lance d'abord la <strong>synchro catalogue</strong> (Casino Pro) pour récupérer les codes produits, puis clique <strong>« Re-résoudre les EAN »</strong>.
            </div>
          )}
        </div>
      )}

      <div style={{marginTop:12, padding:10, background:'#eff6ff', border:'1px solid #bfdbfe', borderRadius:8, fontSize:12, color:'#1e40af'}}>
        <strong>À noter :</strong> la 1<sup>re</sup> fois, Chrome te redemandera ton mot de passe GED. Enregistre-le dans Chrome pour que les synchros suivantes soient automatiques.
      </div>
    </div>
  );
}

function UsersCard() {
  const [users, setUsers] = useState([]);
  const [editing, setEditing] = useState(null);
  const [inviting, setInviting] = useState(false);
  const [invitations, setInvitations] = useState([]);
  const { showToast } = useApp();
  const load = async () => {
    const [u, i] = await Promise.all([
      window.sb.from('users').select('*').eq('store_id', VivalData._storeId).order('created_at'),
      window.sb.from('invitations').select('*').is('accepted_at', null).order('created_at', { ascending: false }),
    ]);
    setUsers(u.data || []);
    setInvitations((i.data || []).filter(x => new Date(x.expires_at) > new Date()));
  };
  useEffect(() => { load(); }, []);
  const del = async (u) => {
    if (!confirm(`Supprimer ${u.name} ?`)) return;
    await window.sb.from('users').update({ active: false }).eq('id', u.id);
    load();
  };
  const revokeInvite = async (id) => {
    await window.sb.from('invitations').delete().eq('id', id);
    load();
  };
  return (
    <div className="card" style={{padding:20}}>
      <div style={{display:'flex', alignItems:'center', marginBottom:12, gap:8, flexWrap:'wrap'}}>
        <h3 style={{margin:0, fontSize:16, fontWeight:800, flex:1}}>Équipe</h3>
        <Button variant="outline" size="sm" icon="envelope" onClick={() => setInviting(true)}>Inviter par email</Button>
        <Button variant="ghost" size="sm" icon="plus" onClick={() => setEditing({ name:'', role:'caissier', pin:'', active:true })}>PIN direct</Button>
      </div>
      <p className="muted" style={{fontSize:12, margin:'0 0 12px'}}>Invitez par email (recommandé) pour créer un compte, ou ajoutez un PIN direct pour l'équipe sur place.</p>
      {invitations.length > 0 && (
        <div style={{marginBottom:12}}>
          <div className="kpi-label" style={{marginBottom:6}}>Invitations en attente</div>
          <div style={{display:'grid', gap:6}}>
            {invitations.map(inv => (
              <div key={inv.id} style={{display:'flex', alignItems:'center', gap:10, padding:'8px 10px', background:'#fef3c7', borderRadius:6, fontSize:13}}>
                <Icon name="envelope" style={{width:14, height:14, color:'#92400e'}} />
                <div style={{flex:1, minWidth:0}}>
                  <div style={{fontWeight:600}}>{inv.email}</div>
                  <div style={{fontSize:11, color:'#92400e'}}>{inv.role} · Expire {new Date(inv.expires_at).toLocaleDateString('fr-FR')}</div>
                </div>
                <Button variant="ghost" size="sm" icon="close" onClick={() => revokeInvite(inv.id)} />
              </div>
            ))}
          </div>
        </div>
      )}
      {users.filter(u => u.active).length === 0 && <div className="muted" style={{fontSize:13}}>Aucun utilisateur — le PIN par défaut ({localStorage.getItem('vival_pin') || '1234'}) est utilisé.</div>}
      <div style={{display:'grid', gap:6}}>
        {users.filter(u => u.active).map(u => (
          <div key={u.id} style={{display:'flex', alignItems:'center', gap:10, padding:'8px 10px', background:'var(--surface-1)', borderRadius:6}}>
            <div className="customer-avatar" style={{width:32, height:32, fontSize:13}}>{u.name[0]?.toUpperCase()}</div>
            <div style={{flex:1}}>
              <div style={{fontWeight:600, fontSize:14}}>{u.name}</div>
              <div className="muted" style={{fontSize:11}}>{u.role} · PIN ••••</div>
            </div>
            <Button variant="ghost" size="sm" icon="edit" onClick={() => setEditing(u)} />
            <Button variant="ghost" size="sm" icon="trash" onClick={() => del(u)} />
          </div>
        ))}
      </div>
      {editing && <UserEditModal user={editing} onClose={() => setEditing(null)} onSaved={() => { setEditing(null); load(); showToast('Utilisateur enregistré'); }} />}
      {inviting && <InviteUserModal onClose={() => setInviting(false)} onSent={() => { setInviting(false); load(); showToast('Invitation envoyée'); }} />}
    </div>
  );
}

function InviteUserModal({ onClose, onSent }) {
  const { showToast } = useApp();
  const [email, setEmail] = useState('');
  const [role, setRole] = useState('caissier');
  const [pin, setPin] = useState('');
  const [sending, setSending] = useState(false);
  const [err, setErr] = useState('');

  const send = async () => {
    setErr('');
    if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { setErr('Email invalide'); return; }
    if (pin && !/^\d{4}$/.test(pin)) { setErr('PIN doit faire 4 chiffres'); return; }
    setSending(true);
    const { data, error } = await window.sb.functions.invoke('invite-user', {
      body: { email: email.trim(), role, pin: pin || null, redirect_url: window.location.origin }
    });
    setSending(false);
    if (error || data?.error) { setErr(error?.message || data?.error); return; }
    onSent();
  };

  return (
    <Modal open={true} onClose={onClose} title="Inviter un collaborateur"
      footer={<><Button variant="ghost" onClick={onClose}>Annuler</Button><Button variant="primary" icon="envelope" onClick={send} disabled={sending || !email}>{sending ? 'Envoi…' : 'Envoyer l\'invitation'}</Button></>}>
      <p className="muted" style={{marginTop:0, fontSize:13}}>Un email sera envoyé avec un lien pour rejoindre votre magasin.</p>
      <div style={{display:'grid', gap:12}}>
        <div><div className="kpi-label" style={{marginBottom:4}}>Email</div>
          <Input type="email" value={email} onChange={e => setEmail(e.target.value)} placeholder="collaborateur@exemple.fr" autoFocus /></div>
        <div><div className="kpi-label" style={{marginBottom:4}}>Rôle</div>
          <select value={role} onChange={e => setRole(e.target.value)} style={{width:'100%', padding:'10px 12px', border:'1px solid var(--line)', borderRadius:8, fontSize:14, background:'#fff'}}>
            <option value="admin">Administrateur</option>
            <option value="caissier">Caissier</option>
            <option value="livreur">Livreur</option>
          </select></div>
        <div><div className="kpi-label" style={{marginBottom:4}}>PIN rapide (optionnel, 4 chiffres)</div>
          <input type="text" inputMode="numeric" maxLength={4} value={pin} onChange={e => setPin(e.target.value.replace(/\D/g,''))}
            placeholder="Ex: 5678"
            style={{width:'100%', padding:'10px 12px', border:'1px solid var(--line)', borderRadius:8, fontSize:16, letterSpacing:'0.3em', textAlign:'center'}} />
          <p className="muted" style={{fontSize:11, margin:'4px 0 0'}}>Si défini, cette personne pourra aussi se connecter via le sélecteur PIN sur l'écran d'accueil.</p></div>
        {err && <div style={{color:'var(--danger, #c02424)', fontSize:13}}>{err}</div>}
      </div>
    </Modal>
  );
}

function UserEditModal({ user, onClose, onSaved }) {
  const [name, setName] = useState(user.name || '');
  const [role, setRole] = useState(user.role || 'caissier');
  const [pin, setPin] = useState(user.pin || '');
  const [err, setErr] = useState('');
  const save = async () => {
    if (!name.trim()) { setErr('Nom requis'); return; }
    if (!/^\d{4}$/.test(pin)) { setErr('PIN à 4 chiffres requis'); return; }
    const payload = { name: name.trim(), role, pin, store_id: VivalData._storeId };
    const { error } = user.id
      ? await window.sb.from('users').update(payload).eq('id', user.id)
      : await window.sb.from('users').insert(payload);
    if (error) { setErr(error.message); return; }
    onSaved();
  };
  return (
    <Modal open={true} onClose={onClose} title={user.id ? 'Modifier utilisateur' : 'Nouvel utilisateur'}
      footer={<><Button variant="ghost" onClick={onClose}>Annuler</Button><Button variant="primary" icon="check" onClick={save}>Enregistrer</Button></>}>
      <div style={{display:'grid', gap:12}}>
        <div><div className="kpi-label" style={{marginBottom:4}}>Nom</div><Input value={name} onChange={e => setName(e.target.value)} autoFocus /></div>
        <div><div className="kpi-label" style={{marginBottom:4}}>Rôle</div>
          <select value={role} onChange={e => setRole(e.target.value)} style={{width:'100%', padding:'10px 12px', border:'1px solid var(--line)', borderRadius:8, fontSize:14, background:'#fff'}}>
            <option value="admin">Administrateur</option>
            <option value="caissier">Caissier</option>
            <option value="livreur">Livreur</option>
          </select>
        </div>
        <div><div className="kpi-label" style={{marginBottom:4}}>Code PIN (4 chiffres)</div>
          <input type="text" inputMode="numeric" maxLength={4} value={pin} onChange={e => setPin(e.target.value.replace(/\D/g,''))} style={{width:'100%', padding:'10px 12px', border:'1px solid var(--line)', borderRadius:8, fontSize:18, letterSpacing:'0.3em', textAlign:'center'}} />
        </div>
        {err && <div style={{color:'var(--danger)', fontSize:13}}>{err}</div>}
      </div>
    </Modal>
  );
}

function RegisterHistory() {
  const [sessions, setSessions] = useState([]);
  const [loading, setLoading] = useState(true);
  useEffect(() => {
    (async () => {
      const { data } = await window.sb.from('register_sessions').select('*').eq('store_id', VivalData._storeId).order('opened_at', {ascending:false}).limit(10);
      setSessions(data || []);
      setLoading(false);
    })();
  }, []);
  if (loading) return null;
  if (!sessions.length) return <div className="muted" style={{fontSize:13, marginTop:12}}>Aucune session caisse enregistrée.</div>;
  return (
    <div style={{marginTop:16, borderTop:'1px solid var(--line-2)', paddingTop:12}}>
      <div className="kpi-label" style={{marginBottom:8}}>Dernières sessions</div>
      <div style={{display:'grid', gap:6}}>
        {sessions.map(s => {
          const closed = !!s.closed_at;
          const delta = Number(s.delta || 0);
          return (
            <div key={s.id} style={{display:'flex', justifyContent:'space-between', alignItems:'center', padding:'8px 10px', background:'var(--surface-1)', borderRadius:6, fontSize:13}}>
              <div>
                <div style={{fontWeight:600}}>{new Date(s.opened_at).toLocaleDateString('fr-FR', {day:'2-digit', month:'short', hour:'2-digit', minute:'2-digit'})}{s.cashier_name ? ` · ${s.cashier_name}` : ''}</div>
                <div className="muted" style={{fontSize:11}}>Fond {formatPrice(s.opening_fund).full}{closed ? ` · ${s.tickets_count||0} tickets · encaissé ${formatPrice((Number(s.cash_sales)||0)+(Number(s.card_sales)||0)).full}` : ' · en cours'}</div>
              </div>
              {closed
                ? <span style={{fontWeight:700, color: delta === 0 ? 'var(--success)' : (delta < 0 ? 'var(--danger, #c02424)' : 'var(--warning, #ea580c)')}}>{delta > 0 ? '+' : ''}{formatPrice(delta).full}</span>
                : <span className="status-pill status-en-cours">Ouverte</span>}
            </div>
          );
        })}
      </div>
    </div>
  );
}

function PinChangeModal({ onClose, showToast }) {
  const [current, setCurrent] = useState('');
  const [next, setNext] = useState('');
  const [confirm, setConfirm] = useState('');
  const [err, setErr] = useState('');
  const save = () => {
    const stored = localStorage.getItem('vival_pin') || '1234';
    if (current !== stored) { setErr('Code actuel incorrect'); return; }
    if (!/^\d{4}$/.test(next)) { setErr('Le nouveau code doit faire 4 chiffres'); return; }
    if (next !== confirm) { setErr('Les codes ne correspondent pas'); return; }
    localStorage.setItem('vival_pin', next);
    showToast('Code PIN modifié');
    onClose();
  };
  const input = (val, set, ph) => <input type="password" inputMode="numeric" maxLength={4} value={val} onChange={e => { set(e.target.value.replace(/\D/g,'')); setErr(''); }} placeholder={ph} style={{width:'100%', padding:'10px 12px', border:'1px solid var(--line)', borderRadius:8, fontSize:18, letterSpacing:'0.3em', textAlign:'center'}} />;
  return (
    <Modal open={true} onClose={onClose} title="Modifier le code PIN"
      footer={<><Button variant="ghost" onClick={onClose}>Annuler</Button><Button variant="primary" icon="check" onClick={save}>Enregistrer</Button></>}>
      <div style={{display:'grid', gap:12}}>
        <div><div className="kpi-label" style={{marginBottom:4}}>Code actuel</div>{input(current, setCurrent, '••••')}</div>
        <div><div className="kpi-label" style={{marginBottom:4}}>Nouveau code</div>{input(next, setNext, '••••')}</div>
        <div><div className="kpi-label" style={{marginBottom:4}}>Confirmer</div>{input(confirm, setConfirm, '••••')}</div>
        {err && <div style={{color:'var(--danger)', fontSize:13}}>{err}</div>}
      </div>
    </Modal>
  );
}

// ===== Product detail modal =====
async function compressImage(file, maxSize = 800, quality = 0.82) {
  return new Promise((resolve, reject) => {
    const url = URL.createObjectURL(file);
    const img = new Image();
    img.onload = () => {
      const ratio = Math.min(maxSize / img.width, maxSize / img.height, 1);
      const w = Math.round(img.width * ratio);
      const h = Math.round(img.height * ratio);
      const canvas = document.createElement('canvas');
      canvas.width = w; canvas.height = h;
      const ctx = canvas.getContext('2d');
      ctx.drawImage(img, 0, 0, w, h);
      URL.revokeObjectURL(url);
      resolve(canvas.toDataURL('image/jpeg', quality));
    };
    img.onerror = (e) => { URL.revokeObjectURL(url); reject(e); };
    img.src = url;
  });
}

// ===== Admin Catalogue =====
function LastSyncLabel({ align = 'right' }) {
  const [lastSync, setLastSync] = useState(() => window.VivalData?._lastSync || null);

  const fetchLast = async () => {
    try {
      const { data } = await window.sb
        .from('scrape_runs')
        .select('finished_at')
        .eq('status', 'success')
        .order('finished_at', { ascending: false })
        .limit(1);
      const t = data?.[0]?.finished_at || null;
      if (t) {
        window.VivalData._lastSync = t;
        setLastSync(t);
      }
    } catch (_) {}
  };

  useEffect(() => {
    fetchLast();
    const iv = setInterval(fetchLast, 60000);
    const onRefresh = () => fetchLast();
    window.addEventListener('vival:last-sync-refresh', onRefresh);
    return () => { clearInterval(iv); window.removeEventListener('vival:last-sync-refresh', onRefresh); };
  }, []);

  if (!lastSync) return null;

  const d = new Date(lastSync);
  const n = new Date();
  const sameDay = d.toDateString() === n.toDateString();
  const hhmm = d.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' });
  const label = sameDay
    ? `Produits mis à jour à ${hhmm}`
    : `Produits mis à jour le ${d.toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit' })} à ${hhmm}`;

  return (
    <div className="muted" style={{ fontSize: 11, padding: '6px 16px', textAlign: align, color: 'var(--ink-3)' }}>
      {label}
    </div>
  );
}

function AdminCatalogScreen() {
  const app = useApp();
  const { setRoute } = app;
  const [query, setQuery] = useState('');
  // Source d'origine pour afficher un bouton retour si on vient d'un autre ecran
  const [comeFrom, setComeFrom] = useState(null); // 'topflop' | 'alerts' | null
  const [rayonFilter, setRayonFilter] = useState('all');
  const [subFilter, setSubFilter] = useState('all');
  const [stockFilter, setStockFilter] = useState('all'); // all | in | low | out
  const [activeFilter, setActiveFilter] = useState('active'); // active | inactive | all
  const [sort, setSort] = useState('name');
  const [page, setPage] = useState(0);
  const [editing, setEditing] = useState(null);
  const [adding, setAdding] = useState(false);
  const [refreshTick, setRefreshTick] = useState(0);
  const PAGE_SIZE = 50;

  // Charge le catalogue complet a la demande (le boot ne charge que les
  // produits actifs ~2500 ; ici on a besoin de tous les ~13k pour navigation/edition)
  useEffect(() => {
    if (window.ensureFullCatalog) {
      window.ensureFullCatalog().then(() => setRefreshTick(t => t + 1));
    }
    // Recupere une recherche pre-remplie (depuis Top/Flop, alertes, etc.)
    const pending = sessionStorage.getItem('vival_admin_search');
    if (pending) {
      setQuery(pending);
      sessionStorage.removeItem('vival_admin_search');
    }
    const from = sessionStorage.getItem('vival_admin_come_from');
    if (from) {
      setComeFrom(from);
      sessionStorage.removeItem('vival_admin_come_from');
    }
  }, []);

  const currentCashier = (() => { try { return JSON.parse(sessionStorage.getItem('vival_cashier') || 'null'); } catch { return null; } })();
  const isAdmin = currentCashier?.role === 'admin';
  const [syncState, setSyncState] = useState({ running: false, runId: null, status: null, error: null, productsUpserted: null, progressPercent: null, progressMessage: null });

  // Filtre "nouveaux produits de cette synchro" (ou null = pas de filtre)
  const [newRunFilter, setNewRunFilter] = useState(null); // { runId, startedAt, productsUpserted } | null
  const [newProductIds, setNewProductIds] = useState(null); // Set<string> | null
  const [historyOpen, setHistoryOpen] = useState(false);

  // Quand on active un filtre "nouveaux de la synchro X", on fetch les IDs concernés
  useEffect(() => {
    if (!newRunFilter) { setNewProductIds(null); return; }
    (async () => {
      const { data } = await window.sb.from('products').select('id').eq('added_by_run', newRunFilter.runId).limit(10000);
      setNewProductIds(new Set((data || []).map(r => r.id)));
    })();
  }, [newRunFilter?.runId]);

  const triggerScrape = async () => {
    if (syncState.running) return;
    setSyncState({ running: true, runId: null, status: 'starting', error: null, productsUpserted: null });
    try {
      // Récupère explicitement la session pour forcer l'envoi du JWT
      const { data: sessionData } = await window.sb.auth.getSession();
      const token = sessionData?.session?.access_token;
      if (!token) {
        throw new Error("Session Supabase expirée. Reconnectez-vous (Réglages → Déconnexion → Relogin).");
      }
      const res = await fetch(`${window.SUPABASE_URL}/functions/v1/trigger-scrape`, {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${token}`,
          'apikey': window.SUPABASE_KEY,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({}),
      });
      const payload = await res.json().catch(() => ({}));
      if (!res.ok) {
        const msg = payload?.error || `HTTP ${res.status}`;
        setSyncState({ running: false, runId: null, status: 'failed', error: msg, productsUpserted: null });
        app.showToast && app.showToast('Synchro KO : ' + msg);
        return;
      }
      const data = payload;
      const error = null;
      if (error) {
        setSyncState({ running: false, runId: null, status: 'failed', error: 'Échec', productsUpserted: null });
        return;
      }
      const runId = data?.run_id;
      setSyncState({ running: true, runId, status: 'running', error: null, productsUpserted: null, progressPercent: 0, progressMessage: 'Démarrage…' });
      app.showToast && app.showToast('Synchronisation lancée…');
      // Poll toutes les 3s
      const pollStart = Date.now();
      const poll = async () => {
        if (Date.now() - pollStart > 15 * 60 * 1000) {
          setSyncState(s => ({ ...s, running: false, status: 'failed', error: 'Timeout (>15 min)' }));
          return;
        }
        const { data: rows } = await window.sb.from('scrape_runs').select('status,error,products_upserted,finished_at,progress_percent,progress_message').eq('id', runId).limit(1);
        const r = rows?.[0];
        if (!r) return setTimeout(poll, 3000);
        if (r.status === 'running') {
          setSyncState(s => ({ ...s, progressPercent: r.progress_percent ?? s.progressPercent, progressMessage: r.progress_message ?? s.progressMessage }));
          return setTimeout(poll, 3000);
        }
        if (r.status === 'success') {
          setSyncState({ running: false, runId, status: 'success', error: null, productsUpserted: r.products_upserted, progressPercent: 100, progressMessage: r.progress_message });
          app.showToast && app.showToast(`Synchro OK : ${r.products_upserted ?? '?'} nouveaux produits · Rechargement du catalogue…`);
          window.VivalData._lastSync = r.finished_at;
          window.dispatchEvent(new CustomEvent('vival:last-sync-refresh'));
          // Invalide le cache local et recharge depuis Supabase
          try { localStorage.removeItem('vival_cache_v1'); } catch {}
          if (typeof window.hydrateFromSupabase === 'function') {
            try {
              await window.hydrateFromSupabase();
              app.showToast && app.showToast(`Catalogue rechargé : ${window.VivalData.PRODUCTS.length} produits`);
              // Force un re-render de l'écran (réutilise le hook lastScanAt)
              app.notifyScan && app.notifyScan();
            } catch (e) {
              app.showToast && app.showToast('Erreur rechargement : ' + e.message);
            }
          }
        } else {
          setSyncState({ running: false, runId, status: 'failed', error: r.error || 'Échec inconnu', productsUpserted: null, progressPercent: r.progress_percent, progressMessage: r.progress_message });
          app.showToast && app.showToast('Synchro KO : ' + (r.error || 'Échec inconnu'));
        }
      };
      setTimeout(poll, 3000);
    } catch (e) {
      setSyncState({ running: false, runId: null, status: 'failed', error: e.message, productsUpserted: null });
      app.showToast && app.showToast('Synchro KO : ' + e.message);
    }
  };

  const filtered = useMemo(() => {
    let list = PRODUCTS;
    if (rayonFilter !== 'all') list = list.filter(p => p.rayon === rayonFilter);
    if (subFilter !== 'all' && rayonFilter !== 'all') list = list.filter(p => (p.sub || 'Tout') === subFilter);
    if (stockFilter === 'in') list = list.filter(p => p.stock > 3);
    else if (stockFilter === 'low') list = list.filter(p => p.stock > 0 && p.stock <= 3);
    else if (stockFilter === 'out') list = list.filter(p => p.stock === 0);
    if (activeFilter === 'active') list = list.filter(p => p.isActive !== false);
    else if (activeFilter === 'inactive') list = list.filter(p => p.isActive === false);
    const q = query.trim().toLowerCase();
    if (q) {
      list = list.filter(p =>
        (p.name || '').toLowerCase().includes(q) ||
        (p.brand || '').toLowerCase().includes(q) ||
        (p.barcode || '').includes(q));
    }
    if (newProductIds) list = list.filter(p => newProductIds.has(p.id));
    list = [...list];
    list.sort((a, b) => {
      if (sort === 'name') return (a.name || '').localeCompare(b.name || '');
      if (sort === 'price') return a.price - b.price;
      if (sort === 'priceDesc') return b.price - a.price;
      if (sort === 'stock') return a.stock - b.stock;
      if (sort === 'rayon') return (a.rayon || '').localeCompare(b.rayon || '') || (a.name || '').localeCompare(b.name || '');
      return 0;
    });
    return list;
  }, [query, rayonFilter, subFilter, stockFilter, activeFilter, sort, app.lastScanAt, editing, adding, newProductIds]);

  const pageCount = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE));
  const pageList = filtered.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE);
  useEffect(() => { if (page >= pageCount) setPage(0); }, [pageCount]);
  useEffect(() => { setPage(0); }, [query, rayonFilter, subFilter, stockFilter, sort]);
  useEffect(() => { setSubFilter('all'); }, [rayonFilter]);

  const exportCsv = () => {
    const rows = [
      ['id','ean','nom','marque','format','rayon','prix_ttc','tva','stock','isAlcohol','byWeight','pinned'],
      ...filtered.map(p => [p.id, p.barcode||'', p.name||'', p.brand||'', p.format||'', p.rayon||'', p.price, p.tvaRate, p.stock, p.isAlcohol?1:0, p.byWeight?1:0, p.pinned?1:0]),
    ];
    const csv = rows.map(r => r.map(c => {
      const s = String(c ?? '');
      return /[",;\n]/.test(s) ? `"${s.replace(/"/g,'""')}"` : s;
    }).join(';')).join('\n');
    const blob = new Blob(['\uFEFF' + csv], { type: 'text/csv;charset=utf-8;' });
    const a = document.createElement('a');
    a.href = URL.createObjectURL(blob);
    a.download = `catalogue-vival-${new Date().toISOString().slice(0,10)}.csv`;
    a.click();
  };

  const newProduct = () => {
    const blank = {
      id: 'custom-' + Date.now(),
      rayon: 'epicerie-salee',
      name: '',
      format: '',
      price: 0,
      badges: [],
      sub: 'Tout',
      byWeight: false,
      stock: 0,
      pinned: false,
      barcode: null,
      image: null,
      brand: '',
      isAlcohol: false,
      tvaRate: 0.055,
    };
    setAdding(blank);
  };

  return (
    <>
      {comeFrom === 'topflop' && (
        <div style={{padding:'8px 32px 0'}}>
          <button onClick={() => { sessionStorage.setItem('vival_reports_tab', 'topflop'); setRoute('reports'); }}
            style={{display:'inline-flex', alignItems:'center', gap:6, padding:'6px 12px', border:'1px solid var(--line-1)', borderRadius:8, background:'white', cursor:'pointer', fontSize:13, color:'var(--ink-2)'}}>
            ← Retour à Top / Flop
          </button>
        </div>
      )}
      <div className="page-header">
        <div className="page-header-grow">
          <h1 className="page-title">Catalogue</h1>
          <p className="page-subtitle">{filtered.length.toLocaleString('fr-FR')} produit{filtered.length>1?'s':''} · {PRODUCTS.length.toLocaleString('fr-FR')} au total</p>
        </div>
        <Button variant="outline" icon="download" onClick={exportCsv}>Export CSV</Button>
        {isAdmin && (
          <Button variant="outline" icon={syncState.running ? 'clock' : 'download'} onClick={triggerScrape} disabled={syncState.running}>
            {syncState.running ? 'Synchro en cours…' : 'Synchroniser'}
          </Button>
        )}
        {isAdmin && (
          <Button variant="outline" icon="clock" onClick={() => setHistoryOpen(true)}>Historique</Button>
        )}
        <Button variant="outline" icon="box-check" onClick={() => app.setReceivingOpen(true)}>Réception marchandise</Button>
        <Button variant="primary" icon="plus" onClick={newProduct}>Nouveau produit</Button>
      </div>

      {syncState.status && (
        <div style={{margin:'0 32px 12px', padding:'12px 16px', borderRadius:'var(--r-md)', fontSize:13,
          background: syncState.status === 'failed' ? 'var(--danger-soft, #fee2e2)' : syncState.status === 'success' ? 'var(--success-soft, #dcfce7)' : 'var(--surface-1)',
          border: '1px solid ' + (syncState.status === 'failed' ? 'var(--danger, #c02424)' : syncState.status === 'success' ? 'var(--success, #16a34a)' : 'var(--line-1)'),
          color: syncState.status === 'failed' ? 'var(--danger, #c02424)' : 'var(--ink-1)'}}>
          <div style={{display:'flex', alignItems:'center', gap:10}}>
            <Icon name={syncState.status === 'failed' ? 'alert' : syncState.status === 'success' ? 'check-circle' : 'clock'} style={{width:16, height:16}} />
            <div style={{flex:1, fontWeight:600}}>
              {syncState.status === 'starting' && 'Démarrage de la synchronisation…'}
              {syncState.status === 'running' && `Synchronisation en cours — ${syncState.progressPercent ?? 0}%`}
              {syncState.status === 'success' && (<>
                <span>Synchronisation terminée · </span>
                <span style={{fontWeight:700}}>{syncState.productsUpserted ?? '?'} nouveaux produits</span>
              </>)}
              {syncState.status === 'failed' && <>Échec de la synchronisation — {syncState.error}</>}
            </div>
            {syncState.status === 'success' && syncState.runId && (syncState.productsUpserted ?? 0) > 0 && (
              <Button variant="primary" size="sm" icon="search" onClick={() => {
                setNewRunFilter({ runId: syncState.runId, startedAt: null, productsUpserted: syncState.productsUpserted });
              }}>Voir les {syncState.productsUpserted}</Button>
            )}
            {(syncState.status === 'success' || syncState.status === 'failed') && (
              <Button variant="ghost" size="sm" icon="close" onClick={() => setSyncState({ running: false, runId: null, status: null, error: null, productsUpserted: null, progressPercent: null, progressMessage: null })} />
            )}
          </div>
          {(syncState.status === 'running' || syncState.status === 'starting') && (
            <>
              <div style={{marginTop:10, height:8, borderRadius:4, background:'var(--line-2, #e5e7eb)', overflow:'hidden'}}>
                <div style={{
                  height:'100%',
                  width: `${Math.max(2, syncState.progressPercent || 0)}%`,
                  background: 'var(--vival-red)',
                  transition: 'width .4s ease-out',
                }} />
              </div>
              {syncState.progressMessage && (
                <div style={{marginTop:6, fontSize:12, color:'var(--ink-3)', fontWeight:500}}>
                  {syncState.progressMessage}
                </div>
              )}
            </>
          )}
        </div>
      )}

      {newRunFilter && (
        <div style={{margin:'0 32px 12px', padding:'10px 14px', borderRadius:'var(--r-md)', background:'#eef6ff', border:'1px solid #93c5fd', fontSize:13, display:'flex', alignItems:'center', gap:10}}>
          <Icon name="filter" style={{width:16, height:16, color:'#2563eb'}} />
          <div style={{flex:1}}>
            Filtre actif : <strong>nouveaux produits de cette synchro</strong>
            {newRunFilter.productsUpserted != null && <span> · {newRunFilter.productsUpserted} produit{newRunFilter.productsUpserted>1?'s':''}</span>}
            {newRunFilter.startedAt && <span className="muted"> · {new Date(newRunFilter.startedAt).toLocaleString('fr-FR')}</span>}
          </div>
          <Button variant="ghost" size="sm" icon="close" onClick={() => setNewRunFilter(null)}>Effacer le filtre</Button>
        </div>
      )}

      <div className="admin-filters" style={{padding:'0 32px 16px', display:'flex', gap:10, flexWrap:'wrap', alignItems:'center'}}>
        <div style={{flex:'1 1 260px', minWidth:200}}>
          <Input icon="search" placeholder="Rechercher (nom, marque, EAN)…" value={query} onChange={e => setQuery(e.target.value)} />
        </div>
        <select value={rayonFilter} onChange={e => setRayonFilter(e.target.value)}
          style={{padding:'10px 12px', border:'1px solid var(--line)', borderRadius:8, fontSize:14, background:'#fff'}}>
          <option value="all">Tous rayons ({PRODUCTS.length})</option>
          {RAYONS.map(r => {
            const n = PRODUCTS.filter(p => p.rayon === r.id).length;
            return <option key={r.id} value={r.id}>{r.name} ({n})</option>;
          })}
        </select>
        {rayonFilter !== 'all' && (() => {
          const r = RAYONS.find(x => x.id === rayonFilter);
          if (!r || r.subs.length <= 1) return null;
          const productsInRayon = PRODUCTS.filter(p => p.rayon === rayonFilter);
          return (
            <select value={subFilter} onChange={e => setSubFilter(e.target.value)}
              style={{padding:'10px 12px', border:'1px solid var(--line)', borderRadius:8, fontSize:14, background:'#fff'}}>
              <option value="all">Toutes sous-catégories</option>
              {r.subs.filter(s => s !== 'Tout').map(s => {
                const n = productsInRayon.filter(p => (p.sub || 'Tout') === s).length;
                if (n === 0) return null;
                return <option key={s} value={s}>{s} ({n})</option>;
              })}
            </select>
          );
        })()}
        <select value={stockFilter} onChange={e => setStockFilter(e.target.value)}
          style={{padding:'10px 12px', border:'1px solid var(--line)', borderRadius:8, fontSize:14, background:'#fff'}}>
          <option value="all">Stock · tous</option>
          <option value="in">En stock (&gt; 3)</option>
          <option value="low">Stock faible (≤ 3)</option>
          <option value="out">Rupture</option>
        </select>
        <select value={activeFilter} onChange={e => setActiveFilter(e.target.value)}
          style={{padding:'10px 12px', border:'1px solid var(--line)', borderRadius:8, fontSize:14, background:'#fff'}}>
          <option value="active">Actifs seulement</option>
          <option value="inactive">Inactifs seulement</option>
          <option value="all">Actifs et inactifs</option>
        </select>
        <select value={sort} onChange={e => setSort(e.target.value)}
          style={{padding:'10px 12px', border:'1px solid var(--line)', borderRadius:8, fontSize:14, background:'#fff'}}>
          <option value="name">Tri · nom A→Z</option>
          <option value="rayon">Tri · par rayon</option>
          <option value="price">Tri · prix croissant</option>
          <option value="priceDesc">Tri · prix décroissant</option>
          <option value="stock">Tri · stock croissant</option>
        </select>
      </div>

      <div className="admin-table-wrap" style={{padding:'0 32px 32px', overflowX:'auto'}}>
        <table className="admin-table" style={{width:'100%', borderCollapse:'collapse', background:'#fff', borderRadius:10, overflow:'hidden', fontSize:13}}>
          <thead>
            <tr style={{background:'var(--surface-2)', textAlign:'left'}}>
              <th style={{padding:'10px 12px', width:52}}></th>
              <th style={{padding:'10px 12px'}}>Produit</th>
              <th style={{padding:'10px 12px'}}>Rayon</th>
              <th style={{padding:'10px 12px', textAlign:'right'}}>Prix TTC</th>
              <th style={{padding:'10px 12px', textAlign:'right'}}>TVA</th>
              <th style={{padding:'10px 12px', textAlign:'right'}}>Stock</th>
              <th style={{padding:'10px 12px', width:60}}></th>
            </tr>
          </thead>
          <tbody>
            {pageList.map(p => {
              const rn = RAYONS.find(r => r.id === p.rayon)?.name || p.rayon;
              const stockCls = p.stock === 0 ? '#dc2626' : (p.stock <= 3 ? '#ea580c' : '#1b5e20');
              const inactive = p.isActive === false;
              return (
                <tr key={p.id} onClick={() => setEditing(p)} style={{borderTop:'1px solid var(--line)', cursor:'pointer', opacity: inactive ? 0.5 : 1}}
                    onMouseEnter={e => e.currentTarget.style.background='var(--surface-2)'}
                    onMouseLeave={e => e.currentTarget.style.background=''}>
                  <td style={{padding:'8px 12px'}}>
                    {p.image
                      ? <img src={p.image} alt="" style={{width:40, height:40, objectFit:'contain', background:'var(--surface-2)', borderRadius:6, filter: inactive ? 'grayscale(1)' : 'none'}} onError={e => { e.target.style.visibility='hidden'; }} />
                      : <div style={{width:40, height:40, background:'var(--surface-2)', borderRadius:6}} />}
                  </td>
                  <td style={{padding:'8px 12px'}}>
                    <div style={{fontWeight:600, textDecoration: inactive ? 'line-through' : 'none'}}>{p.name || <em style={{color:'#999'}}>(sans nom)</em>}</div>
                    <div className="muted" style={{fontSize:12, marginTop:2}}>
                      {p.brand}{p.brand && p.format ? ' · ' : ''}{p.format}
                      {p.barcode && <span style={{marginLeft:6, fontFamily:'var(--font-mono)'}}>EAN {p.barcode}</span>}
                      {inactive && <span className="status-pill" style={{marginLeft:6, background:'#fef3c7', color:'#92400e', fontSize:10, fontWeight:700}}>INACTIF</span>}
                      {p.isAlcohol && <span className="status-pill" style={{marginLeft:6, background:'#fef3c7', color:'#92400e', fontSize:10}}>alcool</span>}
                      {p.pinned && <span className="status-pill" style={{marginLeft:6, background:'#dcfce7', color:'#166534', fontSize:10}}>épinglé</span>}
                      {p.isShared && <span className="status-pill" style={{marginLeft:6, background:'#e0f2fe', color:'#075985', fontSize:10}}>Partagé</span>}
                      {p.importedFrom && <span className="status-pill" style={{marginLeft:6, background:'#ecfccb', color:'#365314', fontSize:10}}>Importé</span>}
                    </div>
                  </td>
                  <td style={{padding:'8px 12px', fontSize:12, whiteSpace:'nowrap'}}>{rn}</td>
                  <td style={{padding:'8px 12px', textAlign:'right', fontWeight:700, fontVariantNumeric:'tabular-nums'}}>{formatPrice(p.price).full}</td>
                  <td style={{padding:'8px 12px', textAlign:'right', fontSize:12, color:'var(--ink-2)'}}>{(p.tvaRate*100).toFixed(1).replace('.0','')} %</td>
                  <td style={{padding:'8px 12px', textAlign:'right', fontWeight:600, color:stockCls, fontVariantNumeric:'tabular-nums'}}>
                    {p.stock === 0 ? 'Rupture' : p.stock}
                  </td>
                  <td style={{padding:'8px 12px', textAlign:'center'}}>
                    <div style={{display:'flex', gap:6, justifyContent:'center'}}>
                      <button
                        onClick={async (e) => {
                          e.stopPropagation();
                          const next = inactive; // si inactif -> activer, et inversement
                          await window.updateProduct(p.id, { isActive: next });
                          // Reflete localement sans recharger
                          p.isActive = next;
                          setRefreshTick(t => t + 1);
                        }}
                        title={inactive ? 'Activer' : 'Désactiver'}
                        aria-label={inactive ? 'Activer' : 'Désactiver'}
                        style={{
                          background: inactive ? '#fef3c7' : '#dcfce7',
                          color: inactive ? '#92400e' : '#166534',
                          border:'1px solid ' + (inactive ? '#fbbf24' : '#4ade80'),
                          borderRadius:6, padding:'4px 10px', cursor:'pointer',
                          fontSize:11, fontWeight:700,
                        }}>
                        {inactive ? 'Activer' : 'Actif'}
                      </button>
                      <button onClick={(e) => { e.stopPropagation(); setEditing(p); }}
                        style={{background:'transparent', border:'1px solid var(--line)', borderRadius:6, padding:'4px 8px', cursor:'pointer'}}
                        aria-label="Modifier">
                        <Icon name="edit" />
                      </button>
                    </div>
                  </td>
                </tr>
              );
            })}
            {pageList.length === 0 && (
              <tr><td colSpan={7} style={{padding:40, textAlign:'center'}} className="muted">Aucun produit ne correspond aux filtres.</td></tr>
            )}
          </tbody>
        </table>

        {pageCount > 1 && (
          <div style={{display:'flex', justifyContent:'center', gap:8, marginTop:16, alignItems:'center'}}>
            <Button variant="ghost" icon="chevron-left" onClick={() => setPage(p => Math.max(0, p-1))} disabled={page === 0}>Précédent</Button>
            <span className="muted" style={{fontSize:13}}>Page {page+1} / {pageCount}</span>
            <Button variant="ghost" icon="chevron-right" onClick={() => setPage(p => Math.min(pageCount-1, p+1))} disabled={page >= pageCount-1}>Suivant</Button>
          </div>
        )}
      </div>

      {editing && (
        <ProductEditModal
          product={editing}
          onClose={() => setEditing(null)}
          onSaved={() => { setEditing(null); app.showToast('Produit modifié'); }}
        />
      )}
      {adding && (
        <ProductEditModal
          product={adding}
          isNew
          onClose={() => setAdding(false)}
          onSaved={(p) => { window.addCustomProduct(p); setAdding(false); app.showToast('Produit ajouté au catalogue'); }}
        />
      )}

      {historyOpen && (
        <SyncHistoryModal
          onClose={() => setHistoryOpen(false)}
          onSelectRun={(run) => { setNewRunFilter({ runId: run.id, startedAt: run.started_at, productsUpserted: run.products_upserted }); setHistoryOpen(false); }}
        />
      )}

      <LastSyncLabel align="center" />
    </>
  );
}

function SyncHistoryModal({ onClose, onSelectRun }) {
  const [runs, setRuns] = useState(null);
  const [selected, setSelected] = useState(null); // run complet sélectionné
  const [selectedProducts, setSelectedProducts] = useState(null); // produits ajoutés par ce run

  useEffect(() => {
    (async () => {
      const { data } = await window.sb
        .from('scrape_runs')
        .select('id, started_at, finished_at, status, trigger, triggered_by, products_upserted, error')
        .order('started_at', { ascending: false })
        .limit(60);
      setRuns(data || []);
    })();
  }, []);

  useEffect(() => {
    if (!selected) { setSelectedProducts(null); return; }
    (async () => {
      const { data } = await window.sb
        .from('products')
        .select('id, name, brand, format, price, rayon')
        .eq('added_by_run', selected.id)
        .order('name', { ascending: true })
        .limit(1000);
      setSelectedProducts(data || []);
    })();
  }, [selected?.id]);

  const formatDate = (s) => s ? new Date(s).toLocaleString('fr-FR', { dateStyle: 'short', timeStyle: 'short' }) : '—';

  return (
    <Modal open={true} onClose={onClose} title="Historique des synchronisations" size="lg"
      footer={<Button variant="ghost" onClick={onClose}>Fermer</Button>}
    >
      <div style={{display:'grid', gridTemplateColumns:'320px 1fr', gap:16, minHeight:400, maxHeight:'70vh'}}>
        {/* Liste des runs */}
        <div style={{overflowY:'auto', borderRight:'1px solid var(--line-2)'}}>
          {!runs && <div className="muted" style={{padding:16}}>Chargement…</div>}
          {runs?.length === 0 && <div className="muted" style={{padding:16}}>Aucune synchronisation encore.</div>}
          {runs?.map(r => {
            const isSel = selected?.id === r.id;
            const color = r.status === 'success' ? '#16a34a' : r.status === 'failed' ? '#c02424' : '#6b7280';
            return (
              <div key={r.id}
                onClick={() => setSelected(r)}
                style={{
                  padding:'10px 12px',
                  cursor:'pointer',
                  borderBottom:'1px solid var(--line-2)',
                  background: isSel ? 'var(--surface-1)' : 'transparent',
                  borderLeft: isSel ? '3px solid var(--vival-red)' : '3px solid transparent',
                }}>
                <div style={{display:'flex', justifyContent:'space-between', alignItems:'baseline', fontSize:13}}>
                  <strong>{formatDate(r.started_at)}</strong>
                  <span style={{color, fontSize:11, fontWeight:700, textTransform:'uppercase'}}>{r.status}</span>
                </div>
                <div className="muted" style={{fontSize:11, marginTop:2}}>
                  {r.trigger === 'cron' ? 'Auto (5h)' : r.trigger === 'manual' ? 'Manuel' : r.trigger}
                  {r.triggered_by && r.triggered_by !== 'cron' && ` · ${r.triggered_by}`}
                </div>
                {r.status === 'success' && (
                  <div style={{fontSize:12, marginTop:3, color:'var(--ink-2)'}}>
                    <strong>{r.products_upserted ?? 0}</strong> nouveau{(r.products_upserted ?? 0)>1?'x':''} produit{(r.products_upserted ?? 0)>1?'s':''}
                  </div>
                )}
                {r.status === 'failed' && r.error && (
                  <div style={{fontSize:11, marginTop:3, color:'#c02424'}}>{r.error.slice(0, 80)}</div>
                )}
              </div>
            );
          })}
        </div>

        {/* Détail du run sélectionné */}
        <div style={{overflowY:'auto'}}>
          {!selected && (
            <div className="muted" style={{padding:32, textAlign:'center'}}>
              <Icon name="clock" style={{width:48, height:48, marginBottom:8, color:'var(--ink-4)'}} />
              <div>Sélectionne une synchronisation pour voir les produits ajoutés</div>
            </div>
          )}
          {selected && (
            <div>
              <div style={{padding:'0 4px 12px', borderBottom:'1px solid var(--line-2)', marginBottom:12}}>
                <div style={{fontSize:14, fontWeight:700}}>{formatDate(selected.started_at)}</div>
                <div className="muted" style={{fontSize:12, marginTop:2}}>
                  {selected.trigger === 'cron' ? 'Synchro automatique' : `Déclenché par ${selected.triggered_by}`}
                  {selected.finished_at && ` · Durée ${Math.round((new Date(selected.finished_at) - new Date(selected.started_at))/1000)}s`}
                </div>
                {selected.status === 'success' && (selected.products_upserted ?? 0) > 0 && (
                  <div style={{marginTop:10}}>
                    <Button variant="primary" size="sm" icon="filter" onClick={() => onSelectRun(selected)}>
                      Filtrer le catalogue sur ces {selected.products_upserted} produits
                    </Button>
                  </div>
                )}
              </div>
              {!selectedProducts && <div className="muted" style={{padding:16}}>Chargement…</div>}
              {selectedProducts?.length === 0 && (
                <div className="muted" style={{padding:16, fontSize:13}}>
                  {selected.status === 'success'
                    ? 'Aucun nouveau produit lors de cette synchro (tout existait déjà en base).'
                    : 'Synchro échouée ou interrompue — aucun produit ajouté.'}
                </div>
              )}
              {selectedProducts?.length > 0 && (
                <div>
                  {selectedProducts.map(p => (
                    <div key={p.id} style={{padding:'8px 4px', borderBottom:'1px solid var(--line-2)', fontSize:13}}>
                      <div style={{fontWeight:600}}>{p.name}</div>
                      <div className="muted" style={{fontSize:11, marginTop:1}}>
                        {p.brand && `${p.brand} · `}{p.format && `${p.format} · `}{p.rayon}
                      </div>
                      <div style={{fontSize:12, fontWeight:700, marginTop:2}}>{formatPrice(Number(p.price)).full}</div>
                    </div>
                  ))}
                </div>
              )}
            </div>
          )}
        </div>
      </div>
    </Modal>
  );
}

function ProductEditModal({ product, onClose, onSaved, isNew }) {
  const [name, setName] = useState(product.name);
  const [brand, setBrand] = useState(product.brand || '');
  const [format, setFormat] = useState(product.format || '');
  const [price, setPrice] = useState(String(product.price ?? '').replace('.', ','));
  const [purchasePrice, setPurchasePrice] = useState(String(product.purchasePrice ?? '').replace('.', ','));
  const [rayon, setRayon] = useState(product.rayon);
  const [sub, setSub] = useState(product.sub || 'Tout');
  const [tvaRate, setTvaRate] = useState(product.tvaRate ?? 0.055);
  const [isAlcohol, setIsAlcohol] = useState(!!product.isAlcohol);
  const [byWeight, setByWeight] = useState(!!product.byWeight);
  const [stock, setStock] = useState(String(product.stock ?? 0));
  const [pinned, setPinned] = useState(!!product.pinned);
  const [isShared, setIsShared] = useState(!!product.isShared);
  const [isActive, setIsActive] = useState(product.isActive !== false);
  const [image, setImage] = useState(product.image || '');

  const rayonObj = RAYONS.find(r => r.id === rayon) || RAYONS[0];

  const save = () => {
    const patch = {
      name: name.trim(),
      brand: brand.trim(),
      format: format.trim(),
      price: Number(String(price).replace(',', '.')) || 0,
      purchasePrice: Number(String(purchasePrice).replace(',', '.')) || 0,
      rayon,
      sub,
      tvaRate: Number(tvaRate),
      isAlcohol,
      byWeight,
      stock: parseInt(stock) || 0,
      pinned,
      isShared,
      isActive,
      image: image.trim() || null,
    };
    if (isNew) {
      onSaved({ ...product, ...patch });
    } else {
      window.updateProduct(product.id, patch);
      onSaved({ ...product, ...patch });
    }
  };

  const remove = async () => {
    if (!confirm(`Supprimer "${product.name}" du catalogue ?`)) return;
    await window.deleteProduct(product.id);
    onClose();
  };

  const inputStyle = { width: '100%', padding: '10px 12px', border: '1px solid var(--line)', borderRadius: 8, fontSize: 14 };

  return (
    <Modal open={true} onClose={onClose} title={isNew ? 'Nouveau produit' : `Modifier : ${product.name}`}
      footer={<>
        {!isNew && <Button variant="danger" icon="trash" onClick={remove}>Supprimer</Button>}
        <div style={{flex:1}}></div>
        <Button variant="ghost" onClick={onClose}>Annuler</Button>
        <Button variant="primary" icon="check-circle" onClick={save} disabled={!name.trim()}>{isNew ? 'Ajouter au catalogue' : 'Enregistrer'}</Button>
      </>}
    >
      <div style={{display:'grid', gap:12}}>
        {!isNew && (
          <label style={{
            display:'flex', alignItems:'center', gap:12,
            padding:'12px 14px',
            background: isActive ? 'var(--success-soft, #dcfce7)' : 'var(--warning-soft, #fef3c7)',
            border: '1px solid ' + (isActive ? 'var(--success, #16a34a)' : 'var(--warning, #d97706)'),
            borderRadius:8, cursor:'pointer',
          }}>
            <input type="checkbox" checked={isActive} onChange={e => setIsActive(e.target.checked)} style={{width:18, height:18}} />
            <div style={{flex:1}}>
              <div style={{fontWeight:700, fontSize:13, color: isActive ? '#166534' : '#92400e'}}>
                {isActive ? 'Produit actif' : 'Produit inactif'}
              </div>
              <div style={{fontSize:11, color: isActive ? '#166534' : '#92400e', marginTop:2}}>
                {isActive
                  ? 'Visible dans la boutique en ligne et la caisse.'
                  : 'Masqué partout (boutique + caisse). Réactivable à tout moment.'}
              </div>
            </div>
          </label>
        )}
        <label style={{fontSize:13}}>
          <div style={{fontWeight:700, marginBottom:4}}>Nom</div>
          <input type="text" value={name} onChange={e => setName(e.target.value)} style={inputStyle} />
        </label>
        <div style={{display:'grid', gridTemplateColumns:'1fr 1fr', gap:12}}>
          <label style={{fontSize:13}}>
            <div style={{fontWeight:700, marginBottom:4}}>Marque</div>
            <input type="text" value={brand} onChange={e => setBrand(e.target.value)} style={inputStyle} />
          </label>
          <label style={{fontSize:13}}>
            <div style={{fontWeight:700, marginBottom:4}}>Format / Conditionnement</div>
            <input type="text" value={format} onChange={e => setFormat(e.target.value)} placeholder="ex: 500 g ou 75 cl" style={inputStyle} />
          </label>
        </div>
        <div style={{display:'grid', gridTemplateColumns:'1fr 1fr 1fr 1fr', gap:12}}>
          <label style={{fontSize:13}}>
            <div style={{fontWeight:700, marginBottom:4}}>Prix TTC (€)</div>
            <input type="text" inputMode="decimal" value={price} onChange={e => setPrice(e.target.value)} style={inputStyle} />
          </label>
          <label style={{fontSize:13}}>
            <div style={{fontWeight:700, marginBottom:4}}>Prix d'achat HT (€)</div>
            <input type="text" inputMode="decimal" value={purchasePrice} onChange={e => setPurchasePrice(e.target.value)} placeholder="0,00" style={inputStyle} />
          </label>
          <label style={{fontSize:13}}>
            <div style={{fontWeight:700, marginBottom:4}}>TVA</div>
            <select value={tvaRate} onChange={e => setTvaRate(parseFloat(e.target.value))} style={{...inputStyle, background:'#fff'}}>
              <option value={0.055}>5,5 % (alimentaire)</option>
              <option value={0.10}>10 % (restauration)</option>
              <option value={0.20}>20 % (alcool / non-alim.)</option>
              <option value={0.021}>2,1 % (presse, médic.)</option>
              <option value={0}>0 %</option>
            </select>
          </label>
          <label style={{fontSize:13}}>
            <div style={{fontWeight:700, marginBottom:4}}>Stock</div>
            <input type="number" value={stock} onChange={e => setStock(e.target.value)} style={inputStyle} />
          </label>
        </div>
        {(() => {
          const priceNum = Number(String(price).replace(',', '.')) || 0;
          const purchaseNum = Number(String(purchasePrice).replace(',', '.')) || 0;
          const priceHt = priceNum / (1 + Number(tvaRate));
          const marginEur = priceHt - purchaseNum;
          const marginPct = priceHt > 0 ? (marginEur / priceHt) * 100 : 0;
          if (!purchaseNum) {
            return <div style={{fontSize:12, color:'var(--ink-3)', marginTop:-4, fontStyle:'italic'}}>Renseigne un prix d'achat pour suivre la marge de ce produit.</div>;
          }
          const color = marginEur < 0 ? 'var(--danger, #c02424)' : marginEur < priceHt * 0.1 ? 'var(--warning, #ea580c)' : 'var(--success, #16a34a)';
          return (
            <div style={{fontSize:13, marginTop:-4, color, fontWeight:600}}>
              Marge : {marginEur.toFixed(2).replace('.', ',')} € HT ({marginPct.toFixed(1).replace('.', ',')} %) · PA {purchaseNum.toFixed(2).replace('.', ',')} € → PV HT {priceHt.toFixed(2).replace('.', ',')} €
            </div>
          );
        })()}
        <div style={{display:'grid', gridTemplateColumns:'1fr 1fr', gap:12}}>
          <label style={{fontSize:13}}>
            <div style={{fontWeight:700, marginBottom:4}}>Rayon</div>
            <select value={rayon} onChange={e => { setRayon(e.target.value); setSub('Tout'); }} style={{...inputStyle, background:'#fff'}}>
              {RAYONS.map(r => <option key={r.id} value={r.id}>{r.name}</option>)}
            </select>
          </label>
          <label style={{fontSize:13}}>
            <div style={{fontWeight:700, marginBottom:4}}>Sous-rayon</div>
            <select value={sub} onChange={e => setSub(e.target.value)} style={{...inputStyle, background:'#fff'}}>
              {rayonObj.subs.map(s => <option key={s} value={s}>{s}</option>)}
            </select>
          </label>
        </div>
        <div style={{fontSize:13}}>
          <div style={{fontWeight:700, marginBottom:8}}>Photo produit</div>
          <div style={{display:'flex', gap:12, alignItems:'flex-start'}}>
            <div style={{width:100, height:100, background:'var(--surface-2)', borderRadius:8, display:'flex', alignItems:'center', justifyContent:'center', overflow:'hidden', flexShrink:0}}>
              {image
                ? <img src={image} alt="" style={{width:'100%', height:'100%', objectFit:'contain'}} onError={e => { e.target.style.display='none'; }} />
                : <span className="muted" style={{fontSize:11}}>aucune</span>}
            </div>
            <div style={{flex:1, display:'flex', flexDirection:'column', gap:6}}>
              <label style={{cursor:'pointer'}}>
                <input type="file" accept="image/*" style={{display:'none'}} onChange={async e => {
                  const file = e.target.files?.[0];
                  if (!file) return;
                  if (file.size > 3 * 1024 * 1024) { alert('Image trop volumineuse (max 3 Mo).'); return; }
                  try {
                    const dataUrl = await compressImage(file, 800, 0.82);
                    setImage(dataUrl);
                  } catch (_) {
                    const reader = new FileReader();
                    reader.onload = () => setImage(reader.result);
                    reader.readAsDataURL(file);
                  }
                }} />
                <span style={{display:'inline-flex', alignItems:'center', gap:6, padding:'8px 14px', background:'var(--vival-red)', color:'#fff', borderRadius:8, fontWeight:600, fontSize:13}}>
                  <Icon name="plus" /> {image ? 'Changer la photo' : 'Téléverser une photo'}
                </span>
              </label>
              {image && (
                <button type="button" onClick={() => setImage('')}
                  style={{background:'transparent', border:'1px solid var(--line)', borderRadius:8, padding:'8px 14px', fontSize:13, cursor:'pointer', color:'var(--ink-2)', width:'fit-content'}}>
                  Retirer la photo
                </button>
              )}
              <div className="muted" style={{fontSize:11, marginTop:2}}>JPEG ou PNG, 3 Mo max. Compressée automatiquement à 800 px.</div>
            </div>
          </div>
        </div>
        <div style={{display:'flex', gap:20, flexWrap:'wrap', marginTop:4}}>
          <label style={{display:'flex', alignItems:'center', gap:8, fontSize:14, cursor:'pointer'}}>
            <input type="checkbox" checked={isAlcohol} onChange={e => { setIsAlcohol(e.target.checked); if (e.target.checked) setTvaRate(0.20); }} />
            <span>Produit alcoolisé</span>
          </label>
          <label style={{display:'flex', alignItems:'center', gap:8, fontSize:14, cursor:'pointer'}}>
            <input type="checkbox" checked={byWeight} onChange={e => setByWeight(e.target.checked)} />
            <span>Vendu au poids</span>
          </label>
          <label style={{display:'flex', alignItems:'center', gap:8, fontSize:14, cursor:'pointer'}}>
            <input type="checkbox" checked={pinned} onChange={e => setPinned(e.target.checked)} />
            <span>Épinglé (accès rapide)</span>
          </label>
        </div>
        {(isNew || product.storeId === VivalData._storeId) && (
          <label style={{display:'flex', alignItems:'flex-start', gap:8, padding:'10px 12px', background:'var(--surface-1)', borderRadius:8, cursor:'pointer'}}>
            <input type="checkbox" checked={isShared} onChange={e => setIsShared(e.target.checked)} style={{marginTop:3}} />
            <div>
              <div style={{fontWeight:600, fontSize:13}}>Partager avec le réseau Vival</div>
              <div className="muted" style={{fontSize:11, marginTop:2}}>Les autres Vival pourront importer ce produit dans leur catalogue (ils gèrent leur propre stock).</div>
            </div>
          </label>
        )}
        {product.importedFrom && (
          <div style={{padding:'8px 12px', background:'#ecfccb', border:'1px solid #84cc16', borderRadius:8, fontSize:12, color:'#365314'}}>
            Produit importé depuis un autre magasin du réseau. Vous gérez votre prix et stock de manière indépendante.
          </div>
        )}
        <div className="muted" style={{fontSize:12}}>
          <code style={{fontFamily:'var(--font-mono)'}}>EAN {product.barcode || '—'}</code> · ID {product.id}
        </div>
      </div>
    </Modal>
  );
}

function ProductDetailModal() {
  const { selectedProduct, setSelectedProduct, addToCart, showToast } = useApp();
  const [qty, setQty] = useState(1);
  const [editing, setEditing] = useState(false);
  useEffect(() => { setQty(selectedProduct?.byWeight ? 1 : 1); setEditing(false); }, [selectedProduct]);
  if (!selectedProduct) return null;
  const p = selectedProduct;
  const step = p.byWeight ? 0.1 : 1;
  if (editing) {
    return <ProductEditModal product={p} onClose={() => setEditing(false)} onSaved={(np) => { setSelectedProduct({...np}); setEditing(false); showToast('Produit modifié'); }} />;
  }
  return (
    <Modal open={true} onClose={() => setSelectedProduct(null)} title={p.name}
      footer={<>
        <Button variant="ghost" icon="edit" onClick={() => setEditing(true)}>Modifier</Button>
        <Button variant="ghost" onClick={() => setSelectedProduct(null)}>Annuler</Button>
        <Button variant="primary" icon="plus" onClick={() => { addToCart(p.id, qty); setSelectedProduct(null); }}>
          Ajouter · {formatPrice(p.price * qty).full}
        </Button>
      </>}
    >
      <div style={{display:'flex', gap:20, alignItems:'center'}}>
        <div className="product-image" style={{width:140, height:140, flexShrink:0}}>
          {p.image ? <img src={p.image} alt="" style={{width:'100%',height:'100%',objectFit:'contain'}} /> : <Icon name={p.rayon} />}
        </div>
        <div style={{flex:1}}>
          <div style={{fontSize:12, textTransform:'uppercase', letterSpacing:'0.05em', color:'var(--ink-3)', fontWeight:700}}>
            {RAYONS.find(r => r.id === p.rayon)?.name} · {p.sub}
          </div>
          <div style={{fontSize:24, fontWeight:800, margin:'4px 0'}}>{formatPrice(p.price).full}</div>
          <div className="muted" style={{fontSize:14}}>{p.format}</div>
          <div style={{marginTop:8, display:'flex', gap:6}}>{p.badges.map(b => <Badge key={b} kind={b}>{b}</Badge>)}</div>
        </div>
      </div>

      <div style={{marginTop:24}}>
        <div className="kpi-label" style={{marginBottom:8}}>Quantité {p.byWeight && '(en kg)'}</div>
        <div style={{display:'flex', alignItems:'center', gap:12, flexWrap:'wrap'}}>
          <QtyControl value={qty} onChange={setQty} step={step} byWeight={p.byWeight} />
          <div style={{display:'flex', alignItems:'center', gap:6}}>
            <input
              type="text"
              inputMode={p.byWeight ? 'decimal' : 'numeric'}
              value={p.byWeight ? String(qty).replace('.', ',') : String(qty)}
              onChange={e => {
                const n = Number(String(e.target.value).replace(',', '.'));
                if (!isNaN(n) && n >= 0) setQty(p.byWeight ? n : Math.floor(n));
              }}
              style={{width:90, padding:'8px 10px', fontSize:14, border:'2px solid var(--vival-red)', borderRadius:8, fontWeight:700, textAlign:'center'}}
            />
            <span style={{fontSize:13, fontWeight:700, color:'var(--ink-2)'}}>{p.byWeight ? 'kg' : 'u'}</span>
          </div>
          {p.byWeight && (
            <div style={{display:'flex', gap:6}}>
              {[0.5, 1, 2, 5].map(v => <button key={v} className="chip" onClick={() => setQty(v)}>{v}kg</button>)}
            </div>
          )}
          {!p.byWeight && (
            <div style={{display:'flex', gap:6}}>
              {[1, 2, 5, 10].map(v => <button key={v} className="chip" onClick={() => setQty(v)}>×{v}</button>)}
            </div>
          )}
        </div>
      </div>
    </Modal>
  );
}

// ===== Scan overlay =====
// Charge ZXing en fallback si BarcodeDetector n'est pas dispo
let _zxingPromise = null;
function loadZXing() {
  if (_zxingPromise) return _zxingPromise;
  const candidates = [
    'https://cdn.jsdelivr.net/npm/@zxing/library@0.21.3/umd/index.min.js',
    'https://unpkg.com/@zxing/library@0.21.3/umd/index.min.js',
    'https://cdn.jsdelivr.net/npm/@zxing/library@0.20.0/umd/index.min.js',
  ];
  const tryLoad = (urls) => new Promise((resolve, reject) => {
    if (!urls.length) return reject(new Error('Aucun CDN ZXing accessible'));
    if (window.ZXing) return resolve(window.ZXing);
    const s = document.createElement('script');
    s.src = urls[0];
    s.async = true;
    s.onload = () => window.ZXing ? resolve(window.ZXing) : tryLoad(urls.slice(1)).then(resolve, reject);
    s.onerror = () => { s.remove(); tryLoad(urls.slice(1)).then(resolve, reject); };
    document.head.appendChild(s);
  });
  _zxingPromise = tryLoad(candidates).catch(e => { _zxingPromise = null; throw e; });
  return _zxingPromise;
}

function ScanModal() {
  const { scanOpen, setScanOpen, addToCart, setUnknownBarcode, showToast } = useApp();
  const [detected, setDetected] = useState(null);
  const [error, setError] = useState(null);
  const [manual, setManual] = useState('');
  const [lastScannedEan, setLastScannedEan] = useState(null);
  const [scannedCount, setScannedCount] = useState(0);
  const videoRef = useRef(null);
  const streamRef = useRef(null);
  const stopFnRef = useRef(null);

  const handleDetect = (rawEan) => {
    if (!rawEan) return;
    const ean = normalizeEan(rawEan); // 12 chiffres -> 13 chiffres auto
    if (ean === lastScannedEan) return;
    setLastScannedEan(ean);
    const p = PRODUCTS.find(x => x.barcode === ean);
    if (p) {
      // Auto-ajout au panier + flash visuel ; le scanner reste ouvert
      addToCart(p.id);
      setDetected(p);
      setScannedCount(c => c + 1);
      setTimeout(() => {
        setDetected(null);
        setLastScannedEan(null); // permet de rescanner le même article si besoin
      }, 1500);
    } else {
      // Pas dans le catalogue local → on ouvre le modal de recherche via API.
      // Ordre : d'abord setUnknownBarcode puis setScanOpen(false)
      // pour que React rende les deux modals en séquence sans "trou".
      setUnknownBarcode(ean);
      setScanOpen(false);
    }
  };

  const stopCamera = () => {
    if (stopFnRef.current) { try { stopFnRef.current(); } catch {} stopFnRef.current = null; }
    if (streamRef.current) {
      streamRef.current.getTracks().forEach(t => t.stop());
      streamRef.current = null;
    }
  };

  useEffect(() => {
    if (!scanOpen) { setDetected(null); setError(null); setManual(''); setLastScannedEan(null); stopCamera(); return; }

    let cancelled = false;
    (async () => {
      if (!navigator.mediaDevices?.getUserMedia) {
        setError("Caméra non disponible sur cet appareil/navigateur (essayez Safari ou Chrome récent).");
        return;
      }
      try {
        const stream = await navigator.mediaDevices.getUserMedia({
          video: { facingMode: { ideal: 'environment' }, width: { ideal: 1280 }, height: { ideal: 720 } },
          audio: false,
        });
        if (cancelled) { stream.getTracks().forEach(t => t.stop()); return; }
        streamRef.current = stream;
        if (videoRef.current) {
          videoRef.current.srcObject = stream;
          videoRef.current.setAttribute('playsinline', 'true');
          await videoRef.current.play().catch(() => {});
        }

        // 1) BarcodeDetector natif
        if ('BarcodeDetector' in window) {
          try {
            const detector = new window.BarcodeDetector({ formats: ['ean_13', 'ean_8', 'upc_a', 'upc_e', 'code_128', 'code_39'] });
            let raf;
            const loop = async () => {
              if (cancelled || !videoRef.current) return;
              try {
                const codes = await detector.detect(videoRef.current);
                if (codes && codes[0]?.rawValue) handleDetect(codes[0].rawValue);
              } catch {}
              raf = requestAnimationFrame(loop);
            };
            loop();
            stopFnRef.current = () => { if (raf) cancelAnimationFrame(raf); };
            return;
          } catch (e) { /* fallback ZXing */ }
        }

        // 2) Fallback ZXing
        try {
          const ZX = await loadZXing();
          if (cancelled) return;
          const reader = new ZX.BrowserMultiFormatReader();
          // API: decodeFromVideoElement (stream déjà attaché) renvoie une Promise,
          // on utilise plutôt decodeFromStream pour continu.
          if (typeof reader.decodeFromStream === 'function') {
            reader.decodeFromStream(streamRef.current, videoRef.current, (result, err) => {
              if (result) handleDetect(result.getText ? result.getText() : result.text);
            });
            stopFnRef.current = () => { try { reader.reset(); } catch {} };
          } else if (typeof reader.decodeFromVideoElementContinuously === 'function') {
            const controls = await reader.decodeFromVideoElementContinuously(videoRef.current, (result) => {
              if (result) handleDetect(result.getText());
            });
            stopFnRef.current = () => { try { controls.stop(); } catch {} try { reader.reset(); } catch {} };
          } else {
            // Polling manuel : decodeOnceFromVideoElement en boucle
            let stopped = false;
            const loop = async () => {
              if (stopped || cancelled || !videoRef.current) return;
              try {
                const r = await reader.decodeOnceFromVideoElement(videoRef.current);
                if (r) handleDetect(r.getText ? r.getText() : r.text);
              } catch {}
              if (!stopped) setTimeout(loop, 250);
            };
            loop();
            stopFnRef.current = () => { stopped = true; try { reader.reset(); } catch {} };
          }
        } catch (e) {
          setError("Scanner ZXing indisponible (" + (e.message || e) + "). Saisissez le code à la main ci-dessous.");
        }
      } catch (err) {
        let msg = err?.message || 'Caméra indisponible';
        if (err?.name === 'NotAllowedError') msg = "Accès caméra refusé. Réglages iPad → Safari → Appareil photo.";
        else if (err?.name === 'NotFoundError') msg = "Aucune caméra trouvée sur cet appareil.";
        else if (err?.name === 'NotReadableError') msg = "Caméra déjà utilisée par une autre app.";
        setError(msg);
      }
    })();
    return () => { cancelled = true; stopCamera(); };
  }, [scanOpen]);

  const submitManual = () => {
    const ean = manual.trim();
    if (!/^\d{8,14}$/.test(ean)) { showToast && showToast('Code EAN invalide (8 à 14 chiffres)'); return; }
    setManual('');
    handleDetect(ean);
  };

  useEffect(() => { if (!scanOpen) setScannedCount(0); }, [scanOpen]);

  if (!scanOpen) return null;
  return (
    <Modal open={true} onClose={() => setScanOpen(false)} title={scannedCount > 0 ? `Scanner · ${scannedCount} article${scannedCount>1?'s':''} ajouté${scannedCount>1?'s':''}` : 'Scanner un code-barres'}
      footer={<Button variant="primary" icon="check-circle" onClick={() => setScanOpen(false)}>Terminer</Button>}
    >
      <div className="scan-viewport" style={{position:'relative', background:'#000', overflow:'hidden', minHeight:280}}>
        <video
          ref={videoRef}
          autoPlay
          playsInline
          muted
          style={{width:'100%', height:'100%', objectFit:'cover', display:'block', minHeight:280}}
        />
        <div className="scan-frame" style={{position:'absolute', top:'50%', left:'50%', transform:'translate(-50%,-50%)', width:'75%', height:'35%'}}><span></span></div>
        {!detected && !error && <div className="scan-line"></div>}
        {error && (
          <div style={{position:'absolute', inset:0, display:'flex', alignItems:'center', justifyContent:'center', background:'rgba(0,0,0,.75)', color:'#fff', padding:20, textAlign:'center', zIndex:3}}>
            <div>
              <Icon name="alert" style={{width:32, height:32, color:'#f87171'}} />
              <div style={{marginTop:8, fontSize:14, fontWeight:600}}>{error}</div>
            </div>
          </div>
        )}
        {detected && (
          <div style={{position:'absolute', inset:0, background:'rgba(22, 163, 74, .85)', display:'flex', alignItems:'center', justifyContent:'center', padding:20, zIndex:3, animation:'fadein .2s var(--ease-out)'}}>
            <div style={{background:'white', borderRadius:12, padding:16, display:'flex', alignItems:'center', gap:14, width:'100%', maxWidth:480, boxShadow:'0 10px 40px rgba(0,0,0,.3)'}}>
              <div style={{width:64, height:64, background:'var(--surface-2, #f3f4f6)', borderRadius:10, display:'flex', alignItems:'center', justifyContent:'center', overflow:'hidden', flexShrink:0}}>
                {detected.image
                  ? <img src={detected.image} alt="" style={{width:'100%', height:'100%', objectFit:'contain'}} />
                  : <Icon name={detected.rayon} style={{width:32, height:32, color:'var(--ink-3)'}} />}
              </div>
              <div style={{flex:1, minWidth:0}}>
                <div style={{display:'flex', alignItems:'center', gap:6, color:'var(--success, #16a34a)', fontWeight:700, fontSize:12, textTransform:'uppercase', letterSpacing:'0.04em'}}>
                  <Icon name="check-circle" style={{width:14, height:14}} /> Ajouté au panier
                </div>
                <div style={{fontWeight:700, fontSize:15, lineHeight:1.3, marginTop:2, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap'}}>{detected.name}</div>
                <div className="muted" style={{fontSize:12, marginTop:2}}>{detected.format} · <strong style={{color:'var(--vival-red)'}}>{formatPrice(detected.price).full}</strong></div>
              </div>
            </div>
          </div>
        )}
      </div>
      <div style={{marginTop:12, display:'flex', gap:8, alignItems:'center'}}>
        <input
          type="text"
          inputMode="numeric"
          value={manual}
          onChange={e => setManual(e.target.value.replace(/\D/g, ''))}
          onKeyDown={e => e.key === 'Enter' && submitManual()}
          placeholder="Saisir le code à la main (EAN 8-14 chiffres)"
          style={{flex:1, padding:'10px 12px', border:'1px solid var(--line-1)', borderRadius:8, fontSize:14}}
        />
        <Button variant="outline" onClick={submitManual} disabled={manual.length < 8}>Valider</Button>
      </div>
      <p className="muted" style={{fontSize:12, marginTop:8, textAlign:'center'}}>
        {error ? '' : 'Pointez la caméra vers le code-barres — scan continu, pas besoin de cliquer'}
      </p>
    </Modal>
  );
}

// ===== Customer picker modal =====
function CustomerPickerModal() {
  const { customerPicker, setCustomerPicker, setCustomer, showToast, orders } = useApp();
  const [q, setQ] = useState('');
  const [creating, setCreating] = useState(false);
  if (!customerPicker && !creating) return null;
  const list = window.VivalData.CUSTOMERS.filter(c => !q || c.name.toLowerCase().includes(q.toLowerCase()) || (c.phone || '').includes(q));
  const lastOrderOf = (cid) => orders.filter(o => o.customerId === cid).sort((a,b) => new Date(b.createdAt) - new Date(a.createdAt))[0];

  if (creating) {
    return <CustomerEditModal
      customer={{ name: q.trim(), phone: '', streetNumber: '', street: '', postalCode: '', city: '', notes: '' }}
      onClose={() => setCreating(false)}
      onSaved={() => {
        setCreating(false);
        setCustomerPicker(false);
        const latest = window.VivalData.CUSTOMERS[0];
        if (latest) setCustomer(latest);
        showToast('Client créé et associé');
      }} />;
  }

  return (
    <Modal open={true} onClose={() => setCustomerPicker(false)} title="Associer un client"
      footer={<>
        <Button variant="ghost" onClick={() => setCustomerPicker(false)}>Annuler</Button>
        <Button variant="outline" icon="plus" onClick={() => setCreating(true)}>Nouveau</Button>
      </>}
    >
      <Input icon="search" autoFocus placeholder="Nom ou téléphone…" value={q} onChange={e => setQ(e.target.value)} />
      <div style={{marginTop:12, maxHeight:360, overflowY:'auto', borderTop:'1px solid var(--line-1)'}}>
        {list.map(c => {
          const last = lastOrderOf(c.id);
          const count = orders.filter(o => o.customerId === c.id).length;
          return (
            <div key={c.id} className="customer-row" onClick={() => { setCustomer(c); setCustomerPicker(false); }}>
              <div className="customer-avatar">{(c.name.split(' ').slice(-1)[0] || '?')[0]}</div>
              <div style={{flex:1, minWidth:0}}>
                <div style={{fontWeight:700}}>{c.name}</div>
                <div className="muted" style={{fontSize:12}}>{c.phone || '—'} · {count} cmde{count>1?'s':''}{c.loyaltyPoints ? ` · ${c.loyaltyPoints} pts fidélité` : ''}</div>
                {last && <div className="muted" style={{fontSize:11, marginTop:2}}>Dernière : {last.id} · {formatPrice(last.totalTtc || 0).full} · {new Date(last.createdAt).toLocaleDateString('fr-FR')}</div>}
              </div>
              <Icon name="chevron-right" style={{color:'var(--ink-4)'}} />
            </div>
          );
        })}
        {list.length === 0 && (
          <div style={{padding:24, textAlign:'center'}} className="muted">
            Aucun client ne correspond.{q.trim() && <> Clique <strong>Nouveau</strong> pour créer « {q} ».</>}
          </div>
        )}
      </div>
    </Modal>
  );
}

// ===== Print A4 bon de livraison =====
function PrintScreen() {
  const { printOrder, setPrintOrder, setRoute } = useApp();
  const o = printOrder;
  if (!o) return <div className="empty-state"><Icon name="printer" /><div>Aucun bon à imprimer</div></div>;
  const customer = CUSTOMERS.find(c => c.id === o.customerId);
  const total = o.lines.reduce((s, l) => {
    if (l.manual) return s + (Number(l.unitPrice ?? l.price) || 0) * l.qty;
    const p = PRODUCTS.find(x => x.id === l.pid);
    return s + (p ? p.price * l.qty : 0);
  }, 0);
  const d = new Date(o.createdAt);

  return (
    <>
      <div className="page-header">
        <Button variant="ghost" icon="chevron-left" onClick={() => setRoute('orders')}>Retour</Button>
        <div className="page-header-grow">
          <h1 className="page-title" style={{fontSize:20}}>Bon de livraison {o.id}</h1>
        </div>
        <Button variant="outline" icon="download" onClick={() => window.print()}>Télécharger PDF</Button>
        <Button variant="primary" icon="printer" onClick={() => window.print()}>Imprimer</Button>
      </div>
      <div className={"print-doc" + ((() => { const f = localStorage.getItem('vival_print_format'); return f === '80mm' ? ' ticket-80' : f === '58mm' ? ' ticket-58' : ''; })())}>
        {(() => { const store = getStoreInfo(); return (
        <div className="bl-header">
          <div style={{display:'flex', gap:16, alignItems:'center'}}>
            <div className="bl-logo"><img src="assets/vival-logo.png" alt="Vival" style={{height:40}} /></div>
            <div>
              <h2>{store.name}</h2>
              <div style={{fontSize:12, color:'#555', marginTop:2}}>{store.address} · {store.phone}</div>
            </div>
          </div>
          <div className="bl-meta" style={{display:'flex', gap:16, alignItems:'flex-start'}}>
            <div>
              <div style={{fontSize:11, textTransform:'uppercase', color:'#888', letterSpacing:'0.05em', fontWeight:700}}>Bon de livraison</div>
              <div style={{fontSize:18, fontWeight:800, fontFamily:'ui-monospace, monospace'}}>{o.id}</div>
              <div style={{fontSize:12, color:'#555', marginTop:4}}>{d.toLocaleDateString('fr-FR', {weekday:'long', day:'numeric', month:'long', year:'numeric'})}</div>
              <div style={{fontSize:12, color:'#555'}}>{d.toLocaleTimeString('fr-FR', {hour:'2-digit', minute:'2-digit'})}</div>
            </div>
            <img alt={`QR ${o.id}`} src={`https://api.qrserver.com/v1/create-qr-code/?size=80x80&margin=0&data=${encodeURIComponent(o.id)}`} style={{width:80, height:80}} />
          </div>
        </div>
        ); })()}

        <div style={{display:'grid', gridTemplateColumns:'1fr 1fr', gap:24, marginBottom:16}}>
          <div>
            <div style={{fontSize:11, textTransform:'uppercase', color:'#888', letterSpacing:'0.05em', fontWeight:700, marginBottom:4}}>Livré à</div>
            <div style={{fontWeight:700, fontSize:15}}>{customer?.name || o.customerName || 'Client libre'}</div>
            <div style={{color:'#333', fontSize:13, marginTop:2}}>{customer?.phone}</div>
            <div style={{color:'#333', fontSize:13}}>{customer?.address}</div>
            {customer?.notes && <div style={{color:'#b45309', fontSize:12, marginTop:6, fontStyle:'italic'}}>⚑ {customer.notes}</div>}
          </div>
          <div>
            <div style={{fontSize:11, textTransform:'uppercase', color:'#888', letterSpacing:'0.05em', fontWeight:700, marginBottom:4}}>Paiement</div>
            <div style={{fontWeight:700, fontSize:15, textTransform:'capitalize'}}>{o.payment}</div>
            <div style={{color:'#333', fontSize:13, marginTop:2}}>Statut : {o.status}</div>
          </div>
        </div>

        <table>
          <thead>
            <tr>
              <th>Produit</th>
              <th style={{textAlign:'right'}}>Qté</th>
              <th style={{textAlign:'right'}}>P.U.</th>
              <th style={{textAlign:'right'}}>Total</th>
            </tr>
          </thead>
          <tbody>
            {o.lines.map(l => {
              if (l.manual) {
                const unit = Number(l.unitPrice ?? l.price) || 0;
                return (
                  <tr key={l.pid}>
                    <td><div style={{fontWeight:600}}>{l.name}</div><div style={{color:'#777', fontSize:11}}>Ligne libre</div></td>
                    <td className="num">{l.qty}</td>
                    <td className="num">{formatPrice(unit).full}</td>
                    <td className="num" style={{fontWeight:700}}>{formatPrice(unit * l.qty).full}</td>
                  </tr>
                );
              }
              const p = PRODUCTS.find(x => x.id === l.pid);
              if (!p) return null;
              return (
                <tr key={l.pid}>
                  <td><div style={{fontWeight:600}}>{p.name}</div><div style={{color:'#777', fontSize:11}}>{p.format}</div></td>
                  <td className="num">{p.byWeight ? `${l.qty.toFixed(2)} kg` : l.qty}</td>
                  <td className="num">{formatPrice(p.price).full}</td>
                  <td className="num" style={{fontWeight:700}}>{formatPrice(p.price * l.qty).full}</td>
                </tr>
              );
            })}
          </tbody>
        </table>

        <BonLivraisonTotals lines={o.lines} discountPct={o.discountPct || 0} />


        <div style={{marginTop:48, display:'grid', gridTemplateColumns:'1fr 1fr', gap:32, fontSize:11, color:'#555'}}>
          <div>
            <div style={{height:60}}></div>
            <div style={{borderTop:'1px solid #ccc', paddingTop:8, fontWeight:600}}>Signature gérant</div>
          </div>
          <div>
            {o.signature
              ? <img src={o.signature} alt="Signature client" style={{height:60, maxWidth:'100%', objectFit:'contain', display:'block'}} />
              : <div style={{height:60}}></div>}
            <div style={{borderTop:'1px solid #ccc', paddingTop:8, fontWeight:600}}>
              Signature client {o.deliveredAt && <span style={{fontWeight:400, color:'#888'}}>· {new Date(o.deliveredAt).toLocaleString('fr-FR')}</span>}
            </div>
          </div>
        </div>
        {(() => { const s = getStoreInfo(); return (s.siret || s.tvaNumber) ? (
          <div style={{marginTop:24, paddingTop:12, borderTop:'1px solid #ddd', textAlign:'center', fontSize:10, color:'#666'}}>
            {s.name}{s.siret ? ` · SIRET ${s.siret}` : ''}{s.tvaNumber ? ` · TVA ${s.tvaNumber}` : ''}
          </div>
        ) : null; })()}
        <div style={{marginTop:16, textAlign:'center', fontSize:10, color:'#999'}}>
          Merci de votre fidélité — Vival, votre proximité au quotidien
        </div>
      </div>
    </>
  );
}

// ===== Tweaks panel =====
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "cartLayout": "lateral",
  "density": "confortable",
  "theme": "light",
  "cardStyle": "grid",
  "redVariant": "saturated"
}/*EDITMODE-END*/;

function Tweaks({ open, values, onChange }) {
  return (
    <div className={`tweaks ${open ? 'open' : ''}`}>
      <div className="tweaks-header">
        <Icon name="settings" />
        <div className="tweaks-title">Tweaks</div>
      </div>
      <div className="tweaks-body">
        <div className="tweak">
          <div className="tweak-label">Densité</div>
          <div className="segmented" style={{width:'100%'}}>
            <button style={{flex:1}} aria-pressed={values.density==='confortable'} onClick={() => onChange({density:'confortable'})}>Confort</button>
            <button style={{flex:1}} aria-pressed={values.density==='compact'} onClick={() => onChange({density:'compact'})}>Compact</button>
          </div>
        </div>
        <div className="tweak">
          <div className="tweak-label">Thème</div>
          <div className="segmented" style={{width:'100%'}}>
            <button style={{flex:1}} aria-pressed={values.theme==='light'} onClick={() => onChange({theme:'light'})}>Clair</button>
            <button style={{flex:1}} aria-pressed={values.theme==='dark'} onClick={() => onChange({theme:'dark'})}>Sombre</button>
          </div>
        </div>
        <div className="tweak">
          <div className="tweak-label">Carte produit</div>
          <div className="segmented" style={{width:'100%'}}>
            <button style={{flex:1}} aria-pressed={values.cardStyle==='grid'} onClick={() => onChange({cardStyle:'grid'})}>Grille photo</button>
            <button style={{flex:1}} aria-pressed={values.cardStyle==='compact'} onClick={() => onChange({cardStyle:'compact'})}>Liste compacte</button>
          </div>
        </div>
        <div className="tweak">
          <div className="tweak-label">Nuance du rouge</div>
          <div className="segmented" style={{width:'100%'}}>
            <button style={{flex:1}} aria-pressed={values.redVariant==='saturated'} onClick={() => onChange({redVariant:'saturated'})}>#d10a11</button>
            <button style={{flex:1}} aria-pressed={values.redVariant==='soft'} onClick={() => onChange({redVariant:'soft'})}>Désaturé</button>
          </div>
        </div>
      </div>
    </div>
  );
}

// ===== Toast =====
function Toast({ msg }) {
  if (!msg) return null;
  return (
    <div style={{
      position:'fixed', bottom:24, left:'50%', transform:'translateX(-50%)',
      background:'var(--ink-1)', color:'var(--surface-0)', padding:'10px 16px',
      borderRadius:'var(--r-pill)', fontSize:13, fontWeight:600, zIndex:200,
      boxShadow:'var(--shadow-lg)', display:'flex', alignItems:'center', gap:8
    }}>
      <Icon name="check-circle" /> {msg}
    </div>
  );
}

// ========== Tour guidé (onboarding) ==========
const TOUR_STEPS = [
  {
    id: 'welcome',
    centered: true,
    title: 'Bienvenue sur MonVival 👋',
    body: "Je vais te faire un petit tour de l'application en 2 minutes. Tu peux fermer le tour à tout moment en cliquant sur ×, et le relancer dans Réglages → Compte.",
  },
  {
    id: 'dashboard',
    selector: '[data-tour="nav-dashboard"]',
    title: 'Accueil',
    body: "Vue du jour : chiffre d'affaires, panier moyen, commandes en cours. Le tableau de bord rapide du matin.",
  },
  {
    id: 'catalog',
    selector: '[data-tour="nav-catalog"]',
    title: 'Vente / Caisse',
    body: "Ici tu scannes un produit (code-barres) ou cherches par nom, puis tu encaisses le client. Les ventes alimentent automatiquement les rapports.",
  },
  {
    id: 'orders',
    selector: '[data-tour="nav-orders"]',
    title: 'Commandes',
    body: "Toutes les commandes en cours et passées. Tu peux voir le détail de chaque ticket, le réimprimer, ou suivre une livraison.",
  },
  {
    id: 'customers',
    selector: '[data-tour="nav-customers"]',
    title: 'Clients',
    body: "Fiches clients avec historique des achats, points de fidélité et ardoise (crédit). Rattache un client à une commande pour suivre son encours.",
  },
  {
    id: 'temperatures',
    selector: '[data-tour="nav-temperatures"]',
    title: 'Températures HACCP',
    body: "Relevés matin + après-midi de tes frigos, surgelés, meuble coupe. Pavé tactile rapide, signature du preneur et du gérant, export PDF mensuel pour le contrôle sanitaire.",
  },
  {
    id: 'admin-catalog',
    selector: '[data-tour="nav-admin-catalog"]',
    title: 'Catalogue produits',
    body: "Tous tes produits : prix, prix d'achat, stock, famille. Active/désactive un produit pour le masquer de la vente sans le supprimer.",
  },
  {
    id: 'reports',
    selector: '[data-tour="nav-reports"]',
    title: 'Rapports',
    body: "Le cœur de l'analyse : CA, marges, top produits, alertes, budget. 7 onglets à explorer.",
    awaitClick: true,
    hint: 'Clique sur « Rapports » pour continuer',
  },
  {
    id: 'tab-period',
    selector: '[data-tour-tab="period"]',
    title: 'Rapports → Par période',
    body: "CA et marge du jour, de la semaine, du mois. Compare aussi avec la période précédente ET l'année dernière (N-1).",
  },
  {
    id: 'tab-history',
    selector: '[data-tour-tab="history"]',
    title: 'Historique des imports',
    body: "Tous les imports caisse (PDF PLU) que tu as enregistrés. Tu peux en consulter ou en supprimer un s'il y a eu une erreur.",
  },
  {
    id: 'tab-families',
    selector: '[data-tour-tab="families"]',
    title: 'Familles Casino',
    body: "Les 190 familles Casino extraites du PDF d'inventaire. Tu peux compléter les libellés tronqués et affecter le bon rayon.",
  },
  {
    id: 'tab-fl',
    selector: '[data-tour-tab="fl"]',
    title: 'Marges rayon',
    body: "Calcul EXACT de marge pour Fruits & Légumes, Boulangerie, Boucherie, Fromage à la coupe. Saisis tes factures + stock + casse et tu as la vraie marge du mois.",
  },
  {
    id: 'tab-topflop',
    selector: '[data-tour-tab="topflop"]',
    title: 'Top / Flop',
    body: "Tes meilleurs vendeurs, les produits qui dorment (stock dormant = argent immobilisé) et ceux vendus à perte.",
  },
  {
    id: 'tab-budget',
    selector: '[data-tour-tab="budget"]',
    title: 'Budget',
    body: "Fixe un objectif de CA pour le mois, l'app te montre où tu en es et te projette une fin de mois basée sur la tendance.",
  },
  {
    id: 'tab-alerts',
    selector: '[data-tour-tab="alerts"]',
    title: 'Alertes',
    body: "Notifications automatiques : ruptures de stock, marges négatives, familles à valider. À vérifier chaque matin.",
  },
  {
    id: 'settings',
    selector: '[data-tour="nav-settings"]',
    title: 'Réglages',
    body: "Infos magasin, utilisateurs, impression, logo. Le bouton pour relancer ce tour est dans la section Compte.",
  },
  {
    id: 'end',
    centered: true,
    title: "C'est parti ! 🚀",
    body: "Tu peux relancer ce tour à tout moment depuis Réglages → Compte → « Relancer le tour guidé ». Bon business !",
  },
];

function TourOverlay() {
  const app = useApp();
  const { tourActive, tourStepIndex, stopTour, nextStep, prevStep } = app;
  const [rect, setRect] = useState(null);
  const [bubblePos, setBubblePos] = useState(null);
  const bubbleRef = useRef(null);

  const step = TOUR_STEPS[tourStepIndex];
  const isLast = tourStepIndex >= TOUR_STEPS.length - 1;
  const isFirst = tourStepIndex === 0;

  // Recalcule position de l'élément cible
  const computePosition = () => {
    if (!step || step.centered || !step.selector) { setRect(null); return; }
    const els = [...document.querySelectorAll(step.selector)];
    const el = els.find(e => e.offsetParent !== null) || els[0];
    if (!el) { setRect(null); return; }
    el.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'center' });
    const r = el.getBoundingClientRect();
    setRect({ top: r.top, left: r.left, width: r.width, height: r.height });
  };

  // À chaque changement de step : recalc avec retry si cible pas encore montée
  useEffect(() => {
    if (!tourActive || !step) return;
    let retries = 0;
    const tryFind = () => {
      if (step.centered || !step.selector) { setRect(null); return; }
      const els = [...document.querySelectorAll(step.selector)];
      const el = els.find(e => e.offsetParent !== null) || els[0];
      if (el) { computePosition(); }
      else if (retries < 10) { retries++; setTimeout(tryFind, 50); }
      else { setRect(null); }
    };
    tryFind();
  }, [tourActive, tourStepIndex]);

  // Listener resize/scroll
  useEffect(() => {
    if (!tourActive) return;
    let raf;
    const onChange = () => { cancelAnimationFrame(raf); raf = requestAnimationFrame(computePosition); };
    window.addEventListener('resize', onChange);
    window.addEventListener('scroll', onChange, true);
    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener('resize', onChange);
      window.removeEventListener('scroll', onChange, true);
    };
  }, [tourActive, tourStepIndex]);

  // Si étape attend un clic, passe à la suivante au clic sur la cible
  useEffect(() => {
    if (!tourActive || !step || !step.awaitClick || !step.selector) return;
    const els = [...document.querySelectorAll(step.selector)];
    const el = els.find(e => e.offsetParent !== null);
    if (!el) return;
    const handler = () => setTimeout(() => nextStep(), 150); // laisse le re-render
    el.addEventListener('click', handler, { once: true });
    return () => el.removeEventListener('click', handler);
  }, [tourActive, tourStepIndex, rect]);

  // Esc pour fermer
  useEffect(() => {
    if (!tourActive) return;
    const onKey = (e) => { if (e.key === 'Escape') stopTour(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [tourActive]);

  // Positionne la bulle après rendu (quand rect change)
  useEffect(() => {
    if (!tourActive || !step) return;
    if (step.centered || !rect) { setBubblePos(null); return; }
    // Calcule position après que la bulle soit rendue
    const t = setTimeout(() => {
      const b = bubbleRef.current;
      if (!b) return;
      const BW = b.offsetWidth, BH = b.offsetHeight;
      const vw = window.innerWidth, vh = window.innerHeight;
      const M = 12;
      const slots = {
        bottom: { top: rect.top + rect.height + M, left: rect.left + rect.width/2 - BW/2 },
        top:    { top: rect.top - BH - M, left: rect.left + rect.width/2 - BW/2 },
        right:  { top: rect.top + rect.height/2 - BH/2, left: rect.left + rect.width + M },
        left:   { top: rect.top + rect.height/2 - BH/2, left: rect.left - BW - M },
      };
      const order = ['bottom', 'right', 'top', 'left'];
      let chosen = null;
      for (const k of order) {
        const p = slots[k];
        if (p.top >= 8 && p.left >= 8 && p.top + BH <= vh - 8 && p.left + BW <= vw - 8) { chosen = p; break; }
      }
      if (!chosen) chosen = slots.bottom;
      // Clamp
      chosen.top = Math.max(8, Math.min(vh - BH - 8, chosen.top));
      chosen.left = Math.max(8, Math.min(vw - BW - 8, chosen.left));
      setBubblePos(chosen);
    }, 20);
    return () => clearTimeout(t);
  }, [rect, tourActive, tourStepIndex]);

  if (!tourActive || !step) return null;

  const overlayContent = (
    <div className="tour-overlay">
      {/* Backdrop cliquable — masqué pendant awaitClick pour que le clic atteigne la cible */}
      {!step.awaitClick && (
        <div className="tour-backdrop-click" onClick={stopTour} />
      )}

      {/* Trou de spotlight */}
      {rect && !step.centered && (
        <div className="tour-hole pulse" style={{
          top: rect.top - 6, left: rect.left - 6,
          width: rect.width + 12, height: rect.height + 12,
          pointerEvents: step.awaitClick ? 'none' : undefined,
        }} />
      )}

      {/* Bulle */}
      <div ref={bubbleRef}
        className={"tour-bubble" + (step.centered || !rect ? " centered" : "")}
        style={bubblePos && !step.centered ? { top: bubblePos.top, left: bubblePos.left } : undefined}
        onClick={e => e.stopPropagation()}>
        <div style={{display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom:8}}>
          <span style={{fontSize:11, fontWeight:600, color:'var(--ink-3)', textTransform:'uppercase', letterSpacing:'0.05em'}}>
            Étape {tourStepIndex + 1} / {TOUR_STEPS.length}
          </span>
          <button onClick={stopTour} title="Fermer le tour"
            style={{background:'transparent', border:0, cursor:'pointer', fontSize:18, color:'var(--ink-3)', padding:0, lineHeight:1}}>×</button>
        </div>
        <h3 style={{margin:'0 0 8px', fontSize:16, fontWeight:800, color:'var(--ink-1)'}}>{step.title}</h3>
        <p style={{margin:'0 0 12px', fontSize:13, lineHeight:1.5, color:'var(--ink-2)'}}>{step.body}</p>
        {step.hint && (
          <div style={{marginBottom:12, padding:'8px 10px', background:'#fef3c7', borderRadius:6, fontSize:12, color:'#92400e', fontWeight:600}}>
            {step.hint}
          </div>
        )}
        {/* Barre progression */}
        <div style={{height:4, background:'var(--line-1, #e5e7eb)', borderRadius:2, marginBottom:12, overflow:'hidden'}}>
          <div style={{height:'100%', width: `${((tourStepIndex + 1) / TOUR_STEPS.length) * 100}%`, background:'var(--vival-red)', transition:'width 0.3s'}} />
        </div>
        <div style={{display:'flex', gap:8, justifyContent:'space-between'}}>
          <button onClick={stopTour}
            style={{background:'transparent', border:'1px solid var(--line-1)', borderRadius:6, padding:'6px 10px', cursor:'pointer', fontSize:12, color:'var(--ink-3)'}}>
            Passer
          </button>
          <div style={{display:'flex', gap:6}}>
            {!isFirst && (
              <button onClick={prevStep}
                style={{background:'white', border:'1px solid var(--line-1)', borderRadius:6, padding:'6px 12px', cursor:'pointer', fontSize:12, fontWeight:600}}>
                ← Précédent
              </button>
            )}
            <button onClick={isLast ? stopTour : nextStep}
              style={{background:'var(--vival-red)', border:0, borderRadius:6, padding:'6px 14px', cursor:'pointer', fontSize:12, fontWeight:700, color:'white'}}>
              {isLast ? 'Terminer ✓' : 'Suivant →'}
            </button>
          </div>
        </div>
      </div>
    </div>
  );

  return ReactDOM.createPortal(overlayContent, document.body);
}

// ===== Root =====
function App() {
  const app = useApp();
  const { route, cartDrawer, setCartDrawer, toast } = app;

  // Track offline status reactively (VivalData._offline change apres hydrateFromSupabase)
  const [isOffline, setIsOffline] = useState(() => !!window.VivalData?._offline);
  useEffect(() => {
    const sync = () => setIsOffline(!!window.VivalData?._offline);
    sync();
    window.addEventListener('vival-data-refreshed', sync);
    window.addEventListener('online', sync);
    window.addEventListener('offline', sync);
    return () => {
      window.removeEventListener('vival-data-refreshed', sync);
      window.removeEventListener('online', sync);
      window.removeEventListener('offline', sync);
    };
  }, []);

  const [tweaksOpen, setTweaksOpen] = useState(false);
  const [tweaks, setTweaks] = useState(TWEAK_DEFAULTS);
  const setTweak = (partial) => {
    const next = { ...tweaks, ...partial };
    setTweaks(next);
    try { window.parent.postMessage({type:'__edit_mode_set_keys', edits: partial}, '*'); } catch(e){}
  };

  useEffect(() => {
    const handler = (e) => {
      if (e.data?.type === '__activate_edit_mode') setTweaksOpen(true);
      if (e.data?.type === '__deactivate_edit_mode') setTweaksOpen(false);
    };
    window.addEventListener('message', handler);
    try { window.parent.postMessage({type:'__edit_mode_available'}, '*'); } catch(e){}
    return () => window.removeEventListener('message', handler);
  }, []);

  // Tour guidé : auto-déclenchement à la 1re connexion
  useEffect(() => {
    try {
      if (localStorage.getItem('vival_tour_done_v1') !== '1') {
        const t = setTimeout(() => app.startTour(), 800);
        return () => clearTimeout(t);
      }
    } catch {}
  }, []);

  useEffect(() => {
    document.documentElement.setAttribute('data-theme', tweaks.theme || 'light');
    document.documentElement.setAttribute('data-density', tweaks.density || 'confortable');
    document.documentElement.setAttribute('data-pcard', tweaks.cardStyle || 'grid');
    document.documentElement.setAttribute('data-red', tweaks.redVariant || 'saturated');
  }, [tweaks]);

  // Applique le branding custom du store (logo + couleur primaire)
  useEffect(() => {
    (async () => {
      if (!VivalData._storeId) return;
      const { data } = await window.sb.from('stores').select('logo_url, primary_color').eq('id', VivalData._storeId).maybeSingle();
      if (data?.primary_color) document.documentElement.style.setProperty('--vival-red', data.primary_color);
      if (data?.logo_url) VivalData._customLogo = data.logo_url;
    })();
  }, []);

  // Polling : nouvelles commandes en ligne toutes les 20s
  useEffect(() => {
    const known = new Set(app.orders.filter(o => o.source === 'online').map(o => o.id));
    const tick = async () => {
      try {
        const { data } = await window.sb.from('orders').select('*').eq('source', 'online').order('created_at', { ascending: false }).limit(20);
        const { data: lines } = await window.sb.from('order_lines').select('*').in('order_id', (data || []).map(o => o.id));
        const byId = {};
        (lines || []).forEach(l => { (byId[l.order_id] ||= []).push(l); });
        const fresh = (data || []).filter(o => !known.has(o.id));
        if (fresh.length) {
          const mapped = fresh.map(o => window.VivalData && (function(){
            // Inline mapping (même logique que dbToOrder)
            return {
              id: o.id, customerId: o.customer_id, customerName: o.customer_name,
              status: o.status, payment: o.payment, totalTtc: Number(o.total_ttc)||0,
              totalHt: Number(o.total_ht)||0, totalTva: Number(o.total_tva)||0,
              discountPct: Number(o.discount_pct)||0, discountAmount: Number(o.discount_amount)||0,
              notes: o.notes || '', signature: o.signature, deliveredAt: o.delivered_at,
              cashierId: o.cashier_id, cashierName: o.cashier_name,
              source: o.source || 'counter', deliveryMode: o.delivery_mode,
              deliveryAddress: o.delivery_address, deliveryFee: Number(o.delivery_fee)||0,
              customerEmail: o.customer_email, customerPhone: o.customer_phone,
              createdAt: o.created_at,
              lines: (byId[o.id] || []).map(l => ({ pid: l.product_id, qty: Number(l.qty), unitPrice: Number(l.unit_price), tvaRate: Number(l.tva_rate), name: l.product_name, brand: l.product_brand, format: l.product_format })),
            };
          })());
          app.setOrders(prev => [...mapped, ...prev.filter(p => !mapped.find(m => m.id === p.id))]);
          fresh.forEach(o => known.add(o.id));
          app.showToast(`${fresh.length} nouvelle${fresh.length>1?'s':''} commande${fresh.length>1?'s':''} en ligne`);
          // Bip sonore simple
          try { new Audio('data:audio/wav;base64,UklGRjIAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAAAAAA==').play().catch(()=>{}); } catch {}
        }
      } catch {}
    };
    const id = setInterval(tick, 20000);
    return () => clearInterval(id);
  }, []);

  // Bluetooth barcode scanner: detects rapid consecutive keypresses + Enter
  useEffect(() => {
    let buffer = '';
    let lastKeyTime = 0;
    const SCANNER_THRESHOLD_MS = 50; // BT scanners type much faster than humans

    const onKey = (e) => {
      const tag = e.target.tagName;
      const isTyping = (tag === 'INPUT' || tag === 'TEXTAREA') && buffer.length < 4;
      if (isTyping) return;

      const now = Date.now();
      if (e.key === 'Enter') {
        if (buffer.length >= 4) {
          e.preventDefault();
          const code = normalizeEan(buffer); // 12 chiffres -> 13 chiffres auto
          buffer = '';
          app.notifyScan();
          if (app.pendingScanRef?.current) {
            app.pendingScanRef.current(code);
            return;
          }
          const product = PRODUCTS.find(p => p.barcode === code);
          if (product) {
            app.addToCart(product.id);
          } else {
            app.setUnknownBarcode(code);
          }
        }
        buffer = '';
        return;
      }
      if (e.key.length === 1 && /[\w-]/.test(e.key)) {
        if (now - lastKeyTime > 500) buffer = '';
        if (now - lastKeyTime < SCANNER_THRESHOLD_MS || buffer === '') {
          buffer += e.key;
        } else {
          buffer = e.key;
        }
        lastKeyTime = now;
      }
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [app]);

  return (
    <div className="app" data-screen-label={route}>
      {isOffline && (
        <div style={{position:'fixed', top:0, left:0, right:0, zIndex:150, padding:'6px 16px', background:'#fef3c7', borderBottom:'1px solid #d97706', fontSize:12, textAlign:'center', color:'#92400e', fontWeight:600}}>
          Mode hors ligne — données du {VivalData._offlineSince ? new Date(VivalData._offlineSince).toLocaleString('fr-FR') : 'cache local'}. Les modifications ne seront pas synchronisées.
        </div>
      )}
      <SubscriptionBanner />
      <CookieBanner />

      <Sidebar />
      <Topbar />
      <main className="app-main">
        {route === 'dashboard' && <DashboardScreen />}
        {route === 'catalog' && <CatalogScreen />}
        {route === 'orders' && <OrdersScreen />}
        {route === 'delivery' && <DeliveryScreen />}
        {route === 'customers' && <CustomersScreen />}
        {route === 'temperatures' && <TemperaturesScreen />}
        {route === 'admin-catalog' && <AdminCatalogScreen />}
        {route === 'reports' && <ReportsScreen />}
        {route === 'marketplace' && <MarketplaceScreen />}
        {route === 'settings' && <SettingsScreen />}
        {route === 'print' && <PrintScreen />}
      </main>
      <BottomNav />

      {cartDrawer && <Drawer open={true} onClose={() => setCartDrawer(false)}><CartPanel inDrawer /></Drawer>}
      <ProductDetailModal />
      <ScanModal />
      <UnknownProductModal />
      <CustomerPickerModal />
      <OrderDetailModal />
      <ReceivingModal />
      <Tweaks open={tweaksOpen} values={tweaks} onChange={setTweak} />
      <Toast msg={toast} />
      <TourOverlay />
      <TemperatureAlertBanner />
    </div>
  );
}

function Gate() {
  const [authed, setAuthed] = useState(() => sessionStorage.getItem('vival-auth') === '1');
  if (!authed) return <PinLogin onSuccess={() => { sessionStorage.setItem('vival-auth','1'); setAuthed(true); }} />;
  return <AppProvider><App /></AppProvider>;
}

function LoadingSplash({ error, onRetry }) {
  return (
    <div style={{minHeight:'100vh', display:'flex', alignItems:'center', justifyContent:'center', background:'var(--surface-1)'}}>
      <div style={{textAlign:'center', padding:40}}>
        <img src="/assets/vival-logo.png" alt="Vival" style={{height:72, width:'auto', margin:'0 auto 20px', display:'block'}} />
        {error ? (
          <>
            <h2 style={{marginBottom:8}}>Connexion à la base impossible</h2>
            <p className="muted" style={{fontSize:14, marginBottom:16, maxWidth:400}}>{error.message || String(error)}</p>
            <button onClick={onRetry} style={{background:'var(--vival-red)', color:'#fff', border:0, padding:'10px 20px', borderRadius:8, fontSize:14, fontWeight:600, cursor:'pointer'}}>Réessayer</button>
          </>
        ) : (
          <>
            <h2 style={{marginBottom:8}}>Chargement du catalogue…</h2>
            <p className="muted" style={{fontSize:13}}>Synchronisation avec la base de données</p>
          </>
        )}
      </div>
    </div>
  );
}

// ===== PAGE D'ACCUEIL PUBLIQUE (store finder) =====
function Homepage() {
  const [query, setQuery] = useState('');
  const [stores, setStores] = useState([]);
  const [loading, setLoading] = useState(false);
  const [geoState, setGeoState] = useState('idle'); // idle | loading | ok | error
  const [position, setPosition] = useState(null);
  const [searched, setSearched] = useState(false);

  const search = async (q = query, coords = position) => {
    setLoading(true); setSearched(true);
    const { data } = await window.sb.rpc('public_search_stores', {
      p_query: q || null, p_lat: coords?.lat || null, p_lng: coords?.lng || null, p_limit: 30,
    });
    setStores(data || []);
    setLoading(false);
  };

  const useMyLocation = () => {
    if (!navigator.geolocation) { setGeoState('error'); return; }
    setGeoState('loading');
    navigator.geolocation.getCurrentPosition(
      (pos) => {
        const c = { lat: pos.coords.latitude, lng: pos.coords.longitude };
        setPosition(c); setGeoState('ok');
        search(query, c);
      },
      () => setGeoState('error'),
      { timeout: 10000, maximumAge: 300000 }
    );
  };

  useEffect(() => { search('', null); }, []);

  return (
    <div style={{minHeight:'100vh', background:'#f9fafb', fontFamily:'inherit'}}>
      <header style={{background:'#fff', borderBottom:'1px solid var(--line-1)', padding:'14px 20px', position:'sticky', top:0, zIndex:10}}>
        <div style={{maxWidth:1100, margin:'0 auto', display:'flex', alignItems:'center', gap:12}}>
          <img src="/assets/vival-logo.png" alt="Vival" style={{height:32}} />
          <div style={{fontWeight:800, fontSize:16, color:'var(--ink-1)'}}>MonVival</div>
          <div style={{flex:1}} />
          <a href="/suivi" style={{color:'var(--ink-3)', fontSize:13, textDecoration:'none'}}>Suivre ma commande</a>
          <a href="/app" style={{color:'var(--ink-3)', fontSize:13, textDecoration:'none'}}>Espace magasin</a>
        </div>
      </header>

      <section className="home-hero" style={{background:'linear-gradient(180deg, #fff 0%, #f9fafb 100%)', padding:'48px 20px 24px'}}>
        <div style={{maxWidth:720, margin:'0 auto', textAlign:'center'}}>
          <h1 style={{fontSize:'clamp(24px, 4vw, 36px)', fontWeight:800, margin:'0 0 12px', letterSpacing:'-0.02em'}}>
            Vos courses de proximité,<br/>commandées en un clic
          </h1>
          <p className="muted" style={{fontSize:15, margin:'0 0 24px'}}>
            Trouvez votre magasin Vival le plus proche et commandez en ligne. Retrait en magasin ou livraison à domicile.
          </p>
          <form onSubmit={(e) => { e.preventDefault(); search(); }}
            className="home-search-form"
            style={{display:'flex', gap:8, background:'#fff', border:'1px solid var(--line-2)', borderRadius:12, padding:6, boxShadow:'0 4px 16px rgba(0,0,0,0.04)'}}>
            <input type="text" value={query} onChange={e => setQuery(e.target.value)}
              placeholder="Code postal ou ville"
              style={{flex:1, border:0, padding:'12px 14px', fontSize:15, outline:'none', background:'transparent'}} />
            <button type="button" onClick={useMyLocation} title="Ma position"
              style={{background:'var(--surface-1)', border:0, borderRadius:8, padding:'10px 14px', cursor:'pointer', display:'flex', alignItems:'center', gap:4, fontSize:13}}>
              <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="4"/><line x1="12" y1="2" x2="12" y2="4"/><line x1="12" y1="20" x2="12" y2="22"/><line x1="2" y1="12" x2="4" y2="12"/><line x1="20" y1="12" x2="22" y2="12"/></svg>
              <span className="muted">Position</span>
            </button>
            <button type="submit" style={{background:'var(--vival-red)', color:'#fff', border:0, borderRadius:8, padding:'10px 20px', fontWeight:700, cursor:'pointer'}}>Rechercher</button>
          </form>
          {geoState === 'loading' && <p className="muted" style={{fontSize:12, margin:'12px 0 0'}}>Récupération de votre position…</p>}
          {geoState === 'error' && <p style={{fontSize:12, margin:'12px 0 0', color:'var(--danger, #c02424)'}}>Impossible d'accéder à votre position. Saisissez votre code postal.</p>}
          {geoState === 'ok' && position && <p className="muted" style={{fontSize:12, margin:'12px 0 0'}}>Magasins triés par distance depuis votre position.</p>}
        </div>
      </section>

      <section style={{padding:'24px 20px 60px'}}>
        <div style={{maxWidth:1100, margin:'0 auto'}}>
          <h2 style={{fontSize:18, fontWeight:800, margin:'0 0 16px'}}>
            {loading ? 'Recherche…' : `${stores.length} magasin${stores.length > 1 ? 's' : ''} ${searched && query ? 'pour "' + query + '"' : 'disponible' + (stores.length > 1 ? 's' : '')}`}
          </h2>
          {!loading && stores.length === 0 && (
            <div style={{textAlign:'center', padding:40, background:'#fff', borderRadius:12, border:'1px solid var(--line-2)'}}>
              <p className="muted" style={{margin:0}}>Aucun magasin ne correspond. Essayez une autre recherche ou <a href="/app" style={{color:'var(--vival-red)'}}>inscrivez votre magasin</a>.</p>
            </div>
          )}
          <div className="home-store-grid" style={{display:'grid', gridTemplateColumns:'repeat(auto-fill, minmax(280px, 1fr))', gap:14}}>
            {stores.map(s => (
              <a key={s.id} href={`/shop/${s.code}`} style={{textDecoration:'none', color:'inherit', background:'#fff', border:'1px solid var(--line-1)', borderRadius:12, padding:16, display:'flex', flexDirection:'column', gap:8, transition:'all 0.15s'}}>
                <div style={{display:'flex', gap:12, alignItems:'center'}}>
                  <div style={{width:48, height:48, borderRadius:10, background:'#fff', border:'1px solid var(--line-2)', padding:4, display:'flex', alignItems:'center', justifyContent:'center', overflow:'hidden', flexShrink:0}}>
                    <img src={s.logo_url || '/assets/vival-logo.png'} alt="" style={{maxWidth:'100%', maxHeight:'100%', objectFit:'contain'}} />
                  </div>
                  <div style={{flex:1, minWidth:0}}>
                    <div style={{fontWeight:700, fontSize:15}}>{s.name}</div>
                    <div className="muted" style={{fontSize:12}}>{s.city}{s.postal_code ? ` · ${s.postal_code}` : ''}</div>
                  </div>
                  {s.distance_km !== null && s.distance_km !== undefined && (
                    <div style={{background:'var(--surface-1)', padding:'4px 8px', borderRadius:10, fontSize:11, fontWeight:700, color:'var(--ink-2)'}}>{s.distance_km} km</div>
                  )}
                </div>
                <div className="muted" style={{fontSize:12}}>{s.address}</div>
                {s.opening_hours && <div style={{fontSize:12, color:'var(--ink-2)'}}>{s.opening_hours}</div>}
                <div style={{display:'flex', gap:8, flexWrap:'wrap', fontSize:11, color:'var(--ink-3)', marginTop:4}}>
                  {Number(s.delivery_fee) > 0 ? <span>Livraison {Number(s.delivery_fee).toFixed(2)} €</span> : <span>Retrait magasin</span>}
                  {Number(s.min_order) > 0 && <span>· Commande min. {Number(s.min_order).toFixed(0)} €</span>}
                </div>
                <div style={{marginTop:'auto', paddingTop:8, borderTop:'1px solid var(--line-2)', fontSize:13, fontWeight:700, color:'var(--vival-red)'}}>
                  Voir la boutique →
                </div>
              </a>
            ))}
          </div>
        </div>
      </section>

      <footer style={{padding:'40px 20px', borderTop:'1px solid var(--line-1)', background:'#fff', textAlign:'center', fontSize:12, color:'var(--ink-4)'}}>
        <div>MonVival — Outil de gestion pour magasins Vival by Casino</div>
        <div style={{marginTop:8, display:'flex', justifyContent:'center', gap:16, flexWrap:'wrap'}}>
          <a href="/legal/mentions" style={{color:'var(--ink-4)'}}>Mentions légales</a>
          <a href="/legal/cgv" style={{color:'var(--ink-4)'}}>CGV</a>
          <a href="/legal/confidentialite" style={{color:'var(--ink-4)'}}>Confidentialité</a>
          <a href="/app" style={{color:'var(--ink-4)'}}>Espace magasin</a>
        </div>
      </footer>
    </div>
  );
}

// ===== SUIVI COMMANDE CLIENT =====
function OrderTracking({ token }) {
  const [state, setState] = useState('loading');
  const [data, setData] = useState(null);
  const [err, setErr] = useState('');
  useEffect(() => {
    (async () => {
      const { data: res, error } = await window.sb.rpc('public_track_order', { p_token: token });
      if (error) { setErr(error.message); setState('error'); return; }
      setData(res);
      setState('ok');
    })();
  }, [token]);

  const statusLabel = {
    'en-cours': 'Commande reçue',
    'prete': 'Prête pour le retrait / livraison',
    'livree': 'Livrée / retirée',
    'payee': 'Terminée',
    'annulee': 'Annulée',
  };
  const statusColor = {
    'en-cours': '#f59e0b',
    'prete': '#0891b2',
    'livree': '#059669',
    'payee': '#6b7280',
    'annulee': '#dc2626',
  };

  return (
    <div style={{minHeight:'100vh', background:'#f9fafb', fontFamily:'inherit'}}>
      <header style={{background:'#fff', borderBottom:'1px solid var(--line-1)', padding:'14px 20px'}}>
        <div style={{maxWidth:720, margin:'0 auto', display:'flex', alignItems:'center', gap:12}}>
          <a href="/" style={{textDecoration:'none', display:'flex', alignItems:'center', gap:10}}>
            <img src="/assets/vival-logo.png" alt="Vival" style={{height:28}} />
            <span style={{fontWeight:800}}>MonVival</span>
          </a>
        </div>
      </header>
      <div style={{maxWidth:720, margin:'0 auto', padding:24}}>
        {state === 'loading' && <p style={{textAlign:'center', padding:40}}>Chargement de votre commande…</p>}
        {state === 'error' && (
          <div style={{background:'#fff', padding:32, borderRadius:12, border:'1px solid var(--line-1)', textAlign:'center'}}>
            <h1 style={{margin:'0 0 8px', fontSize:20}}>Commande introuvable</h1>
            <p className="muted">Le lien est peut-être expiré ou incorrect. Vérifiez l'email de confirmation.</p>
          </div>
        )}
        {state === 'ok' && data && (() => {
          const o = data.order; const s = data.store; const status = o.status;
          return (
            <>
              <div style={{background:'#fff', padding:24, borderRadius:12, border:'1px solid var(--line-1)', marginBottom:16}}>
                <div style={{display:'flex', alignItems:'center', gap:12, marginBottom:16}}>
                  <div>
                    <div className="kpi-label">Numéro de commande</div>
                    <div style={{fontFamily:'ui-monospace, monospace', fontSize:20, fontWeight:800}}>{o.id}</div>
                  </div>
                  <div style={{marginLeft:'auto', padding:'6px 12px', borderRadius:20, background:statusColor[status] + '22', color:statusColor[status], fontWeight:700, fontSize:13}}>
                    {statusLabel[status] || status}
                  </div>
                </div>
                <div style={{display:'grid', gridTemplateColumns:'1fr 1fr', gap:12, fontSize:13}}>
                  <div><div className="kpi-label">Magasin</div><div style={{fontWeight:600, marginTop:2}}>{s.name}</div></div>
                  <div><div className="kpi-label">Contact magasin</div><div style={{fontWeight:600, marginTop:2}}>{s.phone}</div></div>
                  <div><div className="kpi-label">Mode</div><div style={{fontWeight:600, marginTop:2}}>{o.delivery_mode === 'delivery' ? 'Livraison à domicile' : 'Retrait en magasin'}</div></div>
                  {o.delivery_slot && <div><div className="kpi-label">Créneau</div><div style={{fontWeight:600, marginTop:2}}>{o.delivery_slot}</div></div>}
                  {o.delivery_address && <div style={{gridColumn:'1 / -1'}}><div className="kpi-label">Adresse livraison</div><div style={{fontWeight:600, marginTop:2}}>{o.delivery_address}</div></div>}
                </div>
              </div>

              <div style={{background:'#fff', padding:20, borderRadius:12, border:'1px solid var(--line-1)', marginBottom:16}}>
                <div className="kpi-label" style={{marginBottom:10}}>Articles ({data.lines.length})</div>
                {data.lines.map((l, i) => (
                  <div key={i} style={{display:'flex', gap:8, padding:'8px 0', borderBottom: i < data.lines.length - 1 ? '1px solid var(--line-2)' : 'none', fontSize:13}}>
                    <div style={{flex:1}}>
                      <div style={{fontWeight:600}}>{l.product_name}</div>
                      <div className="muted" style={{fontSize:11}}>{l.product_format} · {l.qty} × {Number(l.unit_price).toFixed(2)} €</div>
                    </div>
                    <div style={{fontWeight:700}}>{Number(l.line_total).toFixed(2)} €</div>
                  </div>
                ))}
                <div style={{display:'flex', justifyContent:'space-between', marginTop:12, paddingTop:12, borderTop:'2px solid var(--line-1)'}}>
                  <strong>Total à régler sur place</strong>
                  <strong style={{fontSize:18}}>{Number(o.total_ttc).toFixed(2)} €</strong>
                </div>
              </div>

              <p className="muted" style={{fontSize:12, textAlign:'center'}}>
                Pour toute question concernant votre commande, contactez le magasin au <a href={`tel:${s.phone}`}>{s.phone}</a>.
              </p>
            </>
          );
        })()}
      </div>
    </div>
  );
}

// ===== BOUTIQUE CLIENT PUBLIQUE =====
function PublicShopRoot({ storeCode }) {
  const [store, setStore] = useState(undefined);
  const [products, setProducts] = useState([]);
  const [cart, setCart] = useState(() => {
    try { return JSON.parse(localStorage.getItem('shop_cart_' + storeCode) || '[]'); } catch { return []; }
  });
  const [view, setView] = useState('shop'); // shop | cart | checkout | confirm
  const [placedOrder, setPlacedOrder] = useState(null);
  const [mode, setMode] = useState('pickup');
  const [slot, setSlot] = useState('');
  const [activeRayon, setActiveRayon] = useState('all');
  const [bioOnly, setBioOnly] = useState(false);
  const [menuOpen, setMenuOpen] = useState(false);

  useEffect(() => {
    (async () => {
      const { data, error } = await window.sb.rpc('public_get_store', { p_code: storeCode });
      const s = data?.[0] || null;
      if (!s) { setStore(null); return; }
      setStore(s);
      if (s.primary_color) document.documentElement.style.setProperty('--vival-red', s.primary_color);
      if (s.delivery_slots?.[0]) setSlot(s.delivery_slots[0]);
      const { data: prods } = await window.sb.rpc('public_list_products', { p_store_id: s.id });
      setProducts(prods || []);
    })();
  }, [storeCode]);

  useEffect(() => {
    localStorage.setItem('shop_cart_' + storeCode, JSON.stringify(cart));
  }, [cart, storeCode]);

  if (store === undefined) return <div style={{padding:40, textAlign:'center'}}>Chargement…</div>;
  if (store === null) return (
    <div style={{padding:40, textAlign:'center', maxWidth:420, margin:'80px auto'}}>
      <img src="/assets/vival-logo.png" alt="Vival" style={{width:180, marginBottom:20}} />
      <h1 style={{fontSize:22, margin:'0 0 8px'}}>Boutique introuvable</h1>
      <p className="muted">Cette boutique n'existe pas ou n'est pas active en ligne.</p>
    </div>
  );

  const addToCart = (pid, qty = 1) => {
    setCart(prev => {
      const ex = prev.find(l => l.pid === pid);
      if (ex) return prev.map(l => l.pid === pid ? { ...l, qty: l.qty + qty } : l);
      return [...prev, { pid, qty }];
    });
  };
  const setQty = (pid, qty) => setCart(prev => qty <= 0 ? prev.filter(l => l.pid !== pid) : prev.map(l => l.pid === pid ? { ...l, qty } : l));
  const clearCart = () => setCart([]);
  const subtotal = cart.reduce((s, l) => { const p = products.find(x => x.id === l.pid); return s + (p ? p.price * l.qty : 0); }, 0);
  const cartCount = cart.reduce((s, l) => s + l.qty, 0);

  return (
    <div style={{minHeight:'100vh', background:'#fff', fontFamily:'inherit'}}>
      <ShopHeader store={store} cart={cart} subtotal={subtotal} cartCount={cartCount} view={view} setView={setView}
        mode={mode} setMode={setMode} slot={slot} setSlot={setSlot} onOpenMenu={() => setMenuOpen(true)} />

      <ShopRayonsDrawer open={menuOpen} onClose={() => setMenuOpen(false)}
        rayons={(typeof RAYONS !== 'undefined' ? RAYONS : []).filter(r => products.some(p => p.rayon === r.id))}
        activeRayon={activeRayon} setActiveRayon={setActiveRayon} bioOnly={bioOnly} setBioOnly={setBioOnly} store={store} />

      {view === 'shop' && <ShopPromoBanner storeId={store.id} />}

      {view === 'shop' && <ShopHome store={store} products={products} cart={cart} addToCart={addToCart} onSetQty={setQty} onCheckout={() => setView('cart')}
        activeRayon={activeRayon} setActiveRayon={setActiveRayon} bioOnly={bioOnly} setBioOnly={setBioOnly} />}
      {view === 'cart' && <ShopCart store={store} products={products} cart={cart} setQty={setQty} clearCart={clearCart} subtotal={subtotal}
        onBack={() => setView('shop')} onCheckout={() => setView('checkout')} />}
      {view === 'checkout' && <ShopCheckout store={store} products={products} cart={cart} subtotal={subtotal}
        initialMode={mode} initialSlot={slot}
        onBack={() => setView('cart')} onPlaced={(order) => { setPlacedOrder(order); clearCart(); setView('confirm'); }} />}
      {view === 'confirm' && <ShopConfirm store={store} order={placedOrder} onBack={() => { setView('shop'); setPlacedOrder(null); }} />}

      {view === 'shop' && <ShopContactBlock store={store} />}
      <ShopAlcoholNotice />
      <ShopFooter store={store} />
    </div>
  );
}

function ShopHeader({ store, cart, subtotal, cartCount, view, setView, mode, setMode, slot, setSlot, onOpenMenu }) {
  const [slotPickerOpen, setSlotPickerOpen] = useState(false);
  // Parse les slots pour grouper par mode
  const slotsByMode = { pickup: [], delivery: [], drive: [] };
  (store.delivery_slots || []).forEach(s => {
    const lower = s.toLowerCase();
    if (lower.includes('livr')) slotsByMode.delivery.push(s);
    else if (lower.includes('drive')) slotsByMode.drive.push(s);
    else slotsByMode.pickup.push(s);
  });
  const labels = { pickup: 'Retrait en magasin', delivery: 'Livraison à domicile', drive: 'Retrait en drive' };
  const firstSlot = (m) => slotsByMode[m][0] || 'Sur demande';

  return (
    <>
      {/* Bande supérieure : store name + Changer */}
      <div className="shop-topband" style={{background:'#4a5c41', color:'#fff', padding:'8px 16px', fontSize:12, position:'sticky', top:0, zIndex:15}}>
        <div style={{maxWidth:1280, margin:'0 auto', display:'flex', alignItems:'center', gap:10}}>
          <span style={{flex:1, textTransform:'uppercase', fontWeight:700, letterSpacing:'0.02em', whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis'}}>{store.name}</span>
          <a href="/" style={{color:'#fff', textDecoration:'underline', fontWeight:700, whiteSpace:'nowrap'}}>Changer</a>
        </div>
      </div>

      {/* Header : icônes seulement */}
      <header style={{background:'#fff', borderBottom:'1px solid #e5e7eb', position:'sticky', top:32, zIndex:14}}>
        <div className="shop-header-inner" style={{maxWidth:1280, margin:'0 auto', padding:'10px 16px', display:'flex', alignItems:'center', gap:10}}>
          <button onClick={onOpenMenu} aria-label="Ouvrir le menu rayons"
            style={{background:'transparent', border:0, padding:6, cursor:'pointer', display:'flex', alignItems:'center', justifyContent:'center', color:'#374151'}}>
            <svg viewBox="0 0 24 24" width="26" height="26" fill="none" stroke="currentColor" strokeWidth="2.2"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
          </button>
          <a href="/" style={{textDecoration:'none', display:'flex', alignItems:'center', gap:8, color:'inherit', flex:1, justifyContent:'center'}}>
            <img src={store.logo_url || "/assets/vival-logo.png"} alt="Vival" style={{height:32, maxWidth:110, objectFit:'contain'}} />
          </a>
          <button aria-label="Rechercher" style={{background:'transparent', border:0, padding:6, cursor:'pointer', color:'#374151'}}>
            <svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
          </button>
          <a href="/suivi" aria-label="Suivre ma commande" style={{color:'#374151', padding:6, display:'flex'}}>
            <svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="12" cy="8" r="4"/><path d="M4 22c0-4.4 3.6-8 8-8s8 3.6 8 8"/></svg>
          </a>
          {view !== 'confirm' && (
            <button onClick={() => setView(view === 'cart' ? 'shop' : 'cart')}
              aria-label="Mon panier"
              style={{background:'transparent', border:0, padding:6, cursor:'pointer', position:'relative', color:'#374151'}}>
              <svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="9" cy="21" r="1"/><circle cx="20" cy="21" r="1"/><path d="M1 1h4l2.7 13.4c0.1 0.7 0.8 1.2 1.5 1.2h10.5c0.8 0 1.4-0.5 1.6-1.2L23 6H6"/></svg>
              {cartCount > 0 && <span style={{position:'absolute', top:0, right:-2, background:'var(--vival-red)', color:'#fff', borderRadius:999, fontSize:10, padding:'1px 6px', fontWeight:800, minWidth:18, textAlign:'center'}}>{cartCount}</span>}
            </button>
          )}
        </div>
      </header>

      {/* Barre créneaux : 3 modes avec dates, cliquable pour choisir */}
      <div style={{background:'#f3f4f6', padding:'10px 16px', borderBottom:'1px solid #e5e7eb'}}>
        <div style={{maxWidth:1280, margin:'0 auto'}}>
          <div className="shop-slots-bar">
            {['pickup', 'delivery', 'drive'].map(m => (
              <button key={m} onClick={() => { setMode(m); setSlot(slotsByMode[m][0] || ''); setSlotPickerOpen(true); }}
                style={{flex:'1 1 180px', padding:'8px 12px', border:0, background: mode === m ? '#fff' : 'transparent', borderRadius:8, cursor:'pointer', fontSize:12, textAlign:'left', display:'flex', alignItems:'center', gap:8}}>
                <div style={{flex:1, minWidth:0}}>
                  <div style={{color:'#6b7280', fontSize:11, whiteSpace:'nowrap'}}>{labels[m]}</div>
                  <div style={{color:'#111827', fontWeight:700, fontSize:13, whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis'}}>{mode === m && slot ? slot.split('·')[1]?.trim() || slot : firstSlot(m).split('·')[1]?.trim() || firstSlot(m)}</div>
                </div>
              </button>
            ))}
            <button onClick={() => setSlotPickerOpen(v => !v)}
              style={{background:'transparent', border:0, color:'#4a5c41', fontSize:12, fontWeight:700, textDecoration:'underline', cursor:'pointer', padding:'0 8px', whiteSpace:'nowrap'}}>
              Voir plus +
            </button>
          </div>
        </div>
      </div>

      {/* Picker créneau dépliable */}
      {slotPickerOpen && (
        <div style={{background:'#fff', borderBottom:'1px solid #e5e7eb'}}>
          <div className="shop-slot-pick" style={{maxWidth:1280, margin:'0 auto', padding:'16px', display:'grid', gridTemplateColumns:'1fr 2fr', gap:20}}>
            <div>
              <div style={{fontSize:11, color:'#6b7280', textTransform:'uppercase', letterSpacing:'0.05em', fontWeight:700, marginBottom:8}}>Mode</div>
              <div style={{display:'grid', gap:6}}>
                {['pickup', 'delivery', 'drive'].map(m => {
                  const fees = { pickup: 'Gratuit', delivery: store.delivery_fee > 0 ? `${Number(store.delivery_fee).toFixed(2)} €` : 'Gratuit', drive: 'Gratuit' };
                  return (
                    <button key={m} onClick={() => { setMode(m); setSlot(slotsByMode[m][0] || ''); }}
                      style={{display:'flex', alignItems:'center', gap:10, padding:'10px 12px', border: '2px solid ' + (mode === m ? 'var(--vival-red)' : '#e5e7eb'), borderRadius:10, background: mode === m ? '#fef2f2' : '#fff', cursor:'pointer', textAlign:'left'}}>
                      <div style={{flex:1}}>
                        <div style={{fontWeight:700, fontSize:13}}>{labels[m]}</div>
                        <div style={{fontSize:11, color:'#6b7280'}}>{fees[m]}</div>
                      </div>
                      {mode === m && <span style={{color:'var(--vival-red)', fontWeight:700}}>✓</span>}
                    </button>
                  );
                })}
              </div>
            </div>
            <div>
              <div style={{fontSize:11, color:'#6b7280', textTransform:'uppercase', letterSpacing:'0.05em', fontWeight:700, marginBottom:8}}>Créneaux disponibles</div>
              <div className="shop-slots-grid" style={{display:'grid', gridTemplateColumns:'repeat(auto-fill, minmax(200px, 1fr))', gap:6}}>
                {(slotsByMode[mode] || []).map((s, i) => (
                  <button key={i} onClick={() => { setSlot(s); setSlotPickerOpen(false); }}
                    style={{padding:'10px 14px', border: '1px solid ' + (slot === s ? 'var(--vival-red)' : '#e5e7eb'), borderRadius:10, background: slot === s ? '#fef2f2' : '#fff', cursor:'pointer', fontSize:13, fontWeight:600, textAlign:'left', color: slot === s ? 'var(--vival-red)' : '#111827'}}>
                    {s.split('·').slice(-1)[0].trim()}
                  </button>
                ))}
                {slotsByMode[mode].length === 0 && (
                  <div style={{color:'#6b7280', fontSize:13}}>Aucun créneau configuré pour ce mode. Le magasin vous contactera.</div>
                )}
              </div>
            </div>
          </div>
        </div>
      )}
    </>
  );
}

function ShopRayonsDrawer({ open, onClose, rayons, activeRayon, setActiveRayon, bioOnly, setBioOnly, store }) {
  if (!open) return null;
  const RAYON_ICONS = {
    'fruits-et-legumes': '🥬', 'produits-frais': '🧀', 'viandes-et-poissons': '🥩',
    'boulangerie-et-patisserie': '🥐', 'epicerie-salee': '🧂', 'epicerie-sucree': '🍪',
    'boissons-et-alcool': '🥤', 'vins': '🍷', 'surgeles': '❄️',
    'hygiene-et-beaute': '🧴', 'entretien-bazar': '🧽', 'univers-bebe': '👶',
    'animaux': '🐾', 'produits-bio': '🌱', 'maison-et-loisirs': '🏠',
    'produit-sans-gluten': '🌾', 'economat': '📦',
  };
  return (
    <div style={{position:'fixed', inset:0, background:'#fff', zIndex:100, overflowY:'auto'}}>
      <div style={{background:'#4a5c41', color:'#fff', padding:'8px 16px', fontSize:12, display:'flex', alignItems:'center', gap:10}}>
        <span style={{flex:1, textTransform:'uppercase', fontWeight:700, letterSpacing:'0.02em'}}>{store.name}</span>
        <a href="/" style={{color:'#fff', textDecoration:'underline', fontWeight:700}}>Changer</a>
      </div>
      <div style={{padding:'10px 16px', borderBottom:'1px solid #e5e7eb', display:'flex', alignItems:'center', gap:10, background:'#fff'}}>
        <button onClick={onClose} aria-label="Fermer" style={{background:'transparent', border:0, padding:6, cursor:'pointer', color:'#374151'}}>
          <svg viewBox="0 0 24 24" width="26" height="26" fill="none" stroke="currentColor" strokeWidth="2.2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
        </button>
        <div style={{flex:1, textAlign:'center'}}>
          <img src={store.logo_url || "/assets/vival-logo.png"} alt="" style={{height:28}} />
        </div>
        <div style={{width:32}} />
      </div>
      <div style={{padding:'18px 20px 6px', fontSize:17, fontWeight:700, color:'#1f2937'}}>Tous les rayons</div>
      <div>
        <button onClick={() => { setBioOnly(true); setActiveRayon('all'); onClose(); }}
          style={{display:'flex', alignItems:'center', gap:14, width:'100%', padding:'16px 20px', border:0, background: bioOnly ? '#16a34a' : '#fff', borderBottom:'1px solid #e5e7eb', cursor:'pointer', fontSize:15, fontWeight:700, letterSpacing:'0.03em', color: bioOnly ? '#fff' : '#16a34a', textAlign:'left', textTransform:'uppercase'}}>
          <span style={{fontSize:22}}>🌱</span>
          <span style={{flex:1}}>Produits Bio</span>
          <svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" strokeWidth="2.4"><polyline points="9 18 15 12 9 6"/></svg>
        </button>
        <button onClick={() => { setActiveRayon('all'); setBioOnly(false); onClose(); }}
          style={{display:'flex', alignItems:'center', gap:14, width:'100%', padding:'16px 20px', border:0, background: activeRayon === 'all' && !bioOnly ? '#fef2f2' : '#fff', borderBottom:'1px solid #e5e7eb', cursor:'pointer', fontSize:14, fontWeight:600, color:'#1f2937', textAlign:'left', textTransform:'uppercase', letterSpacing:'0.02em'}}>
          <span style={{flex:1}}>Tous les produits</span>
          <svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" strokeWidth="2" style={{color:'#9ca3af'}}><polyline points="9 18 15 12 9 6"/></svg>
        </button>
        {rayons.map(r => (
          <button key={r.id} onClick={() => { setActiveRayon(r.id); setBioOnly(false); onClose(); }}
            style={{display:'flex', alignItems:'center', gap:14, width:'100%', padding:'16px 20px', border:0, background: activeRayon === r.id ? '#fef2f2' : '#fff', borderBottom:'1px solid #e5e7eb', cursor:'pointer', fontSize:14, fontWeight:600, color:activeRayon === r.id ? 'var(--vival-red)' : '#1f2937', textAlign:'left', textTransform:'uppercase', letterSpacing:'0.02em'}}>
            <span style={{fontSize:20, width:24, display:'inline-block', textAlign:'center'}}>{RAYON_ICONS[r.id] || '•'}</span>
            <span style={{flex:1}}>{r.name}</span>
            <svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" strokeWidth="2" style={{color:'#9ca3af'}}><polyline points="9 18 15 12 9 6"/></svg>
          </button>
        ))}
      </div>
    </div>
  );
}

function ShopPromoBanner({ storeId }) {
  const [promos, setPromos] = useState([]);
  const [idx, setIdx] = useState(0);
  useEffect(() => {
    window.sb.rpc('public_list_promos', { p_store_id: storeId }).then(({ data }) => setPromos(data || []));
  }, [storeId]);
  useEffect(() => {
    if (promos.length < 2) return;
    const t = setInterval(() => setIdx(i => (i + 1) % promos.length), 5000);
    return () => clearInterval(t);
  }, [promos.length]);
  if (!promos.length) return null;
  const p = promos[idx];
  return (
    <section style={{maxWidth:1280, margin:'16px auto', padding:'0 16px'}}>
      <div style={{background: p.bg_color || '#16a34a', color: p.text_color || '#fff', borderRadius:12, padding:'18px 20px', position:'relative', overflow:'hidden'}}>
        <div style={{display:'flex', alignItems:'center', gap:14, flexWrap:'wrap'}}>
          {p.badge && <div style={{fontSize:32, fontWeight:900, lineHeight:1, letterSpacing:'-0.02em', flexShrink:0}}>{p.badge}</div>}
          <div style={{flex:'1 1 180px', minWidth:0}}>
            <div style={{fontSize:15, fontWeight:800, lineHeight:1.2, textTransform:'uppercase', letterSpacing:'0.02em'}}>{p.title}</div>
            {p.subtitle && <div style={{fontSize:12, opacity:0.95, marginTop:3, lineHeight:1.4}}>{p.subtitle}</div>}
          </div>
          {p.cta_label && (
            <a href={p.cta_url || '#'} style={{background:'rgba(255,255,255,0.2)', color:'inherit', padding:'8px 14px', borderRadius:999, fontSize:12, fontWeight:700, textDecoration:'none', whiteSpace:'nowrap', flexShrink:0}}>{p.cta_label} →</a>
          )}
        </div>
      </div>
      {promos.length > 1 && (
        <div style={{display:'flex', justifyContent:'center', gap:6, marginTop:8}}>
          {promos.map((_, i) => (
            <button key={i} onClick={() => setIdx(i)} aria-label={`Promo ${i+1}`}
              style={{width:i===idx?20:8, height:8, borderRadius:4, border:0, background: i===idx ? '#4a5c41' : '#d1d5db', cursor:'pointer', transition:'all 0.2s'}} />
          ))}
        </div>
      )}
    </section>
  );
}

function ShopHome({ store, products, cart, addToCart, onSetQty, onCheckout, activeRayon, setActiveRayon, bioOnly, setBioOnly }) {
  const [q, setQ] = useState('');

  const filtered = products.filter(p => {
    if (bioOnly && !(p.rayon === 'produits-bio' || (p.badges || []).includes('bio'))) return false;
    if (activeRayon !== 'all' && p.rayon !== activeRayon) return false;
    if (q.trim()) {
      const qq = q.toLowerCase();
      return (p.name||'').toLowerCase().includes(qq) || (p.brand||'').toLowerCase().includes(qq) || (p.ean||'').includes(qq);
    }
    return true;
  });

  const RAYON_ICONS = {
    'fruits-et-legumes': 'fruits-et-legumes',
    'produits-frais': 'produits-frais',
    'viandes-et-poissons': 'viandes-et-poissons',
    'boulangerie-et-patisserie': 'boulangerie-et-patisserie',
    'epicerie-salee': 'epicerie-salee',
    'epicerie-sucree': 'epicerie-sucree',
    'boissons-et-alcool': 'boissons-et-alcool',
    'surgeles': 'surgeles',
    'hygiene-et-beaute': 'hygiene-et-beaute',
    'entretien-bazar': 'entretien-bazar',
    'produits-bio': 'produits-bio',
  };

  const rayonsAvec = (typeof RAYONS !== 'undefined' ? RAYONS : []).filter(r => products.some(p => p.rayon === r.id));

  const filterActive = activeRayon !== 'all' || bioOnly || q.trim();
  const mainRayons = rayonsAvec.filter(r => ['fruits-et-legumes', 'produits-frais', 'viandes-et-poissons', 'boulangerie-et-patisserie', 'epicerie-salee', 'epicerie-sucree'].includes(r.id));

  return (
    <div style={{maxWidth:1280, margin:'0 auto', padding:'16px'}}>
      {/* Grosses tuiles rayons quand pas de filtre actif */}
      {!filterActive && (
        <>
          <h2 style={{fontSize:20, fontWeight:800, margin:'8px 0 4px', color:'#1f2937'}}>Je commence mes courses en ligne</h2>
          <div style={{fontSize:11, fontWeight:600, color:'#6b7280', marginBottom:16, textTransform:'uppercase', letterSpacing:'0.03em'}}>{store.name}</div>
          <button onClick={() => setBioOnly(true)}
            style={{display:'flex', alignItems:'center', gap:12, width:'100%', padding:'14px 22px', background:'#16a34a', color:'#fff', border:0, borderRadius:999, cursor:'pointer', fontSize:14, fontWeight:800, letterSpacing:'0.04em', marginBottom:16, textTransform:'uppercase'}}>
            <span style={{fontSize:20}}>🌱</span>
            <span>Produits bio</span>
          </button>
          <div className="shop-rayon-tiles" style={{display:'grid', gridTemplateColumns:'repeat(2, 1fr)', gap:10, marginBottom:20}}>
            {mainRayons.map(r => (
              <button key={r.id} onClick={() => setActiveRayon(r.id)}
                style={{background:'#fff', border:'1px solid #e5e7eb', borderRadius:12, padding:'20px 12px', cursor:'pointer', fontSize:12, fontWeight:800, textAlign:'center', color:'#1f2937', letterSpacing:'0.04em', minHeight:80, display:'flex', alignItems:'center', justifyContent:'center', textTransform:'uppercase', lineHeight:1.25}}>
                {r.name}
              </button>
            ))}
            <button onClick={() => setActiveRayon('all')} style={{gridColumn:'1 / -1', background:'#fff', border:'1px solid #4a5c41', borderRadius:12, padding:'16px', cursor:'pointer', fontSize:12, fontWeight:800, color:'#4a5c41', display:'flex', alignItems:'center', justifyContent:'center', gap:10, textTransform:'uppercase', letterSpacing:'0.03em'}}>
              <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="9" cy="21" r="1"/><circle cx="20" cy="21" r="1"/><path d="M1 1h4l2.7 13.4c0.1 0.7 0.8 1.2 1.5 1.2h10.5c0.8 0 1.4-0.5 1.6-1.2L23 6H6"/></svg>
              Voir tous les rayons
            </button>
          </div>
        </>
      )}

      {/* Catalogue */}
      <main>
        {store.welcome_message && (
          <div style={{background:'var(--vival-red)', color:'#fff', padding:'14px 18px', borderRadius:12, marginBottom:16, fontWeight:600, fontSize:14, lineHeight:1.4, wordBreak:'break-word'}}>
            {store.welcome_message}
          </div>
        )}

        <div style={{display:'flex', gap:10, marginBottom:16, flexWrap:'wrap', alignItems:'center', background:'#fff', border:'1px solid #e5e7eb', borderRadius:12, padding:6}}>
          <div style={{flex:'1 1 260px', display:'flex', alignItems:'center', padding:'0 10px'}}>
            <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="#9ca3af" strokeWidth="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
            <input type="search" placeholder="Rechercher un produit, une marque, un EAN…" value={q} onChange={e => setQ(e.target.value)}
              style={{flex:1, border:0, padding:'10px', fontSize:14, outline:'none', background:'transparent'}} />
          </div>
          {(activeRayon !== 'all' || bioOnly) && (
            <button onClick={() => { setActiveRayon('all'); setBioOnly(false); }}
              style={{background:'#fef2f2', color:'var(--vival-red)', border:0, borderRadius:999, padding:'8px 14px', fontSize:12, cursor:'pointer', fontWeight:700}}>
              Effacer filtres ×
            </button>
          )}
        </div>

        <div style={{fontSize:13, color:'#6b7280', marginBottom:12}}>{filtered.length} produit{filtered.length > 1 ? 's' : ''}</div>

        <div className="shop-products-grid" style={{display:'grid', gridTemplateColumns:'repeat(auto-fill, minmax(160px, 1fr))', gap:12}}>
          {filtered.slice(0, 100).map(p => {
            const qty = cart.find(l => l.pid === p.id)?.qty || 0;
            return (
              <div key={p.id} style={{background:'#fff', border:'1px solid #e5e7eb', borderRadius:12, padding:12, display:'flex', flexDirection:'column', gap:6}}>
                <div className="shop-card-img" style={{height:120, background:'#f9fafb', borderRadius:8, display:'flex', alignItems:'center', justifyContent:'center', overflow:'hidden'}}>
                  {p.image ? <img src={p.image} alt="" style={{maxWidth:'100%', maxHeight:'100%', objectFit:'contain'}} /> : <Icon name={p.rayon} style={{color:'#d1d5db', width:32, height:32}} />}
                </div>
                <div style={{fontSize:13, fontWeight:600, minHeight:32, lineHeight:1.25, color:'#111827'}}>{p.name}</div>
                <div style={{fontSize:11, color:'#6b7280'}}>{p.format}</div>
                <div style={{display:'flex', alignItems:'center', marginTop:'auto', gap:8}}>
                  <div style={{fontWeight:800, fontSize:15, color:'#111827'}}>{p.price.toFixed(2)}€</div>
                  {qty === 0 ? (
                    <button onClick={() => addToCart(p.id)}
                      style={{marginLeft:'auto', background:'var(--vival-red)', color:'#fff', border:0, borderRadius:999, padding:'6px 14px', fontWeight:700, cursor:'pointer', fontSize:12}}>
                      + Ajouter
                    </button>
                  ) : (
                    <div style={{marginLeft:'auto', display:'flex', alignItems:'center', gap:4}}>
                      <button onClick={() => onSetQty(p.id, qty - 1)} style={{width:26, height:26, borderRadius:'50%', border:'1px solid #e5e7eb', background:'#fff', cursor:'pointer', fontWeight:700}}>−</button>
                      <span style={{minWidth:18, textAlign:'center', fontWeight:700, fontSize:13}}>{qty}</span>
                      <button onClick={() => onSetQty(p.id, qty + 1)} style={{width:26, height:26, borderRadius:'50%', border:0, background:'var(--vival-red)', color:'#fff', cursor:'pointer', fontWeight:700}}>+</button>
                    </div>
                  )}
                </div>
              </div>
            );
          })}
        </div>
        {filtered.length > 100 && (
          <div style={{textAlign:'center', marginTop:24, color:'#6b7280', fontSize:13}}>
            {filtered.length - 100} autres produits — affinez avec la recherche ou un rayon.
          </div>
        )}
        {filtered.length === 0 && (
          <div style={{textAlign:'center', padding:60, color:'#6b7280'}}>
            Aucun produit ne correspond à votre recherche.
          </div>
        )}
      </main>
    </div>
  );
}

function ShopContactBlock({ store }) {
  return (
    <section style={{maxWidth:1280, margin:'0 auto', padding:'0 24px 24px'}}>
      <div style={{background:'#fff', border:'1px solid #e5e7eb', borderRadius:12, padding:20, display:'grid', gridTemplateColumns:'auto 1fr auto', gap:20, alignItems:'center'}}>
        <div style={{width:72, height:72, background:'#f3f4f6', borderRadius:10, display:'flex', alignItems:'center', justifyContent:'center', padding:8}}>
          <img src={store.logo_url || '/assets/vival-logo.png'} alt="" style={{maxWidth:'100%', maxHeight:'100%', objectFit:'contain'}} />
        </div>
        <div>
          <div style={{fontSize:12, color:'#6b7280', textTransform:'uppercase', letterSpacing:'0.05em', fontWeight:700, marginBottom:4}}>Votre magasin</div>
          <div style={{fontWeight:700, fontSize:16, color:'#111827'}}>{store.name}</div>
          <div style={{fontSize:13, color:'#4b5563', marginTop:4}}>{store.address}</div>
          {store.opening_hours && <div style={{fontSize:12, color:'#6b7280', marginTop:4}}>{store.opening_hours}</div>}
        </div>
        {store.phone && (
          <a href={`tel:${store.phone}`} style={{display:'inline-flex', alignItems:'center', gap:8, padding:'10px 18px', background:'#f3f4f6', color:'#111827', borderRadius:10, textDecoration:'none', fontWeight:700, fontSize:14}}>
            <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c0.1 0.72 0.3 1.42 0.6 2.08a2 2 0 0 1-0.45 2.11L8 9.09a16 16 0 0 0 6 6l1.18-1.18a2 2 0 0 1 2.11-0.45c0.66 0.3 1.36 0.5 2.08 0.6a2 2 0 0 1 1.72 2z"/></svg>
            {store.phone}
          </a>
        )}
      </div>
    </section>
  );
}

function ShopAlcoholNotice() {
  return (
    <section style={{padding:'24px', background:'#f9fafb'}}>
      <div style={{maxWidth:1280, margin:'0 auto', fontSize:11, color:'#6b7280', textAlign:'center', lineHeight:1.5}}>
        L'abus d'alcool est dangereux pour la santé, à consommer avec modération. La vente d'alcool est interdite aux mineurs. Article L. 3342-1 du Code de la santé publique.
      </div>
    </section>
  );
}

function ShopFooter({ store }) {
  return (
    <footer style={{background:'#111827', color:'#9ca3af', padding:'32px 24px 24px'}}>
      <div style={{maxWidth:1280, margin:'0 auto', display:'grid', gridTemplateColumns:'repeat(auto-fit, minmax(240px, 1fr))', gap:32, fontSize:13}}>
        <div>
          <div style={{color:'#fff', fontWeight:800, marginBottom:10, display:'flex', alignItems:'center', gap:8}}>
            <img src="/assets/vival-logo.png" alt="" style={{height:28, filter:'brightness(0) invert(1)'}} />
            MonVival
          </div>
          <p style={{fontSize:12, lineHeight:1.6, color:'#9ca3af'}}>Outil de gestion et de vente en ligne pour les magasins Vival by Casino.</p>
        </div>
        <div>
          <div style={{color:'#fff', fontWeight:700, marginBottom:10}}>{store.name}</div>
          <div style={{fontSize:12, color:'#9ca3af', lineHeight:1.7}}>{store.address}<br/>{store.phone}</div>
        </div>
        <div>
          <div style={{color:'#fff', fontWeight:700, marginBottom:10}}>Informations</div>
          <div style={{display:'grid', gap:6, fontSize:12}}>
            <a href="/legal/mentions" style={{color:'#d1d5db', textDecoration:'none'}}>Mentions légales</a>
            <a href="/legal/cgv" style={{color:'#d1d5db', textDecoration:'none'}}>Conditions générales de vente</a>
            <a href="/legal/confidentialite" style={{color:'#d1d5db', textDecoration:'none'}}>Politique de confidentialité</a>
            <a href="/suivi" style={{color:'#d1d5db', textDecoration:'none'}}>Suivi de commande</a>
          </div>
        </div>
      </div>
      <div style={{maxWidth:1280, margin:'24px auto 0', paddingTop:16, borderTop:'1px solid #1f2937', fontSize:11, color:'#6b7280', textAlign:'center'}}>
        © {new Date().getFullYear()} MonVival — Tous droits réservés
      </div>
    </footer>
  );
}

function ShopCart({ store, products, cart, setQty, clearCart, subtotal, onBack, onCheckout }) {
  const belowMin = subtotal < store.min_order;
  return (
    <div style={{maxWidth:720, margin:'0 auto', padding:20}}>
      <button onClick={onBack} style={{background:'transparent', border:0, color:'var(--ink-3)', cursor:'pointer', marginBottom:12}}>← Continuer mes achats</button>
      <h1 style={{fontSize:22, margin:'0 0 16px'}}>Mon panier</h1>
      {cart.length === 0 ? (
        <div style={{background:'#fff', padding:40, textAlign:'center', borderRadius:'var(--r-lg)', border:'1px solid var(--line-1)'}}>
          <Icon name="shopping-cart" style={{width:48, height:48, color:'var(--ink-4)'}} />
          <p className="muted">Votre panier est vide.</p>
        </div>
      ) : (
        <>
          <div style={{background:'#fff', borderRadius:'var(--r-lg)', border:'1px solid var(--line-1)'}}>
            {cart.map(l => {
              const p = products.find(x => x.id === l.pid);
              if (!p) return null;
              return (
                <div key={l.pid} style={{display:'flex', alignItems:'center', gap:10, padding:'12px 14px', borderBottom:'1px solid var(--line-2)'}}>
                  <div style={{width:48, height:48, background:'var(--surface-2)', borderRadius:6, flexShrink:0, display:'flex', alignItems:'center', justifyContent:'center', overflow:'hidden'}}>
                    {p.image ? <img src={p.image} alt="" style={{maxWidth:'100%', maxHeight:'100%', objectFit:'contain'}} /> : <Icon name="shopping-cart" style={{color:'var(--ink-4)'}} />}
                  </div>
                  <div style={{flex:1, minWidth:0}}>
                    <div style={{fontWeight:600, fontSize:13}}>{p.name}</div>
                    <div className="muted" style={{fontSize:11}}>{p.format} · {p.price.toFixed(2)}€</div>
                  </div>
                  <div style={{display:'flex', alignItems:'center', gap:6}}>
                    <button onClick={() => setQty(l.pid, l.qty - 1)} style={{width:28, height:28, borderRadius:6, border:'1px solid var(--line-2)', background:'#fff', cursor:'pointer'}}>−</button>
                    <span style={{minWidth:20, textAlign:'center', fontWeight:700}}>{l.qty}</span>
                    <button onClick={() => setQty(l.pid, l.qty + 1)} style={{width:28, height:28, borderRadius:6, border:'1px solid var(--line-2)', background:'#fff', cursor:'pointer'}}>+</button>
                  </div>
                  <div style={{fontWeight:700, minWidth:70, textAlign:'right'}}>{(p.price * l.qty).toFixed(2)}€</div>
                </div>
              );
            })}
          </div>
          <div style={{marginTop:16, padding:16, background:'#fff', borderRadius:'var(--r-lg)', border:'1px solid var(--line-1)'}}>
            <div style={{display:'flex', justifyContent:'space-between', fontSize:14}}><span>Sous-total</span><strong>{subtotal.toFixed(2)}€</strong></div>
            {store.min_order > 0 && <div className="muted" style={{fontSize:12, marginTop:4}}>Commande minimum : {store.min_order.toFixed(2)}€</div>}
          </div>
          {belowMin && <div style={{marginTop:12, padding:12, background:'#fef3c7', color:'#92400e', borderRadius:'var(--r-md)', fontSize:13}}>Ajoutez {(store.min_order - subtotal).toFixed(2)}€ pour atteindre la commande minimum.</div>}
          <div style={{marginTop:16, display:'flex', gap:10}}>
            <button onClick={clearCart} style={{padding:'12px 16px', border:'1px solid var(--line-2)', borderRadius:10, background:'#fff', cursor:'pointer'}}>Vider</button>
            <button onClick={onCheckout} disabled={belowMin} style={{flex:1, padding:'14px', border:0, borderRadius:10, background: belowMin ? 'var(--ink-5)' : 'var(--vival-red)', color:'#fff', fontWeight:700, cursor: belowMin ? 'not-allowed' : 'pointer'}}>
              Commander →
            </button>
          </div>
        </>
      )}
    </div>
  );
}

function ShopCheckout({ store, products, cart, subtotal, onBack, onPlaced, initialMode, initialSlot }) {
  const saved = (() => { try { return JSON.parse(localStorage.getItem('shop_client_info') || '{}'); } catch { return {}; } })();
  const [mode, setMode] = useState(initialMode || 'pickup');
  const [name, setName] = useState(saved.name || '');
  const [phone, setPhone] = useState(saved.phone || '');
  const [email, setEmail] = useState(saved.email || '');
  const [address, setAddress] = useState(saved.address || '');
  const [notes, setNotes] = useState('');
  const [slot, setSlot] = useState(initialSlot || store.delivery_slots?.[0] || '');
  const [submitting, setSubmitting] = useState(false);
  const [err, setErr] = useState('');

  const deliveryFee = mode === 'delivery' ? Number(store.delivery_fee || 0) : 0;
  const total = subtotal + deliveryFee;

  const submit = async (e) => {
    e?.preventDefault();
    setErr('');
    if (!name.trim()) { setErr('Nom requis'); return; }
    if (!phone.trim()) { setErr('Téléphone requis'); return; }
    if (mode === 'delivery' && !address.trim()) { setErr('Adresse de livraison requise'); return; }
    setSubmitting(true);
    const { data, error } = await window.sb.rpc('public_create_order', {
      payload: {
        store_id: store.id,
        customer_name: name.trim(), customer_phone: phone.trim(), customer_email: email.trim() || null,
        delivery_mode: mode, delivery_address: mode === 'delivery' ? address.trim() : null,
        delivery_slot: slot || null,
        notes: notes.trim() || null,
        lines: cart.map(l => ({ pid: l.pid, qty: l.qty })),
      }
    });
    setSubmitting(false);
    if (error) { setErr(error.message); return; }
    // Sauvegarde les infos pour la prochaine commande
    try { localStorage.setItem('shop_client_info', JSON.stringify({ name: name.trim(), phone: phone.trim(), email: email.trim(), address: address.trim() })); } catch {}
    onPlaced(data);
  };

  const fieldStyle = { width:'100%', padding:'11px 13px', border:'1px solid var(--line-2)', borderRadius:8, fontSize:14, fontFamily:'inherit' };

  return (
    <form onSubmit={submit} style={{maxWidth:640, margin:'0 auto', padding:20}}>
      <button type="button" onClick={onBack} style={{background:'transparent', border:0, color:'var(--ink-3)', cursor:'pointer', marginBottom:12}}>← Retour au panier</button>
      <h1 style={{fontSize:22, margin:'0 0 16px'}}>Vos coordonnées</h1>

      <div style={{background:'#fff', padding:16, borderRadius:'var(--r-lg)', border:'1px solid var(--line-1)', marginBottom:16}}>
        <div style={{fontWeight:700, marginBottom:10}}>Mode</div>
        <div style={{display:'grid', gridTemplateColumns:'1fr 1fr', gap:8}}>
          <label style={{border:'2px solid ' + (mode==='pickup'?'var(--vival-red)':'var(--line-2)'), borderRadius:8, padding:12, cursor:'pointer', textAlign:'center'}}>
            <input type="radio" checked={mode==='pickup'} onChange={() => setMode('pickup')} style={{display:'none'}} />
            <Icon name="shop" style={{width:22, height:22}} />
            <div style={{fontWeight:700, fontSize:13}}>Retrait en magasin</div>
            <div className="muted" style={{fontSize:11}}>Gratuit</div>
          </label>
          <label style={{border:'2px solid ' + (mode==='delivery'?'var(--vival-red)':'var(--line-2)'), borderRadius:8, padding:12, cursor:'pointer', textAlign:'center'}}>
            <input type="radio" checked={mode==='delivery'} onChange={() => setMode('delivery')} style={{display:'none'}} />
            <Icon name="truck" style={{width:22, height:22}} />
            <div style={{fontWeight:700, fontSize:13}}>Livraison à domicile</div>
            <div className="muted" style={{fontSize:11}}>{Number(store.delivery_fee||0).toFixed(2)}€</div>
          </label>
        </div>
      </div>

      <div style={{background:'#fff', padding:16, borderRadius:'var(--r-lg)', border:'1px solid var(--line-1)', marginBottom:16, display:'grid', gap:10}}>
        <div><div className="kpi-label" style={{marginBottom:4}}>Nom complet *</div><input value={name} onChange={e => setName(e.target.value)} style={fieldStyle} required /></div>
        <div style={{display:'grid', gridTemplateColumns:'1fr 1fr', gap:10}}>
          <div><div className="kpi-label" style={{marginBottom:4}}>Téléphone *</div><input type="tel" value={phone} onChange={e => setPhone(e.target.value)} style={fieldStyle} required /></div>
          <div><div className="kpi-label" style={{marginBottom:4}}>Email (optionnel)</div><input type="email" value={email} onChange={e => setEmail(e.target.value)} style={fieldStyle} /></div>
        </div>
        {mode === 'delivery' && (
          <div><div className="kpi-label" style={{marginBottom:4}}>Adresse de livraison *</div><input value={address} onChange={e => setAddress(e.target.value)} style={fieldStyle} required placeholder="37-39 Grand Rue, 38690 Le Grand-Lemps" /></div>
        )}
        {store.delivery_slots && store.delivery_slots.length > 0 && (
          <div><div className="kpi-label" style={{marginBottom:4}}>Créneau souhaité</div>
            <select value={slot} onChange={e => setSlot(e.target.value)} style={{...fieldStyle, background:'#fff'}}>
              {store.delivery_slots.map((s, i) => <option key={i} value={s}>{s}</option>)}
            </select>
          </div>
        )}
        <div><div className="kpi-label" style={{marginBottom:4}}>Note pour le magasin (optionnel)</div><textarea value={notes} onChange={e => setNotes(e.target.value)} rows={2} style={{...fieldStyle, resize:'vertical'}} /></div>
      </div>

      <div style={{background:'#fff', padding:16, borderRadius:'var(--r-lg)', border:'1px solid var(--line-1)', marginBottom:16}}>
        <div style={{display:'flex', justifyContent:'space-between', padding:'4px 0'}}><span className="muted">Sous-total</span><strong>{subtotal.toFixed(2)}€</strong></div>
        {mode === 'delivery' && <div style={{display:'flex', justifyContent:'space-between', padding:'4px 0'}}><span className="muted">Livraison</span><strong>{deliveryFee.toFixed(2)}€</strong></div>}
        <div style={{display:'flex', justifyContent:'space-between', padding:'8px 0', borderTop:'1px solid var(--line-2)', marginTop:6}}><strong>Total à régler sur place</strong><strong style={{fontSize:18}}>{total.toFixed(2)}€</strong></div>
        <div className="muted" style={{fontSize:12, marginTop:6}}>Paiement en espèces ou par carte bancaire au moment du retrait ou de la livraison</div>
      </div>

      {err && <div style={{color:'var(--danger, #c02424)', fontSize:13, marginBottom:12}}>{err}</div>}
      <button type="submit" disabled={submitting} style={{width:'100%', padding:'14px', border:0, borderRadius:10, background:'var(--vival-red)', color:'#fff', fontWeight:700, fontSize:16, cursor:'pointer'}}>
        {submitting ? 'Envoi…' : 'Confirmer ma commande'}
      </button>
    </form>
  );
}

function ShopConfirm({ store, order, onBack }) {
  return (
    <div style={{maxWidth:560, margin:'0 auto', padding:20, textAlign:'center'}}>
      <div style={{fontSize:48, marginTop:40, width:64, height:64, borderRadius:'50%', background:'#dcfce7', color:'#166534', display:'inline-flex', alignItems:'center', justifyContent:'center', fontWeight:800}}>✓</div>
      <h1 style={{fontSize:24, margin:'16px 0 8px'}}>Commande confirmée !</h1>
      <p className="muted">Le magasin <strong>{store.name}</strong> a bien reçu votre commande.</p>
      <div style={{background:'#fff', padding:20, borderRadius:'var(--r-lg)', border:'1px solid var(--line-1)', marginTop:20, textAlign:'left'}}>
        <div className="kpi-label">Numéro de commande</div>
        <div style={{fontFamily:'ui-monospace, monospace', fontSize:20, fontWeight:800, marginTop:4}}>{order?.id}</div>
        <div className="kpi-label" style={{marginTop:12}}>Total</div>
        <div style={{fontSize:20, fontWeight:800, marginTop:4}}>{Number(order?.total_ttc || 0).toFixed(2)}€</div>
        <div className="muted" style={{fontSize:12, marginTop:12}}>Un conseiller vous appellera pour confirmer. Notez votre numéro de commande.</div>
        {order?.tracking_token && (
          <a href={`/suivi/${order.tracking_token}`} style={{display:'inline-block', marginTop:16, padding:'10px 18px', background:'var(--vival-red)', color:'#fff', borderRadius:8, textDecoration:'none', fontWeight:700, fontSize:14}}>
            Suivre ma commande
          </a>
        )}
      </div>
      <button onClick={onBack} style={{marginTop:20, padding:'12px 24px', border:'1px solid var(--line-2)', borderRadius:10, background:'#fff', cursor:'pointer'}}>Retour à la boutique</button>
    </div>
  );
}

// ===== SUPER-ADMIN =====
function SuperAdminRoot() {
  const [session, setSession] = useState(undefined);
  useEffect(() => {
    window.sb.auth.getSession().then(({ data }) => setSession(data.session || null));
    const { data: sub } = window.sb.auth.onAuthStateChange((_e, s) => setSession(s || null));
    return () => sub?.subscription?.unsubscribe();
  }, []);
  if (session === undefined) return <div style={{padding:40, textAlign:'center'}}>Chargement…</div>;
  if (!session) return <div style={{padding:40, textAlign:'center'}}>
    <p>Vous devez être connecté pour accéder à l'admin.</p>
    <a href="/" style={{color:'var(--vival-red)'}}>Se connecter</a>
  </div>;
  const isSuper = !!session.user.app_metadata?.is_superadmin;
  if (!isSuper) return <div style={{padding:40, textAlign:'center'}}>
    <p style={{color:'var(--danger, #c02424)', fontWeight:700}}>Accès refusé</p>
    <p className="muted">Votre compte n'a pas les droits super-administrateur.</p>
    <a href="/" style={{color:'var(--vival-red)'}}>Retour</a>
  </div>;
  return <SuperAdminDashboard session={session} />;
}

function SuperAdminDashboard({ session }) {
  const [data, setData] = useState(null);
  const [err, setErr] = useState('');
  const [selectedId, setSelectedId] = useState(null);
  const [detail, setDetail] = useState(null);

  const load = async () => {
    const { data: res, error } = await window.sb.functions.invoke('admin-stats', { body: {} });
    if (error || res?.error) { setErr(error?.message || res?.error); return; }
    setData(res);
  };
  useEffect(() => { load(); }, []);

  useEffect(() => {
    if (!selectedId) { setDetail(null); return; }
    (async () => {
      const { data: res } = await window.sb.functions.invoke('admin-stats', { body: { store_id: selectedId } });
      setDetail(res);
    })();
  }, [selectedId]);

  if (err) return <div style={{padding:40, color:'var(--danger, #c02424)'}}>{err}</div>;
  if (!data) return <div style={{padding:40, textAlign:'center'}}>Chargement…</div>;

  return (
    <div style={{minHeight:'100vh', background:'#f9fafb', fontFamily:'inherit'}}>
      <header style={{background:'#0b0d0e', color:'#fff', padding:'16px 24px'}}>
        <div style={{maxWidth:1200, margin:'0 auto', display:'flex', alignItems:'center', gap:16}}>
          <span style={{fontWeight:800, fontSize:16}}>MonVival · Super Admin</span>
          <span style={{fontSize:12, opacity:0.7}}>{session.user.email}</span>
          <div style={{flex:1}} />
          <a href="/" style={{color:'#fff', opacity:0.8, fontSize:13, textDecoration:'none'}}>Mon espace →</a>
        </div>
      </header>

      <div style={{maxWidth:1200, margin:'0 auto', padding:24}}>
        <h1 style={{fontSize:24, fontWeight:800, margin:'0 0 20px'}}>Tableau de bord</h1>

        <div style={{display:'grid', gridTemplateColumns:'repeat(auto-fit, minmax(180px, 1fr))', gap:12, marginBottom:28}}>
          <KpiTile label="Magasins total" value={data.kpis.total_stores} />
          <KpiTile label="MRR" value={`${data.kpis.mrr.toFixed(0)} €`} accent="var(--vival-red)" />
          <KpiTile label="ARR projeté" value={`${data.kpis.arr.toFixed(0)} €`} />
          <KpiTile label="Actifs" value={data.kpis.active} accent="#059669" />
          <KpiTile label="En essai" value={data.kpis.trialing} accent="#0891b2" />
          <KpiTile label="Impayés" value={data.kpis.past_due} accent="#d97706" />
          <KpiTile label="Résiliés" value={data.kpis.canceled} accent="#6b7280" />
        </div>

        <ErrorsLogCard />

        <div style={{background:'#fff', borderRadius:12, border:'1px solid var(--line-1)', overflow:'hidden'}}>
          <div style={{padding:'14px 20px', borderBottom:'1px solid var(--line-2)', fontWeight:800}}>
            Liste des magasins
          </div>
          <table style={{width:'100%', borderCollapse:'collapse', fontSize:13}}>
            <thead style={{background:'var(--surface-1)', textTransform:'uppercase', fontSize:10, letterSpacing:'0.05em', color:'var(--ink-3)'}}>
              <tr>
                <th style={{padding:'10px 16px', textAlign:'left'}}>Magasin</th>
                <th style={{padding:'10px 16px', textAlign:'left'}}>Plan</th>
                <th style={{padding:'10px 16px', textAlign:'left'}}>Statut</th>
                <th style={{padding:'10px 16px', textAlign:'right'}}>MRR</th>
                <th style={{padding:'10px 16px', textAlign:'right'}}>Commandes</th>
                <th style={{padding:'10px 16px', textAlign:'right'}}>CA cumulé</th>
                <th style={{padding:'10px 16px', textAlign:'left'}}>Dernière activité</th>
                <th style={{padding:'10px 16px'}}></th>
              </tr>
            </thead>
            <tbody>
              {data.stores.map(s => (
                <tr key={s.id} style={{borderTop:'1px solid var(--line-2)', cursor:'pointer'}} onClick={() => setSelectedId(s.id)}>
                  <td style={{padding:'12px 16px'}}>
                    <div style={{fontWeight:700}}>{s.name}</div>
                    <div className="muted" style={{fontSize:11}}>{s.code} · créé {new Date(s.created_at).toLocaleDateString('fr-FR')}</div>
                  </td>
                  <td style={{padding:'12px 16px', fontSize:12}}>{s.plan}</td>
                  <td style={{padding:'12px 16px'}}><StatusBadge status={s.subscription_status} /></td>
                  <td style={{padding:'12px 16px', textAlign:'right', fontWeight:600}}>{s.mrr} €</td>
                  <td style={{padding:'12px 16px', textAlign:'right'}}>{s.orders_count}</td>
                  <td style={{padding:'12px 16px', textAlign:'right'}}>{s.total_revenue.toFixed(2)} €</td>
                  <td style={{padding:'12px 16px', fontSize:11, color:'var(--ink-3)'}}>{s.last_order_at ? new Date(s.last_order_at).toLocaleString('fr-FR') : '—'}</td>
                  <td style={{padding:'12px 16px', textAlign:'right'}}>
                    <span style={{color:'var(--ink-4)'}}>›</span>
                  </td>
                </tr>
              ))}
              {data.stores.length === 0 && <tr><td colSpan={8} style={{padding:40, textAlign:'center', color:'var(--ink-4)'}}>Aucun magasin.</td></tr>}
            </tbody>
          </table>
        </div>
      </div>

      {selectedId && detail && <StoreDetailModal detail={detail} onClose={() => { setSelectedId(null); setDetail(null); }} />}
    </div>
  );
}

function ErrorsLogCard() {
  const [errors, setErrors] = useState([]);
  const [health, setHealth] = useState(null);
  const load = async () => {
    const { data } = await window.sb.functions.invoke('admin-errors', { body: {} });
    setErrors(data?.errors || []);
    try {
      const res = await fetch('https://vqctcgwvdpcylgqvdurh.supabase.co/functions/v1/health');
      setHealth(await res.json());
    } catch {}
  };
  useEffect(() => { load(); }, []);
  return (
    <div style={{background:'#fff', borderRadius:12, border:'1px solid var(--line-1)', overflow:'hidden', marginBottom:20}}>
      <div style={{padding:'14px 20px', borderBottom:'1px solid var(--line-2)', display:'flex', alignItems:'center', gap:12}}>
        <div style={{fontWeight:800}}>Santé système</div>
        {health && <span style={{fontSize:11, padding:'2px 8px', borderRadius:10, fontWeight:700, background: health.status === 'ok' ? '#dcfce7' : '#fee2e2', color: health.status === 'ok' ? '#166534' : '#991b1b'}}>
          {health.status?.toUpperCase()} · DB {health.latency_ms}ms
        </span>}
        <div style={{flex:1}} />
        <Button variant="ghost" size="sm" icon="refresh" onClick={load}>Actualiser</Button>
      </div>
      <div style={{padding:'12px 20px', fontSize:12}}>
        <div className="kpi-label" style={{marginBottom:6}}>50 dernières erreurs client ({errors.length})</div>
        {errors.length === 0 ? (
          <div className="muted" style={{padding:12, textAlign:'center'}}>Aucune erreur récente.</div>
        ) : (
          <div style={{maxHeight:240, overflowY:'auto', display:'grid', gap:4}}>
            {errors.map(e => (
              <div key={e.id} style={{padding:'8px 10px', background:'#fef2f2', borderLeft:'3px solid #dc2626', borderRadius:4, fontSize:11}}>
                <div style={{fontWeight:600, color:'#991b1b'}}>{e.message}</div>
                <div style={{color:'#6b7280', marginTop:2}}>{new Date(e.created_at).toLocaleString('fr-FR')} · {e.url}</div>
                {e.stack && <details style={{marginTop:4}}><summary style={{cursor:'pointer', color:'#6b7280'}}>Stack</summary><pre style={{fontSize:10, margin:'4px 0 0', whiteSpace:'pre-wrap', color:'#4b5563', fontFamily:'ui-monospace, monospace'}}>{e.stack.slice(0, 800)}</pre></details>}
              </div>
            ))}
          </div>
        )}
      </div>
    </div>
  );
}

function KpiTile({ label, value, accent }) {
  return (
    <div style={{background:'#fff', borderRadius:10, padding:'16px 18px', border:'1px solid var(--line-1)'}}>
      <div style={{fontSize:11, textTransform:'uppercase', letterSpacing:'0.05em', color:'var(--ink-3)', fontWeight:700}}>{label}</div>
      <div style={{fontSize:26, fontWeight:800, marginTop:4, color: accent || 'var(--ink-0)'}}>{value}</div>
    </div>
  );
}

function StatusBadge({ status }) {
  const colors = {
    active: { bg:'#dcfce7', c:'#166534' },
    trialing: { bg:'#dbeafe', c:'#1e40af' },
    past_due: { bg:'#fef3c7', c:'#92400e' },
    canceled: { bg:'#fee2e2', c:'#991b1b' },
  }[status] || { bg:'#f3f4f6', c:'#6b7280' };
  return <span style={{background:colors.bg, color:colors.c, padding:'2px 8px', borderRadius:10, fontSize:11, fontWeight:700}}>{status || '—'}</span>;
}

function StoreDetailModal({ detail, onClose }) {
  const s = detail.store;
  const shopUrl = s.online_enabled ? `${window.location.origin}/shop/${s.code}` : null;
  return (
    <div style={{position:'fixed', inset:0, background:'rgba(0,0,0,0.5)', zIndex:200, display:'flex', alignItems:'flex-start', justifyContent:'center', padding:'40px 20px', overflowY:'auto'}} onClick={onClose}>
      <div style={{background:'#fff', borderRadius:12, maxWidth:900, width:'100%', overflow:'hidden'}} onClick={e => e.stopPropagation()}>
        <div style={{padding:'20px 24px', borderBottom:'1px solid var(--line-2)', display:'flex', alignItems:'center', gap:12}}>
          <div style={{flex:1}}>
            <div style={{fontSize:18, fontWeight:800}}>{s.name}</div>
            <div className="muted" style={{fontSize:12}}>{s.code} · {s.address}</div>
          </div>
          <StatusBadge status={s.subscription_status} />
          <button onClick={onClose} style={{background:'transparent', border:0, fontSize:20, cursor:'pointer', color:'var(--ink-3)'}}>×</button>
        </div>
        <div style={{padding:24, display:'grid', gap:20}}>
          <div style={{display:'grid', gridTemplateColumns:'repeat(auto-fit, minmax(140px, 1fr))', gap:10}}>
            <InfoRow label="Plan" value={s.plan} />
            <InfoRow label="MRR" value={`${detail.stats.mrr_estimate} €`} />
            <InfoRow label="Commandes" value={detail.stats.orders_count} />
            <InfoRow label="Clients" value={detail.stats.customers_count} />
            <InfoRow label="Produits locaux" value={detail.stats.products_count} />
            <InfoRow label="Créé le" value={new Date(s.created_at).toLocaleDateString('fr-FR')} />
          </div>
          {shopUrl && <div>
            <div className="kpi-label">URL boutique</div>
            <a href={shopUrl} target="_blank" rel="noreferrer" style={{color:'var(--vival-red)', fontSize:13}}>{shopUrl}</a>
          </div>}
          <div>
            <div className="kpi-label" style={{marginBottom:6}}>Utilisateurs ({detail.users?.length || 0})</div>
            <div style={{display:'grid', gap:4, fontSize:12}}>
              {(detail.users || []).map((u, i) => <div key={i} style={{padding:'6px 10px', background:'var(--surface-1)', borderRadius:6}}>{u.name} · {u.role} · {u.active ? 'actif' : 'inactif'}</div>)}
              {(!detail.users || detail.users.length === 0) && <div className="muted" style={{fontSize:12}}>Aucun utilisateur PIN.</div>}
            </div>
          </div>
          <div>
            <div className="kpi-label" style={{marginBottom:6}}>10 dernières commandes</div>
            <div style={{display:'grid', gap:4, fontSize:12}}>
              {(detail.recent_orders || []).slice(0, 10).map(o => <div key={o.id} style={{padding:'6px 10px', background:'var(--surface-1)', borderRadius:6, display:'flex', gap:8}}>
                <span style={{fontFamily:'ui-monospace, monospace'}}>{o.id}</span>
                <span style={{color:'var(--ink-3)'}}>{new Date(o.created_at).toLocaleString('fr-FR', {day:'2-digit', month:'short', hour:'2-digit', minute:'2-digit'})}</span>
                <span>{o.source === 'online' ? 'EN LIGNE' : 'COMPTOIR'}</span>
                <span style={{marginLeft:'auto', fontWeight:700}}>{Number(o.total_ttc || 0).toFixed(2)} €</span>
                <span style={{color:'var(--ink-3)'}}>{o.status}</span>
              </div>)}
              {(!detail.recent_orders || detail.recent_orders.length === 0) && <div className="muted" style={{fontSize:12}}>Aucune commande.</div>}
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

function InfoRow({ label, value }) {
  return <div>
    <div className="kpi-label">{label}</div>
    <div style={{fontWeight:600, marginTop:2}}>{value}</div>
  </div>;
}

// ===== PAGES LEGALES =====
const EDITOR = {
  name: 'Pc Relais',
  address: 'À compléter',
  siret: 'À compléter',
  director: 'À compléter',
  email: 'info@pcrelais.fr',
  phone: 'À compléter',
};
const HOST = {
  name: 'Vercel Inc.',
  address: '440 N Barranca Ave #4133, Covina, CA 91723, USA',
  site: 'vercel.com',
};
const DB_HOST = {
  name: 'Supabase Inc.',
  address: '970 Toa Payoh North #07-04, Singapore 318992',
  site: 'supabase.com',
};

function LegalPage({ page }) {
  const content = {
    mentions: <MentionsLegales />,
    cgv: <CGV />,
    confidentialite: <Confidentialite />,
  }[page];
  const title = { mentions: 'Mentions légales', cgv: 'Conditions générales de vente', confidentialite: 'Politique de confidentialité' }[page];
  return (
    <div style={{minHeight:'100vh', background:'#f9fafb', fontFamily:'inherit'}}>
      <header style={{background:'#fff', borderBottom:'1px solid var(--line-1)', padding:'16px 20px', position:'sticky', top:0, zIndex:10}}>
        <div style={{maxWidth:820, margin:'0 auto', display:'flex', alignItems:'center', gap:12}}>
          <a href="/" style={{textDecoration:'none', display:'flex', alignItems:'center', gap:10}}>
            <img src="/assets/vival-logo.png" alt="Vival" style={{height:30}} />
            <span style={{fontWeight:800, color:'var(--ink-1)'}}>MonVival</span>
          </a>
        </div>
      </header>
      <article style={{maxWidth:820, margin:'0 auto', padding:'32px 20px 80px'}}>
        <h1 style={{fontSize:28, fontWeight:800, margin:'0 0 8px'}}>{title}</h1>
        <p className="muted" style={{fontSize:13, margin:'0 0 32px'}}>Dernière mise à jour : {new Date().toLocaleDateString('fr-FR')}</p>
        <div style={{background:'#fff', padding:'32px', borderRadius:12, border:'1px solid var(--line-1)', fontSize:14, lineHeight:1.7, color:'#2a2a2a'}}>
          {content}
        </div>
        <div style={{marginTop:32, display:'flex', gap:16, fontSize:12, flexWrap:'wrap'}}>
          <a href="/legal/mentions" style={{color:'var(--ink-3)'}}>Mentions légales</a>
          <a href="/legal/cgv" style={{color:'var(--ink-3)'}}>CGV</a>
          <a href="/legal/confidentialite" style={{color:'var(--ink-3)'}}>Confidentialité</a>
          <a href="/" style={{color:'var(--ink-3)', marginLeft:'auto'}}>Retour à MonVival</a>
        </div>
      </article>
    </div>
  );
}

function MentionsLegales() {
  return (
    <>
      <h2 style={{fontSize:18, margin:'0 0 12px'}}>Éditeur</h2>
      <p><strong>{EDITOR.name}</strong><br/>{EDITOR.address}<br/>SIRET : {EDITOR.siret}<br/>Email : {EDITOR.email}<br/>Téléphone : {EDITOR.phone}</p>
      <p>Directeur de la publication : {EDITOR.director}</p>
      <h2 style={{fontSize:18, margin:'24px 0 12px'}}>Hébergement</h2>
      <p>Le site est hébergé par <strong>{HOST.name}</strong>, {HOST.address} — <a href={`https://${HOST.site}`} target="_blank" rel="noreferrer">{HOST.site}</a>. Les bases de données et fonctions serveur sont fournies par <strong>{DB_HOST.name}</strong>, {DB_HOST.address} — <a href={`https://${DB_HOST.site}`} target="_blank" rel="noreferrer">{DB_HOST.site}</a>.</p>
      <h2 style={{fontSize:18, margin:'24px 0 12px'}}>Propriété intellectuelle</h2>
      <p>L'ensemble des éléments (textes, logos, graphismes, code) constituant le service MonVival est la propriété exclusive de {EDITOR.name}, à l'exception des marques citées qui restent la propriété de leurs titulaires respectifs. Toute reproduction partielle ou totale est interdite sans autorisation écrite préalable.</p>
      <h2 style={{fontSize:18, margin:'24px 0 12px'}}>Contact</h2>
      <p>Pour toute question concernant ce site, adressez-vous à <a href={`mailto:${EDITOR.email}`}>{EDITOR.email}</a>.</p>
    </>
  );
}

function CGV() {
  return (
    <>
      <h2 style={{fontSize:18, margin:'0 0 12px'}}>1. Objet</h2>
      <p>Les présentes conditions générales de vente (CGV) régissent la fourniture par {EDITOR.name} du service logiciel en ligne MonVival, destiné aux exploitants de magasins de proximité, notamment les franchisés du réseau Vival by Casino. Le service permet de gérer la relation client, les livraisons, la fidélité, le catalogue et les commandes en ligne.</p>
      <h2 style={{fontSize:18, margin:'24px 0 12px'}}>2. Abonnement</h2>
      <p>L'accès au service est réservé aux titulaires d'un abonnement valide. Trois formules sont proposées :</p>
      <ul><li><strong>Interne</strong> — 29 € HT/mois</li><li><strong>Boutique</strong> — 79 € HT/mois</li><li><strong>Boutique+</strong> — 149 € HT/mois</li></ul>
      <p>Une période d'essai gratuite de 30 jours est offerte à la création de chaque compte, sans engagement ni carte bancaire. À l'issue de l'essai, le service bascule en lecture seule tant qu'aucun abonnement n'est souscrit.</p>
      <h2 style={{fontSize:18, margin:'24px 0 12px'}}>3. Paiement</h2>
      <p>Le règlement s'effectue mensuellement par prélèvement automatique via le prestataire Stripe. En cas d'échec de paiement, l'abonnement passe en statut « suspendu » et le client dispose de 7 jours pour régulariser, faute de quoi l'abonnement est résilié.</p>
      <h2 style={{fontSize:18, margin:'24px 0 12px'}}>4. Durée et résiliation</h2>
      <p>L'abonnement est souscrit pour une durée indéterminée, renouvelable mensuellement par tacite reconduction. Il peut être résilié à tout moment depuis l'espace client, la résiliation prenant effet à la fin de la période en cours. Aucun remboursement au prorata n'est effectué.</p>
      <h2 style={{fontSize:18, margin:'24px 0 12px'}}>5. Disponibilité du service</h2>
      <p>{EDITOR.name} s'engage à fournir un service avec un objectif de disponibilité de 99 % sur une base mensuelle, hors maintenances planifiées. Aucune garantie de résultat n'est toutefois contractée. En cas d'indisponibilité prolongée (supérieure à 48 heures consécutives), un avoir pourra être accordé sur demande.</p>
      <h2 style={{fontSize:18, margin:'24px 0 12px'}}>6. Responsabilités</h2>
      <p>Le client est seul responsable des données qu'il saisit ou importe dans le service (produits, clients, tarifs, commandes). Il appartient au client de s'assurer que son usage du service respecte la réglementation en vigueur, notamment en matière fiscale et comptable. MonVival n'est pas une caisse enregistreuse au sens de la loi anti-fraude TVA française (NF525).</p>
      <h2 style={{fontSize:18, margin:'24px 0 12px'}}>7. Données</h2>
      <p>Les données du client lui appartiennent en propre. Il peut à tout moment les exporter depuis l'espace Réglages. La conservation des données se poursuit pendant 30 jours après résiliation avant suppression définitive.</p>
      <h2 style={{fontSize:18, margin:'24px 0 12px'}}>8. Droit applicable</h2>
      <p>Les présentes CGV sont soumises au droit français. Tout litige relatif à leur exécution ou interprétation relève de la compétence exclusive des tribunaux compétents du ressort de {EDITOR.name}.</p>
    </>
  );
}

function Confidentialite() {
  return (
    <>
      <h2 style={{fontSize:18, margin:'0 0 12px'}}>Responsable du traitement</h2>
      <p>{EDITOR.name} — {EDITOR.address} — <a href={`mailto:${EDITOR.email}`}>{EDITOR.email}</a></p>
      <h2 style={{fontSize:18, margin:'24px 0 12px'}}>Données collectées</h2>
      <p>Dans le cadre de l'utilisation de MonVival, nous traitons les catégories de données suivantes :</p>
      <ul>
        <li><strong>Identité</strong> — nom, prénom, email, numéro de téléphone de l'exploitant et des collaborateurs invités</li>
        <li><strong>Données magasin</strong> — nom commercial, adresse, SIRET, horaires, logo</li>
        <li><strong>Données clients finaux</strong> — nom, coordonnées, adresse de livraison, historique de commandes (saisies par l'exploitant)</li>
        <li><strong>Données de facturation</strong> — adresse de facturation et dernier quatre chiffres de la carte, stockées par notre sous-traitant Stripe</li>
        <li><strong>Journaux techniques</strong> — adresses IP, horodatage des connexions, erreurs éventuelles</li>
      </ul>
      <h2 style={{fontSize:18, margin:'24px 0 12px'}}>Finalités</h2>
      <p>Ces données sont utilisées pour : la fourniture du service, la facturation, le support client, l'envoi d'emails transactionnels (invitations, rappels de fin d'essai, reçus de paiement) et l'amélioration du service. Aucune donnée n'est revendue à des tiers à des fins commerciales.</p>
      <h2 style={{fontSize:18, margin:'24px 0 12px'}}>Sous-traitants</h2>
      <p>MonVival s'appuie sur les sous-traitants suivants, tous soumis à des accords de traitement conformes au RGPD :</p>
      <ul>
        <li><strong>Supabase</strong> (Singapour) — hébergement base de données et authentification, région UE (Francfort)</li>
        <li><strong>Vercel</strong> (États-Unis, certifié DPF) — hébergement de l'application web</li>
        <li><strong>Stripe</strong> (Irlande) — traitement des paiements par carte bancaire</li>
        <li><strong>Brevo</strong> (France) — envoi des emails transactionnels</li>
      </ul>
      <h2 style={{fontSize:18, margin:'24px 0 12px'}}>Durée de conservation</h2>
      <p>Les données actives sont conservées pendant la durée d'abonnement. Elles sont supprimées 30 jours après la résiliation, sauf obligations légales (factures : 10 ans).</p>
      <h2 style={{fontSize:18, margin:'24px 0 12px'}}>Vos droits</h2>
      <p>Conformément au Règlement Général sur la Protection des Données (RGPD), vous disposez des droits suivants sur vos données :</p>
      <ul>
        <li>Droit d'accès et de portabilité (export disponible dans Réglages &gt; Données)</li>
        <li>Droit de rectification</li>
        <li>Droit à l'effacement (suppression du compte dans Réglages &gt; Compte)</li>
        <li>Droit d'opposition et de limitation du traitement</li>
      </ul>
      <p>Pour exercer ces droits ou pour toute question, écrivez à <a href={`mailto:${EDITOR.email}`}>{EDITOR.email}</a>. Vous pouvez également introduire une réclamation auprès de la <a href="https://www.cnil.fr" target="_blank" rel="noreferrer">CNIL</a>.</p>
      <h2 style={{fontSize:18, margin:'24px 0 12px'}}>Cookies</h2>
      <p>MonVival utilise uniquement des cookies strictement nécessaires au fonctionnement du service (session d'authentification, préférences utilisateur). Aucun cookie de tracking publicitaire ou d'analyse comportementale n'est déposé.</p>
    </>
  );
}

function MarketplaceScreen() {
  const { showToast, setRoute } = useApp();
  const [q, setQ] = useState('');
  const [rayonFilter, setRayonFilter] = useState('all');
  const [items, setItems] = useState([]);
  const [stores, setStores] = useState({});
  const [loading, setLoading] = useState(true);
  const [importing, setImporting] = useState(null);
  const [importedIds, setImportedIds] = useState(new Set());

  const load = async () => {
    setLoading(true);
    // Produits partagés par les autres magasins
    const { data: prods } = await window.sb.from('products')
      .select('*')
      .eq('is_shared', true)
      .eq('deleted', false)
      .neq('store_id', VivalData._storeId)
      .limit(500);
    const storeIds = [...new Set((prods || []).map(p => p.store_id).filter(Boolean))];
    const { data: sts } = storeIds.length
      ? await window.sb.from('stores').select('id, name').in('id', storeIds)
      : { data: [] };
    const map = {};
    (sts || []).forEach(s => { map[s.id] = s.name; });
    setStores(map);
    setItems(prods || []);
    // Déjà importés
    const already = new Set(VivalData.PRODUCTS.filter(p => p.importedFrom).map(p => p.importedFrom));
    setImportedIds(already);
    setLoading(false);
  };
  useEffect(() => { load(); }, []);

  const importOne = async (p) => {
    setImporting(p.id);
    const { data: newId, error } = await window.sb.rpc('import_product_from_network', { p_source_id: p.id });
    setImporting(null);
    if (error) { showToast('Erreur: ' + error.message); return; }
    // Refresh local catalog
    const { data: newProd } = await window.sb.from('products').select('*').eq('id', newId).single();
    if (newProd) {
      // Add minimal mapping via dbToProduct (reuse the global one)
      const mapped = {
        id: newProd.id, rayon: newProd.rayon, name: newProd.name, brand: newProd.brand || '',
        format: newProd.format || '', price: Number(newProd.price) || 0, badges: [],
        sub: newProd.sub || 'Tout', byWeight: !!newProd.by_weight, stock: newProd.stock || 0,
        pinned: false, barcode: newProd.ean, image: newProd.image, isAlcohol: !!newProd.is_alcohol,
        tvaRate: Number(newProd.tva_rate) || 0.055, source: 'imported',
        storeId: newProd.store_id, isShared: false, importedFrom: newProd.imported_from, originStoreId: newProd.origin_store_id,
      };
      VivalData.PRODUCTS.push(mapped);
    }
    setImportedIds(prev => new Set([...prev, p.id]));
    showToast(`${p.name} importé dans votre catalogue`);
  };

  const filtered = items.filter(p => {
    if (rayonFilter !== 'all' && p.rayon !== rayonFilter) return false;
    if (q.trim()) {
      const qq = q.toLowerCase();
      return (p.name || '').toLowerCase().includes(qq) ||
             (p.brand || '').toLowerCase().includes(qq) ||
             (p.ean || '').includes(qq) ||
             (stores[p.store_id] || '').toLowerCase().includes(qq);
    }
    return true;
  });

  return (
    <>
      <div className="page-header">
        <div className="page-header-grow">
          <h1 className="page-title">Réseau Vival</h1>
          <p className="page-subtitle">{items.length} produits partagés par les autres magasins — importez ceux qui vous intéressent</p>
        </div>
        <Button variant="outline" icon="refresh" onClick={load}>Actualiser</Button>
      </div>
      <div style={{padding:'0 32px 16px', display:'flex', gap:10, flexWrap:'wrap'}}>
        <div style={{flex:'1 1 300px', minWidth:240}}>
          <Input icon="search" placeholder="Rechercher (nom, EAN, magasin)…" value={q} onChange={e => setQ(e.target.value)} />
        </div>
        <select value={rayonFilter} onChange={e => setRayonFilter(e.target.value)}
          style={{padding:'10px 12px', border:'1px solid var(--line)', borderRadius:8, fontSize:14, background:'#fff'}}>
          <option value="all">Tous rayons</option>
          {RAYONS.map(r => <option key={r.id} value={r.id}>{r.name}</option>)}
        </select>
      </div>
      {loading ? (
        <div className="empty-state"><Icon name="star" /><div>Chargement…</div></div>
      ) : filtered.length === 0 ? (
        <div className="empty-state">
          <Icon name="star" />
          <div className="cart-empty-title">Aucun produit partagé</div>
          <div style={{fontSize:13, maxWidth:420, textAlign:'center'}}>
            Les autres magasins Vival n'ont pas encore partagé de produits locaux. De votre côté, activez "Partager avec le réseau" sur vos produits pour les rendre accessibles aux autres.
          </div>
        </div>
      ) : (
        <div style={{padding:'0 32px 32px', display:'grid', gridTemplateColumns:'repeat(auto-fill, minmax(280px, 1fr))', gap:12}}>
          {filtered.map(p => {
            const already = importedIds.has(p.id);
            return (
              <div key={p.id} style={{background:'#fff', border:'1px solid var(--line-1)', borderRadius:'var(--r-lg)', padding:14, display:'flex', flexDirection:'column', gap:8}}>
                <div style={{display:'flex', gap:12}}>
                  <div style={{width:60, height:60, background:'var(--surface-2)', borderRadius:8, display:'flex', alignItems:'center', justifyContent:'center', overflow:'hidden', flexShrink:0}}>
                    {p.image ? <img src={p.image} alt="" style={{width:'100%', height:'100%', objectFit:'contain'}} /> : <Icon name={p.rayon} />}
                  </div>
                  <div style={{flex:1, minWidth:0}}>
                    <div style={{fontWeight:700, fontSize:14, lineHeight:'1.2'}}>{p.name}</div>
                    <div className="muted" style={{fontSize:11, marginTop:2}}>{p.brand} {p.format && '· ' + p.format}</div>
                    <div style={{fontSize:16, fontWeight:800, marginTop:4}}>{formatPrice(p.price).full}</div>
                  </div>
                </div>
                <div style={{fontSize:11, color:'var(--ink-3)', display:'flex', gap:6, alignItems:'center'}}>
                  <Icon name="shop" style={{width:12, height:12}} />
                  <span>Par <strong style={{color:'var(--ink-2)'}}>{stores[p.store_id] || 'Vival'}</strong></span>
                  {p.imported_count > 0 && <span style={{marginLeft:'auto', background:'#ecfccb', color:'#365314', padding:'2px 8px', borderRadius:10, fontWeight:600}}>{p.imported_count} magasin{p.imported_count>1?'s':''} du réseau l'{p.imported_count>1?'ont':'a'} adopté</span>}
                </div>
                <Button
                  variant={already ? 'ghost' : 'primary'}
                  size="sm"
                  icon={already ? 'check' : 'plus'}
                  block
                  disabled={already || importing === p.id}
                  onClick={() => importOne(p)}
                >
                  {already ? 'Déjà importé' : (importing === p.id ? '…' : 'Importer')}
                </Button>
              </div>
            );
          })}
        </div>
      )}
    </>
  );
}

function Root() {
  const path = window.location.pathname;
  const shopMatch = path.match(/^\/shop\/([a-z0-9-]+)\/?$/i);
  if (shopMatch) return <PublicShopRoot storeCode={shopMatch[1]} />;
  const legalMatch = path.match(/^\/legal\/(cgv|mentions|confidentialite)\/?$/i);
  if (legalMatch) return <LegalPage page={legalMatch[1]} />;
  const trackMatch = path.match(/^\/suivi\/([a-f0-9]+)\/?$/i);
  if (trackMatch) return <OrderTracking token={trackMatch[1]} />;
  if (path.startsWith('/admin')) return <SuperAdminRoot />;
  if (path === '/app' || path === '/app/') return <AdminRoot />;
  if (path === '/' || path === '' || path === '/index.html') return <Homepage />;
  return <AdminRoot />;
}

function AdminRoot() {
  const [session, setSession] = useState(undefined); // undefined = checking, null = none
  const [hydrated, setHydrated] = useState(!!window.VivalData._hydrated);
  const [error, setError] = useState(null);

  useEffect(() => {
    window.sb.auth.getSession().then(({ data }) => setSession(data.session || null));
    const { data: sub } = window.sb.auth.onAuthStateChange((_e, s) => setSession(s || null));
    return () => sub?.subscription?.unsubscribe();
  }, []);

  // Stale-while-revalidate : on hydrate depuis le cache (instant) puis on rafraichit en arriere-plan.
  // Affichage du dashboard en <100ms si cache dispo, sinon fallback sur Supabase.
  const load = () => {
    setError(null);
    const hasCache = window.hydrateFromCache?.();
    if (hasCache) {
      setHydrated(true);
      // Refresh en background — pas de splash, pas de blocage
      window.hydrateFromSupabase().then(() => {
        try { window.dispatchEvent(new CustomEvent('vival-data-refreshed')); } catch {}
      }).catch((e) => {
        console.warn('[Hydrate] Background refresh failed, cache conserve', e);
      });
    } else {
      // Pas de cache: on DOIT attendre Supabase
      window.hydrateFromSupabase().then(() => setHydrated(true)).catch((e) => setError(e));
    }
  };
  useEffect(() => {
    if (session && !hydrated) load();
    if (!session) setHydrated(false);
  }, [session]);

  if (session === undefined) return <LoadingSplash />;
  if (!session) return <DeviceBinding onAuth={() => window.sb.auth.getSession().then(({ data }) => setSession(data.session || null))} />;
  // Accepter une invitation si un token est dans l'URL
  const inviteToken = new URLSearchParams(window.location.search).get('invite');
  if (inviteToken) return <AcceptInvitation token={inviteToken} session={session} onDone={() => {
    history.replaceState(null, '', window.location.pathname);
    window.sb.auth.refreshSession().then(({ data }) => setSession(data.session || null));
  }} />;
  // Compte créé mais pas encore rattaché à un store (ex: après confirmation email)
  const needsStoreSetup = !session.user?.app_metadata?.store_id;
  if (needsStoreSetup) return <CompleteStoreSetup session={session} onDone={() => window.sb.auth.refreshSession().then(({ data }) => setSession(data.session || null))} />;
  if (error) return <LoadingSplash error={error} onRetry={load} />;
  if (!hydrated) return <LoadingSplash />;
  return <Gate />;
}

function AcceptInvitation({ token, session, onDone }) {
  const [state, setState] = useState('loading'); // loading | success | error
  const [msg, setMsg] = useState('');
  useEffect(() => {
    (async () => {
      const { data, error } = await window.sb.rpc('accept_invitation', { p_token: token });
      if (error) { setState('error'); setMsg(error.message); return; }
      setState('success');
      setMsg(`Vous avez rejoint avec succès (rôle ${data.role}). Redirection…`);
      setTimeout(() => onDone(), 1500);
    })();
  }, [token]);
  return (
    <div className="pin-screen">
      <div className="pin-card" style={{maxWidth:420}}>
        <div className="pin-logo"><img src="/assets/vival-logo.png" alt="Vival" /></div>
        <h1 className="pin-title">
          {state === 'loading' && 'Traitement de l\'invitation…'}
          {state === 'success' && 'Invitation acceptée'}
          {state === 'error' && 'Impossible d\'accepter'}
        </h1>
        <p className="pin-subtitle" style={{margin:'20px 0'}}>
          {state === 'loading' && `Connecté en tant que ${session.user.email}`}
          {state !== 'loading' && msg}
        </p>
        {state === 'error' && <Button variant="primary" block onClick={onDone}>Continuer</Button>}
      </div>
    </div>
  );
}

function CompleteStoreSetup({ session, onDone }) {
  const [storeName, setStoreName] = useState(session.user?.user_metadata?.store_name || '');
  const [storeAddress, setStoreAddress] = useState('');
  const [storePhone, setStorePhone] = useState('');
  const [casinoCode, setCasinoCode] = useState('');
  const [err, setErr] = useState('');
  const [loading, setLoading] = useState(false);
  const submit = async (e) => {
    e?.preventDefault();
    if (!storeName.trim()) { setErr('Nom requis'); return; }
    const casinoCleaned = casinoCode.trim().toUpperCase();
    if (!/^E\d{3,6}$/.test(casinoCleaned)) {
      setErr('Code client Casino invalide (format attendu : E suivi de 3 à 6 chiffres, ex. E2783)');
      return;
    }
    setLoading(true); setErr('');
    const { data: newStoreId, error } = await window.sb.rpc('create_store_for_user', {
      p_name: storeName.trim(),
      p_address: storeAddress.trim(),
      p_phone: storePhone.trim(),
      p_casino_customer_code: casinoCleaned
    });
    if (error) { setLoading(false); setErr(error.message); return; }
    // Envoie email de bienvenue (fire-and-forget — via send-notification avec auth session)
    try {
      await window.sb.functions.invoke('send-welcome', { body: { store_id: newStoreId } });
    } catch {}
    setLoading(false);
    onDone();
  };
  return (
    <div className="pin-screen">
      <form onSubmit={submit} className="pin-card" style={{maxWidth:440}}>
        <div className="pin-logo"><img src="assets/vival-logo.png" alt="Vival" /></div>
        <h1 className="pin-title">Finaliser votre magasin</h1>
        <p className="pin-subtitle">Connecté en tant que <strong>{session.user.email}</strong></p>
        <div style={{display:'grid', gap:10, marginTop:20, textAlign:'left'}}>
          <div><div className="kpi-label" style={{marginBottom:4}}>Nom du magasin</div>
            <Input value={storeName} onChange={e => setStoreName(e.target.value)} autoFocus required /></div>
          <div><div className="kpi-label" style={{marginBottom:4}}>Adresse</div>
            <Input value={storeAddress} onChange={e => setStoreAddress(e.target.value)} /></div>
          <div><div className="kpi-label" style={{marginBottom:4}}>Téléphone</div>
            <Input value={storePhone} onChange={e => setStorePhone(e.target.value)} /></div>
          <div>
            <div className="kpi-label" style={{marginBottom:4}}>Code client Casino <span style={{color:'var(--danger, #c02424)'}}>*</span></div>
            <Input
              value={casinoCode}
              onChange={e => setCasinoCode(e.target.value.toUpperCase())}
              placeholder="E2783"
              required
            />
            <div style={{fontSize:11, color:'var(--ink-3)', marginTop:4, lineHeight:1.4}}>
              Indispensable pour la synchro des factures et livraisons. Tu le trouves en haut de toutes tes factures Casino : <strong>Magasin : E…</strong>
            </div>
          </div>
          {err && <div style={{color:'var(--danger, #c02424)', fontSize:13}}>{err}</div>}
          <Button variant="primary" size="lg" type="submit" block disabled={loading || !storeName.trim() || !casinoCode.trim()}>{loading ? '…' : 'Créer mon magasin'}</Button>
          <button type="button" onClick={async () => { await window.sb.auth.signOut(); location.reload(); }}
            style={{background:'transparent', border:0, color:'var(--ink-3)', fontSize:12, cursor:'pointer', textDecoration:'underline'}}>
            Se déconnecter
          </button>
        </div>
      </form>
    </div>
  );
}

function DeviceBinding({ onAuth }) {
  const [mode, setMode] = useState('signin'); // signin | signup
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [storeName, setStoreName] = useState('');
  const [storeAddress, setStoreAddress] = useState('');
  const [storePhone, setStorePhone] = useState('');
  const [err, setErr] = useState('');
  const [loading, setLoading] = useState(false);

  const doSignin = async (e) => {
    e?.preventDefault();
    setErr(''); setLoading(true);
    const { error } = await window.sb.auth.signInWithPassword({ email: email.trim(), password });
    setLoading(false);
    if (error) { setErr(error.message); return; }
    onAuth();
  };

  const demoLogin = async (role) => {
    setErr(''); setLoading(true);
    // Connexion en tant qu'admin Le Grand-Lemps (donne accès full)
    const { error } = await window.sb.auth.signInWithPassword({ email: 'laurent@vival-chabons.fr', password: 'vival1234!' });
    if (error) { setErr('Erreur demo : ' + error.message); setLoading(false); return; }
    // Pré-selectionne le rôle PIN pour que Gate entre directement sur le bon profil
    const demoProfile = {
      admin:    { id: null, name: 'Laurent', role: 'admin' },
      caissier: { id: null, name: 'Sophie',  role: 'caissier' },
      livreur:  { id: null, name: 'Marc',    role: 'livreur' },
    }[role];
    sessionStorage.setItem('vival_cashier', JSON.stringify(demoProfile));
    sessionStorage.setItem('vival-auth', '1');
    setLoading(false);
    onAuth();
  };

  const doSignup = async (e) => {
    e?.preventDefault();
    setErr(''); setLoading(true);
    if (password.length < 8) { setErr('Le mot de passe doit faire au moins 8 caractères'); setLoading(false); return; }
    if (!storeName.trim()) { setErr('Nom du magasin requis'); setLoading(false); return; }

    // 1. Signup
    const { data: signupData, error: signupErr } = await window.sb.auth.signUp({
      email: email.trim(),
      password,
      options: { data: { store_name: storeName.trim() } }
    });
    if (signupErr) { setErr(signupErr.message); setLoading(false); return; }
    if (!signupData.session) {
      setErr('Un email de confirmation a été envoyé. Confirmez votre adresse puis revenez vous connecter.');
      setLoading(false);
      return;
    }
    // 2. Crée le store et le rattache
    const { data: storeId, error: rpcErr } = await window.sb.rpc('create_store_for_user', {
      p_name: storeName.trim(), p_address: storeAddress.trim(), p_phone: storePhone.trim()
    });
    if (rpcErr) { setErr('Magasin: ' + rpcErr.message); setLoading(false); return; }
    // 3. Force refresh de la session pour récupérer le claim store_id
    await window.sb.auth.refreshSession();
    setLoading(false);
    onAuth();
  };

  return (
    <div className="pin-screen">
      <form onSubmit={mode === 'signin' ? doSignin : doSignup} className="pin-card" style={{maxWidth:440}}>
        <div className="pin-logo"><img src="assets/vival-logo.png" alt="Vival" /></div>
        <h1 className="pin-title">{mode === 'signin' ? 'Lier cet appareil' : 'Nouveau magasin Vival'}</h1>
        <p className="pin-subtitle">{mode === 'signin' ? 'Connectez-vous avec le compte du magasin' : 'Créez votre espace en 30 secondes'}</p>

        <div style={{display:'grid', gap:10, marginTop:20, textAlign:'left'}}>
          {mode === 'signup' && <>
            <div><div className="kpi-label" style={{marginBottom:4}}>Nom du magasin</div>
              <Input value={storeName} onChange={e => setStoreName(e.target.value)} placeholder="Vival Villeurbanne" autoFocus required /></div>
            <div><div className="kpi-label" style={{marginBottom:4}}>Adresse</div>
              <Input value={storeAddress} onChange={e => setStoreAddress(e.target.value)} placeholder="12 rue de la République, 69100 Villeurbanne" /></div>
            <div><div className="kpi-label" style={{marginBottom:4}}>Téléphone</div>
              <Input value={storePhone} onChange={e => setStorePhone(e.target.value)} placeholder="04 78 00 00 00" /></div>
            <div style={{height:1, background:'var(--line-2)', margin:'6px 0'}} />
          </>}
          <div><div className="kpi-label" style={{marginBottom:4}}>Email {mode === 'signup' && <span className="muted" style={{fontWeight:400}}>(compte administrateur)</span>}</div>
            <Input type="email" value={email} onChange={e => setEmail(e.target.value)} autoFocus={mode === 'signin'} required /></div>
          <div><div className="kpi-label" style={{marginBottom:4}}>Mot de passe {mode === 'signup' && <span className="muted" style={{fontWeight:400}}>(8 caractères min.)</span>}</div>
            <Input type="password" value={password} onChange={e => setPassword(e.target.value)} required /></div>
          {err && <div style={{color:'var(--danger, #c02424)', fontSize:13}}>{err}</div>}
          <Button variant="primary" size="lg" type="submit" block disabled={loading}>
            {loading ? '…' : (mode === 'signin' ? 'Lier cet appareil' : 'Créer mon magasin')}
          </Button>
          <button type="button" onClick={() => { setMode(mode === 'signin' ? 'signup' : 'signin'); setErr(''); }}
            style={{background:'transparent', border:0, color:'var(--ink-3)', fontSize:13, cursor:'pointer', textDecoration:'underline', marginTop:4}}>
            {mode === 'signin' ? 'Pas encore de compte ? Créer un nouveau magasin' : 'Déjà un compte ? Me connecter'}
          </button>
          <p className="muted" style={{fontSize:11, margin:0, textAlign:'center'}}>
            {mode === 'signin' ? 'Cette étape n\'a lieu qu\'une fois par appareil.' : 'Réservé aux magasins du réseau Vival by Casino.'}
          </p>
          {mode === 'signup' && (
            <p className="muted" style={{fontSize:11, margin:'4px 0 0', textAlign:'center'}}>
              En créant un compte, vous acceptez nos <a href="/legal/cgv" style={{color:'var(--ink-3)'}}>CGV</a> et notre <a href="/legal/confidentialite" style={{color:'var(--ink-3)'}}>politique de confidentialité</a>.
            </p>
          )}
          {mode === 'signin' && (
            <div style={{marginTop:16, paddingTop:16, borderTop:'1px dashed #e5e7eb'}}>
              <div style={{fontSize:11, color:'var(--ink-3)', textAlign:'center', marginBottom:10, textTransform:'uppercase', letterSpacing:'0.05em', fontWeight:700}}>Démo rapide — Vival Le Grand-Lemps</div>
              <div style={{display:'grid', gridTemplateColumns:'1fr 1fr 1fr', gap:6}}>
                <button type="button" onClick={() => demoLogin('admin')} disabled={loading}
                  style={{padding:'10px 6px', background:'#dc2626', color:'#fff', border:0, borderRadius:8, cursor:'pointer', fontSize:12, fontWeight:700, letterSpacing:'0.02em'}}>
                  Admin
                </button>
                <button type="button" onClick={() => demoLogin('caissier')} disabled={loading}
                  style={{padding:'10px 6px', background:'#ea580c', color:'#fff', border:0, borderRadius:8, cursor:'pointer', fontSize:12, fontWeight:700, letterSpacing:'0.02em'}}>
                  Caissier
                </button>
                <button type="button" onClick={() => demoLogin('livreur')} disabled={loading}
                  style={{padding:'10px 6px', background:'#0891b2', color:'#fff', border:0, borderRadius:8, cursor:'pointer', fontSize:12, fontWeight:700, letterSpacing:'0.02em'}}>
                  Livreur
                </button>
              </div>
              <p className="muted" style={{fontSize:10, margin:'8px 0 0', textAlign:'center'}}>
                Accès immédiat sans mot de passe — données de démonstration
              </p>
            </div>
          )}
          <div style={{textAlign:'center', fontSize:11, marginTop:12, display:'flex', justifyContent:'center', gap:12, color:'var(--ink-4)'}}>
            <a href="/legal/mentions" style={{color:'var(--ink-4)'}}>Mentions légales</a>
            <a href="/legal/cgv" style={{color:'var(--ink-4)'}}>CGV</a>
            <a href="/legal/confidentialite" style={{color:'var(--ink-4)'}}>Confidentialité</a>
          </div>
        </div>
      </form>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<Root />);
