/**
 * Oveyo PWA — App Root
 * 5 onglets + vue commerçant + sheets
 */

const LuApp = window.LucideReact || {};
const {
  GradientBar: GBar,
  FlashProvider: FP, useFlash: ufApp, Sheet: ShComp, Spinner: SpApp,
} = window.OveyoUI;
const {
  loadState: lS, saveState: sS, defaultState: dS, api: apiApp,
  useOnlineStatus: uOn, useCatalog: ucApp,
} = window.OveyoState;
const {
  HomePage, TopBar, MapPage, ReportPage, PharmacyPage, ProfilePage, MerchantView,
} = window.OveyoPages;

// ============================================================
// TAB BAR — Vivinter style
// ============================================================
const TabBar = ({ active, onChange }) => {
  const { Home, MapPin, Camera, Cross, User } = LuApp;
  const tabs = [
    { id: 'home', icon: Home, label: 'Accueil' },
    { id: 'map', icon: MapPin, label: 'Carte' },
    { id: 'report', icon: Camera, label: 'Partager', primary: true },
    { id: 'pharmacy', icon: Cross, label: 'Pharmacie' },
    { id: 'profile', icon: User, label: 'Moi' },
  ];

  return (
    <div
      className="fixed bottom-0 left-0 right-0 px-3 pt-2 z-50"
      style={{
        // V3.8 P3 — Tabbar 100% opaque pour empêcher la carte/page de derrière
        // d'être visible à travers (le `backdropFilter: blur` créait un
        // stacking context indépendant qui ignorait le z-index du backdrop sheet).
        background: '#FFFFFF',
        borderTop: '1px solid #EAEEF4',
        paddingBottom: 'calc(env(safe-area-inset-bottom, 0) + 16px)',
      }}
    >
      <div className="flex items-center justify-around max-w-[420px] mx-auto">
        {tabs.map((tab) => {
          const Icon = tab.icon;
          const isActive = active === tab.id;
          if (tab.primary) {
            return (
              <button
                key={tab.id}
                onClick={() => onChange(tab.id)}
                className="flex flex-col items-center -mt-7"
                aria-label={tab.label}
              >
                <div
                  className="w-14 h-14 rounded-full flex items-center justify-center transition-transform active:scale-95"
                  style={{
                    background: isActive ? '#F4632B' : '#102A43',
                    boxShadow: '0 8px 20px rgba(244,99,43,0.35)',
                  }}
                >
                  <Icon size={22} className="text-white" strokeWidth={2.2} />
                </div>
                <span
                  className="text-[10px] font-bold mt-1"
                  style={{ color: isActive ? '#F4632B' : '#102A43' }}
                >{tab.label}</span>
              </button>
            );
          }
          return (
            <button
              key={tab.id}
              onClick={() => onChange(tab.id)}
              className="flex flex-col items-center gap-1 px-2 py-1"
              aria-label={tab.label}
            >
              <Icon
                size={20}
                strokeWidth={isActive ? 2.5 : 2}
                style={{ color: isActive ? '#102A43' : '#9AA5B5' }}
              />
              <span
                className="text-[10px] font-semibold"
                style={{ color: isActive ? '#102A43' : '#9AA5B5' }}
              >{tab.label}</span>
            </button>
          );
        })}
      </div>
    </div>
  );
};

// ============================================================
// SHEET : LOGIN OTP WhatsApp
// ============================================================
const LoginSheet = ({ open, onClose, onSuccess }) => {
  const { ChevronRight, Loader2, Lock } = LuApp;
  const flash = ufApp();
  const [step, setStep] = React.useState('phone');
  const [phone, setPhone] = React.useState('');
  const [code, setCode] = React.useState('');
  const [loading, setLoading] = React.useState(false);

  const requestOtp = async () => {
    const cleanPhone = '221' + phone.replace(/\D/g, '');
    if (cleanPhone.length < 11) {
      flash.show('error', 'Numéro invalide');
      return;
    }
    setLoading(true);
    try {
      await apiApp('/api/pwa/otp/request', {
        method: 'POST',
        body: JSON.stringify({ phone: cleanPhone }),
      });
      setStep('code');
      flash.show('success', `Code envoyé sur WhatsApp au +${cleanPhone}`);
    } catch (e) {
      flash.show('error', "Impossible d'envoyer le code");
    } finally {
      setLoading(false);
    }
  };

  const verifyOtp = async () => {
    const cleanPhone = '221' + phone.replace(/\D/g, '');
    setLoading(true);
    try {
      const data = await apiApp('/api/pwa/otp/verify', {
        method: 'POST',
        body: JSON.stringify({ phone: cleanPhone, code }),
      });
      onSuccess({
        phone: cleanPhone,
        pseudonym: data.pseudonym,
        neighborhood_slug: data.neighborhood_slug,
        is_ambassador: data.is_ambassador,
      });
      flash.show('success', '✅ Connexion réussie');
      setStep('phone'); setPhone(''); setCode('');
    } catch (e) {
      flash.show('error', 'Code invalide ou expiré');
    } finally {
      setLoading(false);
    }
  };

  return (
    <ShComp open={open} onClose={onClose} title={step === 'phone' ? 'Me connecter' : 'Code reçu'}>
      {step === 'phone' ? (
        <div className="space-y-4">
          <p className="text-sm text-slate">
            On va t'envoyer un code à 6 chiffres par WhatsApp pour vérifier ton numéro.
          </p>
          <div>
            <label className="text-[11px] font-bold uppercase tracking-wider text-slate">Numéro WhatsApp</label>
            <div className="mt-1.5 flex gap-2">
              <input
                type="text" value="+221" readOnly
                className="w-20 p-3 rounded-xl text-center font-semibold text-ink"
                style={{ background: '#F1F4F9' }}
              />
              <input
                type="tel" value={phone} onChange={(e) => setPhone(e.target.value)}
                placeholder="77 123 45 67"
                className="flex-1 p-3 rounded-xl text-base outline-none"
                style={{ background: '#F1F4F9' }}
                autoComplete="tel-national" inputMode="numeric"
              />
            </div>
            <div className="text-[11px] text-slate mt-1">Format : 77 / 78 / 76 + 7 chiffres</div>
          </div>
          <button
            onClick={requestOtp}
            disabled={loading}
            className="w-full py-3.5 rounded-xl font-display font-bold text-white text-sm flex items-center justify-center gap-2 disabled:opacity-60"
            style={{ background: '#F4632B' }}
          >
            {loading ? <Loader2 size={16} className="animate-spin" /> : <ChevronRight size={16} />}
            Recevoir le code
          </button>
          <div className="flex items-start gap-2 p-3 rounded-xl bg-bg text-[11px] text-slate" style={{ background: '#F1F4F9' }}>
            <Lock size={12} className="flex-shrink-0 mt-0.5" />
            <span>Ton numéro reste privé. Il sert à attribuer tes prix partagés et débloquer ton espace ambassadeur.</span>
          </div>
        </div>
      ) : (
        <div className="space-y-4">
          <p className="text-sm text-slate">
            Saisis le code à 6 chiffres reçu sur WhatsApp au <strong className="text-ink">+221 {phone}</strong>
          </p>
          <input
            type="text"
            value={code}
            onChange={(e) => setCode(e.target.value.replace(/\D/g, '').slice(0, 6))}
            placeholder="000000"
            className="w-full p-4 rounded-xl text-center font-display font-bold text-3xl outline-none"
            style={{ background: '#F1F4F9', letterSpacing: '0.4em' }}
            inputMode="numeric" pattern="[0-9]{6}" maxLength={6} autoFocus
          />
          <button
            onClick={verifyOtp}
            disabled={loading || code.length !== 6}
            className="w-full py-3.5 rounded-xl font-display font-bold text-white text-sm flex items-center justify-center gap-2 disabled:opacity-60"
            style={{ background: '#F4632B' }}
          >
            {loading ? <Loader2 size={16} className="animate-spin" /> : null}
            Valider
          </button>
          <button
            onClick={() => { setStep('phone'); setCode(''); }}
            className="w-full py-2 text-sm text-slate font-semibold"
          >Modifier le numéro</button>
        </div>
      )}
    </ShComp>
  );
};

// ============================================================
// SHEET : ALERTE FRAUDE
// ============================================================
const ReportFraudSheet = ({ open, onClose, state, onOpenSheet }) => {
  const { Send, Shield, Loader2, Lock } = LuApp;
  const flash = ufApp();
  const [description, setDescription] = React.useState('');
  const [shopName, setShopName] = React.useState('');
  // V3.8 — champs libres au lieu d'un select des 50 quartiers
  const [quartierInput, setQuartierInput] = React.useState('');
  const [villeInput, setVilleInput] = React.useState('Dakar');
  const [loading, setLoading] = React.useState(false);

  // V4.0 — Login obligatoire pour signaler une fraude
  // (l'affichage reste anonyme côté communauté, mais on trace côté backend
  // pour empêcher les abus de signalement)
  const userPhone = state?.user?.phone;
  const isLoggedIn = typeof userPhone === 'string' && userPhone.length >= 10;

  const goToLogin = () => {
    onClose();
    if (typeof onOpenSheet === 'function') {
      setTimeout(() => onOpenSheet('login'), 300);
    }
  };

  const submit = async () => {
    if (!isLoggedIn) {
      flash.show('info', 'Connecte-toi avec WhatsApp pour signaler un écart de prix');
      goToLogin();
      return;
    }
    // V3.8 — validation client AVANT l'envoi (le backend exige min 15 chars)
    const desc = description.trim();
    if (desc.length < 15) {
      flash.show('error', 'Décris l\'écart en au moins 15 caractères');
      return;
    }
    setLoading(true);
    try {
      await apiApp('/api/pwa/fraud-alert', {
        method: 'POST',
        body: JSON.stringify({
          description: desc,
          shop_name: shopName.trim() || null,
          neighborhood_name: quartierInput.trim() || null,
          city_name: villeInput.trim() || null,
          reported_by: userPhone, // V4.0 — tracé côté backend
        }),
      });
      flash.show('success', '✅ Alerte envoyée anonymement');
      setDescription(''); setShopName('');
      setQuartierInput(''); setVilleInput('Dakar');
      onClose();
    } catch (e) {
      const status = e?.status;
      if (status === 422) {
        flash.show('error', 'Description trop courte ou invalide (min 15 caractères)');
      } else if (status === 429) {
        flash.show('error', "Trop d'alertes récentes, réessaie dans 1h");
      } else {
        flash.show('error', "Erreur d'envoi, réessaie");
      }
    } finally { setLoading(false); }
  };

  // V4.0 — Vue Connexion requise dans le sheet
  if (!isLoggedIn) {
    return (
      <ShComp open={open} onClose={onClose} title="Signaler un écart de prix">
        <div className="space-y-4 pt-2">
          <div className="rounded-xl p-5 text-center" style={{ background: '#FEEEE3' }}>
            <div className="w-14 h-14 mx-auto rounded-2xl flex items-center justify-center mb-3" style={{ background: 'white' }}>
              <Lock size={26} style={{ color: '#F4632B' }} strokeWidth={2} />
            </div>
            <div className="font-display font-bold text-base text-ink mb-1">Connexion requise</div>
            <div className="text-xs text-slate leading-relaxed mb-4">
              Pour signaler un écart de prix, tu dois te connecter avec WhatsApp.
              <br />Ton signalement reste 100% anonyme pour la communauté. Aucun numéro n'est partagé.
            </div>
            <button
              onClick={goToLogin}
              className="w-full py-3 rounded-xl font-bold text-white text-sm"
              style={{ background: '#F4632B' }}
            >
              <span aria-hidden="true">📱</span> Me connecter avec WhatsApp
            </button>
          </div>
        </div>
      </ShComp>
    );
  }

  return (
    <ShComp open={open} onClose={onClose} title="Signaler un écart de prix">
      <div className="space-y-4">
        <div className="flex items-start gap-3 p-3 rounded-xl" style={{ background: '#FEEEE3' }}>
          <Shield size={18} style={{ color: '#F4632B' }} className="flex-shrink-0 mt-0.5" />
          <div className="text-xs text-orange leading-relaxed">
            <strong>🔒 100% anonyme.</strong> Aucun numéro de téléphone n'est partagé. Seul le prix compte. On veille à l'intérêt commun, ce n'est pas une dénonciation.
          </div>
        </div>
        <div>
          <label className="text-[11px] font-bold uppercase tracking-wider text-slate">Décris l'écart constaté</label>
          <textarea
            value={description} onChange={(e) => setDescription(e.target.value)}
            rows={4}
            placeholder="Ex : Le sucre est vendu 1000 F au lieu de 600 F..."
            className="mt-1.5 w-full p-3 rounded-xl text-sm outline-none resize-none"
            style={{ background: '#F1F4F9' }}
          />
          <div className="mt-1 text-[10px] text-slate">
            {description.trim().length < 15
              ? `${description.trim().length}/15 caractères minimum`
              : '✓ Description suffisante'}
          </div>
        </div>
        <div>
          <label className="text-[11px] font-bold uppercase tracking-wider text-slate">Boutique (facultatif)</label>
          <input
            type="text" value={shopName} onChange={(e) => setShopName(e.target.value)}
            placeholder="Nom de la boutique"
            className="mt-1.5 w-full p-3 rounded-xl text-sm outline-none"
            style={{ background: '#F1F4F9' }}
          />
        </div>
        <div className="grid grid-cols-2 gap-2">
          <div>
            <label className="text-[11px] font-bold uppercase tracking-wider text-slate">Quartier</label>
            <input
              type="text"
              value={quartierInput}
              onChange={(e) => setQuartierInput(e.target.value)}
              placeholder="Ton quartier"
              className="mt-1.5 w-full p-3 rounded-xl text-sm outline-none"
              style={{ background: '#F1F4F9' }}
            />
          </div>
          <div>
            <label className="text-[11px] font-bold uppercase tracking-wider text-slate">Ville</label>
            <input
              type="text"
              value={villeInput}
              onChange={(e) => setVilleInput(e.target.value)}
              placeholder="Ville"
              className="mt-1.5 w-full p-3 rounded-xl text-sm outline-none"
              style={{ background: '#F1F4F9' }}
            />
          </div>
        </div>
        <button
          onClick={submit} disabled={loading || description.trim().length < 15}
          className="w-full py-3.5 rounded-xl font-bold text-white flex items-center justify-center gap-2 disabled:opacity-60"
          style={{ background: '#F4632B' }}
        >
          {loading ? <Loader2 size={16} className="animate-spin" /> : <Send size={16} />}
          Envoyer anonymement
        </button>
      </div>
    </ShComp>
  );
};

// ============================================================
// SHEET : MES SIGNALEMENTS (paginated)
// ============================================================
const MyReportsSheet = ({ open, onClose, state }) => {
  const { Eye, Loader2, TrendingUp, Check, X } = LuApp;
  const [items, setItems] = React.useState([]);
  const [loading, setLoading] = React.useState(false);
  const [total, setTotal] = React.useState(0);

  React.useEffect(() => {
    if (!open || !state.user.phone) return;
    setLoading(true);
    apiApp(`/api/pwa/observations/mine?phone=${encodeURIComponent(state.user.phone)}&limit=50`)
      .then(d => { setItems(d.items || []); setTotal(d.total || 0); })
      .catch(() => setItems([]))
      .finally(() => setLoading(false));
  }, [open, state.user.phone]);

  return (
    <ShComp open={open} onClose={onClose} title="Mes prix partagés">
      {loading ? (
        <div className="text-center py-10"><SpApp /></div>
      ) : items.length === 0 ? (
        <div className="text-center py-10">
          <div className="text-sm text-slate">Aucun prix partagé pour l'instant.</div>
          <div className="text-xs text-slate mt-1">Va sur l'onglet Partager pour commencer !</div>
        </div>
      ) : (
        <div className="space-y-2">
          <div className="text-xs text-slate mb-2">{total} prix partagé{total > 1 ? 's' : ''} au total</div>
          {items.map((r, i) => {
            const overpriced = r.price && r.price_official && r.price > r.price_official;
            return (
              <div key={r.id} className="bg-white rounded-xl p-3 flex items-center gap-3" style={{ border: '1px solid #EAEEF4' }}>
                <div className="text-2xl flex-shrink-0">{r.icon}</div>
                <div className="flex-1 min-w-0">
                  <div className="text-sm font-semibold text-ink truncate">{r.product_name}</div>
                  <div className="text-[11px] text-slate truncate">
                    {r.shop_name || 'Boutique non précisée'} · {r.neighborhood_name}
                  </div>
                  <div className="text-[10px] text-slate mt-0.5">
                    {new Date(r.created_at + 'Z').toLocaleString('fr-FR', { day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit' })}
                  </div>
                </div>
                <div className="text-right flex-shrink-0">
                  {r.price ? (
                    <>
                      <div className="font-display font-bold text-sm" style={{ color: r.is_abuse ? '#F4632B' : overpriced ? '#F4632B' : '#0F1F36' }}>
                        {r.price} F
                      </div>
                      {r.is_abuse && <div className="text-[9px] font-bold uppercase text-orange">Abus</div>}
                      {!r.available && <div className="text-[9px] font-bold uppercase text-slate">Rupture</div>}
                    </>
                  ) : (
                    <div className="text-[10px] font-bold uppercase text-slate">—</div>
                  )}
                </div>
              </div>
            );
          })}
        </div>
      )}
    </ShComp>
  );
};

// ============================================================
// SHEET : MES BADGES (gagnés + verrouillés)
// ============================================================
const MyBadgesSheet = ({ open, onClose, state }) => {
  const [allBadges, setAllBadges] = React.useState([]);
  const [loading, setLoading] = React.useState(false);

  React.useEffect(() => {
    if (!open || !state.user.phone) return;
    setLoading(true);
    apiApp(`/api/pwa/ambassador/me?phone=${encodeURIComponent(state.user.phone)}`)
      .then(d => setAllBadges(d.all_badges || []))
      .catch(() => setAllBadges([]))
      .finally(() => setLoading(false));
  }, [open, state.user.phone]);

  const earned = allBadges.filter(b => b.earned);
  const locked = allBadges.filter(b => !b.earned);

  return (
    <ShComp open={open} onClose={onClose} title="Mes badges 🏆">
      {loading ? (
        <div className="text-center py-10"><SpApp /></div>
      ) : (
        <div className="space-y-4">
          {earned.length > 0 && (
            <div>
              <div className="text-[11px] font-bold uppercase tracking-wider text-slate mb-2">Débloqués ({earned.length})</div>
              <div className="grid grid-cols-2 gap-2">
                {earned.map((b, i) => (
                  <div key={i} className="bg-white rounded-xl p-3" style={{ border: '1px solid #EAEEF4' }}>
                    <div className="flex items-center gap-2 mb-1">
                      <div className="text-2xl">{b.icon}</div>
                      <div className="text-[10px] font-bold uppercase tracking-wide text-success">Acquis</div>
                    </div>
                    <div className="text-xs font-bold text-ink">{b.name}</div>
                    <div className="text-[10px] text-slate mt-0.5 leading-tight">{b.description}</div>
                  </div>
                ))}
              </div>
            </div>
          )}
          {locked.length > 0 && (
            <div>
              <div className="text-[11px] font-bold uppercase tracking-wider text-slate mb-2">À débloquer ({locked.length})</div>
              <div className="grid grid-cols-2 gap-2">
                {locked.map((b, i) => (
                  <div key={i} className="rounded-xl p-3" style={{ background: '#F1F4F9', border: '1px solid #EAEEF4', opacity: 0.65 }}>
                    <div className="flex items-center gap-2 mb-1">
                      <div className="text-2xl grayscale">{b.icon}</div>
                      <div className="text-[10px] font-bold uppercase tracking-wide text-slate">Verrouillé</div>
                    </div>
                    <div className="text-xs font-bold text-ink">{b.name}</div>
                    <div className="text-[10px] text-slate mt-0.5 leading-tight">{b.description}</div>
                    {!!b.threshold && (
                      <div className="text-[10px] text-orange mt-1 font-semibold">Objectif : {b.threshold} prix partagés</div>
                    )}
                  </div>
                ))}
              </div>
            </div>
          )}
          {earned.length === 0 && locked.length === 0 && (
            <div className="text-center py-10 text-sm text-slate">Aucun badge disponible</div>
          )}
        </div>
      )}
    </ShComp>
  );
};

// ============================================================
// SHEET : MODE HORS-LIGNE
// ============================================================
const OfflineModeSheet = ({ open, onClose }) => {
  const { WifiOff, RefreshCw, Check, Wifi } = LuApp;
  const online = uOn();
  const [swStatus, setSwStatus] = React.useState('checking');

  React.useEffect(() => {
    if (!open) return;
    if (!('serviceWorker' in navigator)) {
      setSwStatus('unsupported');
      return;
    }
    navigator.serviceWorker.getRegistration().then((reg) => {
      setSwStatus(reg ? 'active' : 'inactive');
    }).catch(() => setSwStatus('inactive'));
  }, [open]);

  const reloadSW = async () => {
    if (!('serviceWorker' in navigator)) return;
    try {
      const reg = await navigator.serviceWorker.getRegistration();
      if (reg) {
        await reg.update();
        window.location.reload();
      } else {
        window.location.reload();
      }
    } catch (e) {
      window.location.reload();
    }
  };

  return (
    <ShComp open={open} onClose={onClose} title="Mode hors-ligne">
      <div className="space-y-4">
        {/* État réseau temps réel */}
        <div className="rounded-xl p-4 flex items-center gap-3" style={{ background: online ? '#E8F5E9' : '#FEEEE3' }}>
          <div className="w-10 h-10 rounded-full flex items-center justify-center bg-white flex-shrink-0">
            {online
              ? <Wifi size={18} style={{ color: '#15803D' }} />
              : <WifiOff size={18} style={{ color: '#F4632B' }} />}
          </div>
          <div className="flex-1">
            <div className="font-display font-bold text-sm text-ink">
              {online ? 'Connecté' : 'Hors ligne'}
            </div>
            <div className="text-[11px] text-slate">
              {online
                ? 'Tes prix sont partagés en temps réel'
                : 'Tes prix seront partagés au retour du réseau'}
            </div>
          </div>
        </div>

        {/* Bandeau "Toujours actif" */}
        <div className="rounded-xl p-4" style={{ background: 'linear-gradient(135deg, #102A43 0%, #1f3a5f 100%)' }}>
          <div className="text-[10px] font-bold uppercase tracking-wider text-white/70">État du mode hors-ligne</div>
          <div className="font-display font-bold text-xl text-white mt-1">Toujours actif</div>
          <div className="text-xs text-white/85 mt-2 leading-relaxed">
            Le mode hors-ligne d'Oveyo est <strong>activé en permanence</strong>. Aucun bouton à activer — ton téléphone détecte automatiquement quand tu perds le réseau et bascule en file d'attente locale. Volontaire pour les zones avec réseau capricieux.
          </div>
        </div>

        {/* Détails techniques */}
        <div>
          <div className="text-[11px] font-bold uppercase tracking-wider text-slate mb-2">Comment ça marche</div>
          <ul className="space-y-2 text-sm text-ink">
            <li className="flex items-start gap-2">
              <Check size={16} className="text-success flex-shrink-0 mt-0.5" />
              <span>L'app fonctionne même sans réseau (pages, prix, photos déjà chargées)</span>
            </li>
            <li className="flex items-start gap-2">
              <Check size={16} className="text-success flex-shrink-0 mt-0.5" />
              <span>Tes prix partagés sont mis en file d'attente locale</span>
            </li>
            <li className="flex items-start gap-2">
              <Check size={16} className="text-success flex-shrink-0 mt-0.5" />
              <span>Synchronisation automatique dès que la connexion revient</span>
            </li>
            <li className="flex items-start gap-2">
              <Check size={16} className="text-success flex-shrink-0 mt-0.5" />
              <span>Aucune donnée perdue, même si tu fermes l'app</span>
            </li>
          </ul>
        </div>

        {/* Statut Service Worker */}
        <div className="rounded-xl p-3 flex items-center gap-2" style={{ background: '#F1F4F9' }}>
          <div className="w-2 h-2 rounded-full flex-shrink-0" style={{
            background: swStatus === 'active' ? '#15803D' : swStatus === 'checking' ? '#FFA45B' : '#F4632B'
          }} />
          <div className="flex-1 text-xs text-slate">
            Service worker : <strong className="text-ink">
              {swStatus === 'active' ? 'actif' : swStatus === 'checking' ? 'vérification...' : swStatus === 'unsupported' ? 'non supporté' : 'inactif'}
            </strong>
          </div>
        </div>

        {/* Bouton recharger l'app */}
        <button
          onClick={reloadSW}
          className="w-full py-3 rounded-xl font-bold text-sm flex items-center justify-center gap-2"
          style={{ background: '#F1F4F9', color: '#102A43' }}
        >
          <RefreshCw size={14} /> Recharger l'app pour mettre à jour
        </button>
      </div>
    </ShComp>
  );
};

// ============================================================
// SHEET : NOTIFICATIONS PUSH (vraie demande de permission)
// ============================================================
const NotificationsSheet = ({ open, onClose, state, onOpenSheet }) => {
  const { BellRing, Check, X, Loader2, Smartphone } = LuApp;
  const flash = ufApp();
  const [permission, setPermission] = React.useState(
    typeof Notification !== 'undefined' ? Notification.permission : 'unsupported'
  );
  const [hasSubscription, setHasSubscription] = React.useState(false);
  const [loading, setLoading] = React.useState(false);
  const [errorDetail, setErrorDetail] = React.useState(null);

  const userPhone = state?.user?.phone;
  const isLoggedIn = typeof userPhone === 'string' && userPhone.length >= 10 && userPhone.length <= 15;

  const isIOS = typeof navigator !== 'undefined' && /iPad|iPhone|iPod/.test(navigator.userAgent);
  const isStandalone = (
    typeof navigator !== 'undefined' && navigator.standalone === true
  ) || (
    typeof window !== 'undefined' && window.matchMedia &&
    window.matchMedia('(display-mode: standalone)').matches
  );
  const iosNeedsInstall = isIOS && !isStandalone;
  const supportNotif = typeof Notification !== 'undefined';
  const supportPush = typeof navigator !== 'undefined' && 'serviceWorker' in navigator
    && typeof window !== 'undefined' && 'PushManager' in window;

  React.useEffect(() => {
    if (!open) return;
    setErrorDetail(null);
    if (typeof Notification !== 'undefined') {
      setPermission(Notification.permission);
    }
    if (supportPush) {
      navigator.serviceWorker.ready
        .then((reg) => reg.pushManager.getSubscription())
        .then((sub) => setHasSubscription(!!sub))
        .catch(() => setHasSubscription(false));
    } else {
      setHasSubscription(false);
    }
  }, [open]);

  const goToLogin = () => {
    if (typeof onOpenSheet === 'function') {
      onClose();
      // V3.5 — Bug #7 : 500ms (au lieu de 250) pour laisser le flash respirer
      // entre la fermeture d'un sheet et l'ouverture du suivant.
      setTimeout(() => onOpenSheet('login'), 500);
    }
  };

  const enableNotifications = async () => {
    setErrorDetail(null);
    if (!isLoggedIn) {
      setErrorDetail("Connecte-toi avec ton numéro WhatsApp pour activer les notifications.");
      return;
    }
    if (!supportNotif) {
      setErrorDetail("Ce navigateur ne propose pas l'API Notification");
      return;
    }
    if (!supportPush) {
      setErrorDetail("Ce navigateur ne supporte pas les notifications push (PushManager indisponible)");
      return;
    }
    if (iosNeedsInstall) {
      setErrorDetail("Sur iPhone, ajoute d'abord Oveyo à l'écran d'accueil (bouton Partager → Sur l'écran d'accueil)");
      return;
    }
    setLoading(true);
    try {
      const result = await Notification.requestPermission();
      setPermission(result);

      if (result !== 'granted') {
        if (result === 'denied') {
          setErrorDetail("Tu as refusé les notifications. Pour les autoriser : Réglages navigateur → Site app.oveyo.sn → Notifications.");
        } else {
          setErrorDetail("Permission non accordée. Tu peux réessayer.");
        }
        setLoading(false);
        return;
      }

      let public_key = null;
      try {
        const r = await apiApp('/api/pwa/push/vapid-key');
        public_key = r.public_key;
      } catch (e) {
        setErrorDetail("Notifications activées dans le navigateur, mais le serveur push n'est pas joignable.");
        flash.show('warning', 'Notifications OS OK — push serveur indisponible');
        setLoading(false);
        return;
      }
      if (!public_key) {
        setErrorDetail("Le serveur push n'est pas configuré (clé VAPID absente).");
        flash.show('warning', 'Notifications OS OK — push serveur pas configuré');
        setLoading(false);
        return;
      }

      const pad = '='.repeat((4 - public_key.length % 4) % 4);
      const b64 = (public_key + pad).replace(/-/g, '+').replace(/_/g, '/');
      const raw = atob(b64);
      const buf = new Uint8Array(raw.length);
      for (let i = 0; i < raw.length; i++) buf[i] = raw.charCodeAt(i);

      // V3.8 P3 — Timeout 8s pour serviceWorker.ready (peut hang sur iOS si SW cassé)
      const swReadyWithTimeout = Promise.race([
        navigator.serviceWorker.ready,
        new Promise((_, rej) => setTimeout(() => rej(new Error('Service Worker indisponible (timeout 8s)')), 8000))
      ]);
      const reg = await swReadyWithTimeout;
      // V3.8 P3 — Timeout 8s sur pushManager.subscribe aussi
      const subWithTimeout = Promise.race([
        reg.pushManager.subscribe({
          userVisibleOnly: true,
          applicationServerKey: buf,
        }),
        new Promise((_, rej) => setTimeout(() => rej(new Error("Abonnement push impossible (timeout 8s) — vérifie que les notifications sont autorisées dans les Réglages")), 8000))
      ]);
      const sub = await subWithTimeout;

      try {
        await apiApp('/api/pwa/push/subscribe', {
          method: 'POST',
          body: JSON.stringify({
            phone: userPhone,
            subscription: sub.toJSON(),
          }),
        });
        setHasSubscription(true);
        flash.show('success', '🔔 Notifications activées !');
      } catch (e) {
        setErrorDetail("Notifications OS OK mais l'enregistrement serveur a échoué.");
        flash.show('warning', 'Notifications OS OK — enregistrement serveur KO');
      }
    } catch (e) {
      console.error('[push] enable error', e);
      setErrorDetail('Erreur : ' + (e.message || 'inconnue'));
      flash.show('error', "Erreur lors de l'activation");
    } finally {
      setLoading(false);
    }
  };

  const disableNotifications = async () => {
    setErrorDetail(null);
    setLoading(true);
    try {
      const reg = await navigator.serviceWorker.ready;
      const sub = await reg.pushManager.getSubscription();
      if (sub) {
        const endpoint = sub.endpoint;
        await sub.unsubscribe();
        try {
          await apiApp('/api/pwa/push/unsubscribe', {
            method: 'POST',
            body: JSON.stringify({ endpoint }),
          });
        } catch (e) {
          // best-effort
        }
      }
      setHasSubscription(false);
      flash.show('info', 'Notifications désactivées');
    } catch (e) {
      console.error('[push] disable error', e);
      setErrorDetail('Erreur lors de la désactivation : ' + (e.message || 'inconnue'));
      flash.show('error', 'Erreur lors de la désactivation');
    } finally {
      setLoading(false);
    }
  };

  const isActive = isLoggedIn && supportNotif && supportPush && !iosNeedsInstall
    && permission === 'granted' && hasSubscription;
  const isDenied = supportNotif && permission === 'denied';
  // V3.5 — Bug #3 : si l'utilisateur a refusé (permission denied), le toggle
  // ne sert à rien (Chrome/Safari bloquent). On retire le toggle dans ce cas.
  const canActivate = isLoggedIn && supportNotif && supportPush && !iosNeedsInstall
    && !isActive && !isDenied;

  let statusTitle, statusSubtitle, statusBg, statusIconColor;
  if (!isLoggedIn) {
    statusTitle = 'Connexion requise';
    statusSubtitle = "Connecte-toi avec ton numéro WhatsApp pour activer les notifications";
    statusBg = '#FEF3C7'; statusIconColor = '#92400E';
  } else if (iosNeedsInstall) {
    statusTitle = 'Action requise sur iPhone';
    statusSubtitle = "Ajoute Oveyo à ton écran d'accueil pour activer les notifications";
    statusBg = '#FEF3C7'; statusIconColor = '#92400E';
  } else if (!supportNotif || !supportPush) {
    statusTitle = 'Non supporté';
    statusSubtitle = 'Ton navigateur ne permet pas les notifications push';
    statusBg = '#F1F4F9'; statusIconColor = '#6B7891';
  } else if (isActive) {
    statusTitle = 'Notifications activées';
    statusSubtitle = 'Tu recevras les alertes de ton quartier';
    statusBg = '#E8F5E9'; statusIconColor = '#15803D';
  } else if (isDenied) {
    statusTitle = 'Notifications refusées';
    statusSubtitle = 'Tu les as refusées une fois — autorise-les dans les réglages navigateur';
    statusBg = '#FEEEE3'; statusIconColor = '#F4632B';
  } else {
    statusTitle = 'Notifications désactivées';
    statusSubtitle = 'Active pour ne rien rater des prix et alertes';
    statusBg = '#F1F4F9'; statusIconColor = '#102A43';
  }

  return (
    <ShComp open={open} onClose={onClose} title="Notifications">
      <div className="space-y-4">
        <div className="rounded-xl p-4" style={{ background: statusBg }}>
          <div className="flex items-center gap-3">
            <div className="w-10 h-10 rounded-full flex items-center justify-center bg-white flex-shrink-0">
              {iosNeedsInstall && Smartphone
                ? <Smartphone size={18} style={{ color: statusIconColor }} />
                : <BellRing size={18} style={{ color: statusIconColor }} />}
            </div>
            <div className="flex-1">
              <div className="font-display font-bold text-sm text-ink">{statusTitle}</div>
              <div className="text-[11px] text-slate">{statusSubtitle}</div>
            </div>
          </div>
        </div>

        {iosNeedsInstall && (
          <div className="rounded-xl p-3 space-y-2" style={{ background: '#F1F4F9' }}>
            <div className="text-[11px] font-bold uppercase tracking-wider text-slate">Comment installer sur iPhone</div>
            <ol className="space-y-1.5 text-sm text-ink list-decimal list-inside">
              <li>Touche le bouton <strong>Partager</strong> en bas de Safari</li>
              <li>Choisis <strong>Sur l'écran d'accueil</strong></li>
              <li>Ouvre Oveyo depuis l'icône installée</li>
              <li>Reviens ici activer les notifications</li>
            </ol>
          </div>
        )}

        <div>
          <div className="text-[11px] font-bold uppercase tracking-wider text-slate mb-2">Tu seras alertée pour :</div>
          <ul className="space-y-2 text-sm text-ink">
            <li className="flex items-start gap-2"><Check size={16} className="text-success flex-shrink-0 mt-0.5" /><span>Confirmation d'un écart de prix dans ton quartier</span></li>
            <li className="flex items-start gap-2"><Check size={16} className="text-success flex-shrink-0 mt-0.5" /><span>Nouveau groupe d'achat près de chez toi</span></li>
            <li className="flex items-start gap-2"><Check size={16} className="text-success flex-shrink-0 mt-0.5" /><span>Pharmacie de garde change pour ce soir</span></li>
            <li className="flex items-start gap-2"><Check size={16} className="text-success flex-shrink-0 mt-0.5" /><span>Tu as gagné un nouveau badge</span></li>
          </ul>
        </div>

        {/* V3.7 — Gros bouton clair au lieu d'un toggle ambigu */}
        {isActive && (
          <button
            onClick={disableNotifications}
            disabled={loading}
            className="w-full py-3.5 rounded-xl font-display font-bold text-sm flex items-center justify-center gap-2 disabled:opacity-60"
            style={{ background: '#F1F4F9', color: '#102A43', border: '2px solid #DDE3EC' }}
          >
            {loading ? <Loader2 size={16} className="animate-spin" /> : <BellRing size={16} />}
            {loading ? 'Désactivation...' : 'Désactiver les notifications'}
          </button>
        )}
        {canActivate && !isActive && (
          <button
            onClick={enableNotifications}
            disabled={loading}
            className="w-full py-3.5 rounded-xl font-display font-bold text-white text-sm flex items-center justify-center gap-2 disabled:opacity-60"
            style={{ background: '#F4632B' }}
          >
            {loading ? <Loader2 size={16} className="animate-spin" /> : <BellRing size={16} />}
            {loading ? 'Activation...' : 'Activer les notifications'}
          </button>
        )}
        {isDenied && (
          <div className="rounded-xl p-3 text-xs leading-relaxed" style={{ background: '#FEF3C7', color: '#92400E' }}>
            <strong>Comment réactiver les notifications :</strong>
            <ol className="mt-1.5 space-y-0.5 list-decimal list-inside">
              <li>Ouvre les réglages de ton navigateur</li>
              <li>Va dans Site → app.oveyo.sn → Notifications</li>
              <li>Choisis "Autoriser"</li>
              <li>Reviens ici pour activer</li>
            </ol>
          </div>
        )}

        {!isLoggedIn && (
          <button
            onClick={goToLogin}
            className="w-full py-3.5 rounded-xl font-display font-bold text-white text-sm flex items-center justify-center gap-2"
            style={{ background: '#102A43' }}
          >
            <span aria-hidden="true">📱</span>
            Se connecter avec WhatsApp
          </button>
        )}

        {errorDetail && (
          <div className="rounded-xl p-3 text-xs leading-relaxed" style={{ background: '#FEEEE3', color: '#9A3412' }}>
            <strong>Détail :</strong> {errorDetail}
          </div>
        )}
      </div>
    </ShComp>
  );
};

// ============================================================
// SHEET : RÉCOMPENSE ORANGE
// ============================================================
const RewardSheet = ({ open, onClose, data }) => {
  const { Gift, Award, TrendingUp } = LuApp;
  const stats = data?.stats || { nb_observations: 0, rank_in_neighborhood: null };

  return (
    <ShComp open={open} onClose={onClose} title="Récompense Orange">
      <div className="space-y-4">
        <div className="rounded-2xl p-5 text-center text-white" style={{ background: 'linear-gradient(135deg, #FF7900 0%, #F4632B 100%)' }}>
          <div className="w-16 h-16 rounded-2xl bg-white/20 backdrop-blur flex items-center justify-center mx-auto mb-3">
            <Gift size={32} className="text-white" />
          </div>
          <div className="font-display font-bold text-2xl">500 Mo de DATA Orange</div>
          <div className="text-xs opacity-90 mt-1">Offert chaque mois aux 100 meilleures contributrices</div>
        </div>

        <div className="grid grid-cols-2 gap-3">
          <div className="bg-white rounded-xl p-3 text-center" style={{ border: '1px solid #EAEEF4' }}>
            <div className="font-display font-bold text-2xl text-ink">{stats.nb_observations}</div>
            <div className="text-[10px] uppercase tracking-wider font-bold text-slate">Tes prix partagés</div>
          </div>
          <div className="bg-white rounded-xl p-3 text-center" style={{ border: '1px solid #EAEEF4' }}>
            <div className="font-display font-bold text-2xl" style={{ color: '#F4632B' }}>
              {stats.rank_in_neighborhood ? `#${stats.rank_in_neighborhood}` : '—'}
            </div>
            <div className="text-[10px] uppercase tracking-wider font-bold text-slate">Ton rang</div>
          </div>
        </div>

        <div>
          <div className="text-[11px] font-bold uppercase tracking-wider text-slate mb-2">Comment gagner ?</div>
          <ul className="space-y-2 text-sm text-ink">
            <li className="flex items-start gap-2"><Award size={16} style={{ color: '#F4632B' }} className="flex-shrink-0 mt-0.5" /><span>Chaque prix partagé validé = +5 points</span></li>
            <li className="flex items-start gap-2"><Award size={16} style={{ color: '#F4632B' }} className="flex-shrink-0 mt-0.5" /><span>Signaler un abus confirmé = +10 points</span></li>
            <li className="flex items-start gap-2"><TrendingUp size={16} style={{ color: '#F4632B' }} className="flex-shrink-0 mt-0.5" /><span>Top 100 du mois reçoivent 500 Mo Orange</span></li>
          </ul>
        </div>

        <div className="rounded-xl p-3 text-xs text-slate" style={{ background: '#F1F4F9' }}>
          Le crédit data est envoyé directement sur ton numéro à la fin du mois. Tu seras notifiée par WhatsApp.
        </div>
      </div>
    </ShComp>
  );
};

// ============================================================
// SHEET : DEVENIR AMBASSADEUR (CTA upgrade)
// ============================================================
// ============================================================
// SHEET : DEVENIR AMBASSADRICE — V3.5 vrai formulaire
// ============================================================
const BecomeAmbassadorSheet = ({ open, onClose, state, onOpenSheet }) => {
  const { Sparkles, ShieldCheck, Award, Send, Loader2, Camera, Check, X, Lock } = LuApp;
  const flash = ufApp();

  const userPhone = state?.user?.phone;
  const isLoggedIn = typeof userPhone === 'string' && userPhone.length >= 10;
  const { compressImage: cI } = window.OveyoState;

  const [realName, setRealName] = React.useState('');
  const [motivation, setMotivation] = React.useState('');
  const [photoFile, setPhotoFile] = React.useState(null);
  const [photoPreview, setPhotoPreview] = React.useState(null);
  const [submitting, setSubmitting] = React.useState(false);
  const [done, setDone] = React.useState(false);
  const photoInputRef = React.useRef(null);

  // Reset à chaque réouverture
  React.useEffect(() => {
    if (!open) {
      setRealName(''); setMotivation('');
      setPhotoFile(null); setPhotoPreview(null);
      setSubmitting(false); setDone(false);
    }
  }, [open]);

  const handlePhotoSelect = async (file) => {
    if (!file) return;
    try {
      const compressed = await cI(file, 1024, 0.85);
      setPhotoFile(compressed);
      setPhotoPreview(URL.createObjectURL(compressed));
    } catch {
      flash.show('error', "Impossible de lire la photo");
    }
  };

  const submit = async () => {
    if (!isLoggedIn) {
      // sécurité — le bouton ne devrait pas être visible mais on garde-fou
      flash.show('error', 'Connecte-toi avant de candidater');
      return;
    }
    const name = realName.trim();
    if (name.length < 3) { flash.show('error', 'Renseigne ton vrai nom et prénom'); return; }
    if (name.length > 80) { flash.show('error', 'Nom trop long'); return; }

    setSubmitting(true);
    try {
      const fd = new FormData();
      fd.append('phone', userPhone);
      fd.append('real_name', name);
      if (motivation.trim()) fd.append('motivation', motivation.trim());
      if (state?.user?.neighborhood_slug) fd.append('neighborhood_slug', state.user.neighborhood_slug);
      if (photoFile) fd.append('photo', photoFile, 'ambassador_photo.jpg');

      const r = await fetch('/api/pwa/ambassador/apply', { method: 'POST', body: fd });
      const data = await r.json().catch(() => ({}));
      if (!r.ok) {
        flash.show('error', data.detail || `Erreur ${r.status}`);
        setSubmitting(false);
        return;
      }
      if (data.already_pending) {
        flash.show('info', data.message || 'Candidature déjà en cours.');
      } else {
        flash.show('success', '✅ Candidature envoyée');
      }
      setDone(true);
    } catch (e) {
      flash.show('error', "Erreur d'envoi — réessaie");
      setSubmitting(false);
    }
  };

  // Vue confirmation
  if (done) {
    return (
      <ShComp open={open} onClose={onClose} title="Candidature envoyée">
        <div className="text-center py-6">
          <div className="w-20 h-20 rounded-full mx-auto mb-4 flex items-center justify-center" style={{ background: '#E8F5E9' }}>
            <Check size={40} style={{ color: '#15803D' }} strokeWidth={2.5} />
          </div>
          <h3 className="font-display font-bold text-xl text-ink mb-2">Merci !</h3>
          <p className="text-sm text-slate leading-relaxed mb-6 px-4">
            Notre équipe vérifie ton identité et te recontacte par WhatsApp sous 48h.
          </p>
          <button onClick={onClose} className="px-6 py-3 rounded-xl font-bold text-white" style={{ background: '#F4632B' }}>
            Fermer
          </button>
        </div>
      </ShComp>
    );
  }

  // Pas connecté : invite à se connecter (avec intent pour rouvrir ce sheet après login)
  if (!isLoggedIn) {
    return (
      <ShComp open={open} onClose={onClose} title="Devenir Ambassadeur">
        <div className="text-center py-4">
          <div className="w-16 h-16 rounded-2xl mx-auto mb-3 flex items-center justify-center" style={{ background: '#FEEEE3' }}>
            <Lock size={28} style={{ color: '#F4632B' }} />
          </div>
          <h3 className="font-display font-bold text-base text-ink mb-2">Connexion requise</h3>
          <p className="text-sm text-slate mb-4 leading-relaxed">
            Connecte-toi avec ton numéro WhatsApp pour candidater au programme ambassadeur.
          </p>
          <button
            onClick={() => {
              onClose();
              setTimeout(() => onOpenSheet?.('login', { redirect: 'become-ambassador' }), 500);
            }}
            className="px-5 py-3 rounded-xl font-bold text-white"
            style={{ background: '#F4632B' }}
          >
            Me connecter avec WhatsApp
          </button>
        </div>
      </ShComp>
    );
  }

  // Vue formulaire
  return (
    <ShComp open={open} onClose={onClose} title="Devenir Ambassadeur">
      <div className="space-y-4">
        <div className="rounded-2xl p-5 text-center text-white" style={{ background: 'linear-gradient(135deg, #FFA45B 0%, #F4632B 100%)' }}>
          <div className="w-14 h-14 rounded-2xl bg-white/20 backdrop-blur flex items-center justify-center mx-auto mb-3">
            <Sparkles size={28} className="text-white" />
          </div>
          <div className="font-display font-bold text-xl">Programme Ambassadeur</div>
          <div className="text-xs opacity-90 mt-1">Pour les contributrices les plus engagées</div>
        </div>

        <div>
          <div className="text-[11px] font-bold uppercase tracking-wider text-slate mb-2">Tu deviens visible publiquement</div>
          <ul className="space-y-2 text-sm text-ink">
            <li className="flex items-start gap-2"><ShieldCheck size={16} style={{ color: '#F4632B' }} className="flex-shrink-0 mt-0.5" /><span>Vrai nom + photo de profil affichés sur tes prix partagés</span></li>
            <li className="flex items-start gap-2"><Award size={16} style={{ color: '#F4632B' }} className="flex-shrink-0 mt-0.5" /><span>Badge bleu vérifié ✓ visible par tout le monde</span></li>
          </ul>
        </div>

        <div className="rounded-xl p-3 text-xs text-slate" style={{ background: '#F1F4F9' }}>
          <strong className="text-ink">Validation manuelle.</strong> On vérifie ton identité (selfie ou CNI) sous 48h et on te répond par WhatsApp.
        </div>

        {/* Vrai nom et prénom */}
        <div>
          <label className="text-[11px] font-bold uppercase tracking-wider text-slate">Vrai nom et prénom</label>
          <input
            type="text"
            value={realName}
            onChange={(e) => setRealName(e.target.value)}
            placeholder="Ex : Awa Ndiaye"
            maxLength={80}
            className="mt-1.5 w-full p-3 rounded-xl text-base outline-none"
            style={{ background: '#F1F4F9' }}
            autoComplete="name"
          />
          <div className="text-[11px] text-slate mt-1">Ton nom apparaîtra sur tes prix partagés une fois validée.</div>
        </div>

        {/* Photo selfie/CNI (optionnelle mais recommandée) */}
        <div>
          <label className="text-[11px] font-bold uppercase tracking-wider text-slate">Photo (selfie ou CNI)</label>
          <button
            type="button"
            onClick={() => photoInputRef.current?.click()}
            className="mt-1.5 w-full rounded-xl flex items-center gap-3 p-3 text-left"
            style={{ background: photoPreview ? '#E8F5E9' : '#F1F4F9' }}
          >
            {photoPreview ? (
              <>
                <img src={photoPreview} alt="" className="w-14 h-14 rounded-lg object-cover flex-shrink-0" />
                <div className="flex-1">
                  <div className="text-sm font-bold text-success">Photo prête</div>
                  <div className="text-[11px] text-slate">Touche pour changer</div>
                </div>
              </>
            ) : (
              <>
                <div className="w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0" style={{ background: 'white' }}>
                  <Camera size={22} style={{ color: '#F4632B' }} />
                </div>
                <div className="flex-1">
                  <div className="text-sm font-bold text-ink">Ajouter une photo</div>
                  <div className="text-[11px] text-slate">Recommandé pour valider plus vite</div>
                </div>
              </>
            )}
          </button>
          <input
            ref={photoInputRef}
            type="file"
            accept="image/*"
            capture="user"
            className="hidden"
            onChange={(e) => handlePhotoSelect(e.target.files?.[0])}
          />
        </div>

        {/* Motivation libre */}
        <div>
          <label className="text-[11px] font-bold uppercase tracking-wider text-slate">Pourquoi tu veux devenir ambassadrice ? (facultatif)</label>
          <textarea
            value={motivation}
            onChange={(e) => setMotivation(e.target.value)}
            rows={3}
            maxLength={500}
            placeholder="Ex : Je partage les prix de mon quartier depuis 3 mois, je veux aider…"
            className="mt-1.5 w-full p-3 rounded-xl text-sm outline-none resize-none"
            style={{ background: '#F1F4F9' }}
          />
        </div>

        <button
          onClick={submit}
          disabled={submitting}
          className="w-full py-3.5 rounded-xl font-display font-bold text-white text-sm flex items-center justify-center gap-2 disabled:opacity-60"
          style={{ background: '#F4632B' }}
        >
          {submitting ? <Loader2 size={16} className="animate-spin" /> : <Send size={16} />}
          {submitting ? 'Envoi…' : 'Envoyer ma candidature'}
        </button>
      </div>
    </ShComp>
  );
};

// ============================================================
// SHEET : RÉCLAMER UNE BOUTIQUE (NINEA)
// ============================================================
const ClaimShopSheet = ({ open, onClose, data }) => {
  const { Store, Send, Loader2, Check } = LuApp;
  const flash = ufApp();
  const [ninea, setNinea] = React.useState('');
  const [ownerName, setOwnerName] = React.useState('');
  const [phone, setPhone] = React.useState('');
  const [loading, setLoading] = React.useState(false);
  const [done, setDone] = React.useState(false);

  const shop = data?.shop || {};

  React.useEffect(() => {
    if (!open) {
      setNinea(''); setOwnerName(''); setPhone(''); setLoading(false); setDone(false);
    }
  }, [open]);

  const submit = async () => {
    if (ninea.trim().length < 4) { flash.show('error', 'NINEA invalide'); return; }
    if (ownerName.trim().length < 2) { flash.show('error', 'Nom invalide'); return; }
    const cleanPhone = '221' + phone.replace(/\D/g, '');
    if (cleanPhone.length < 11) { flash.show('error', 'Numéro WhatsApp invalide'); return; }

    setLoading(true);
    try {
      await apiApp('/api/pwa/shop-claim', {
        method: 'POST',
        body: JSON.stringify({
          shop_id: shop.id || null,
          shop_name: shop.name || null,
          ninea: ninea.trim(),
          owner_name: ownerName.trim(),
          phone: cleanPhone,
        }),
      });
      setDone(true);
      flash.show('success', '✅ Demande envoyée. On te contactera sous 48h.');
    } catch (e) {
      flash.show('error', "Erreur d'envoi");
    } finally { setLoading(false); }
  };

  if (done) {
    return (
      <ShComp open={open} onClose={onClose} title="Demande envoyée">
        <div className="text-center py-6">
          <div className="w-20 h-20 rounded-full mx-auto mb-4 flex items-center justify-center" style={{ background: '#E8F5E9' }}>
            <Check size={40} style={{ color: '#15803D' }} strokeWidth={2.5} />
          </div>
          <h3 className="font-display font-bold text-xl text-ink mb-2">Merci !</h3>
          <p className="text-sm text-slate leading-relaxed mb-6 px-4">
            Notre équipe va vérifier ton NINEA et te recontacter par WhatsApp sous 48h pour finaliser ta réclamation.
          </p>
          <button onClick={onClose} className="px-6 py-3 rounded-xl font-bold text-white" style={{ background: '#F4632B' }}>
            Fermer
          </button>
        </div>
      </ShComp>
    );
  }

  return (
    <ShComp open={open} onClose={onClose} title={`Réclamer ${shop.name || 'cette boutique'}`}>
      <div className="space-y-4">
        <div className="rounded-xl p-3 flex items-center gap-3" style={{ background: '#F1F4F9' }}>
          <div className="w-10 h-10 rounded-xl flex items-center justify-center" style={{ background: 'white' }}>
            <Store size={18} style={{ color: '#102A43' }} />
          </div>
          <div className="flex-1 min-w-0">
            <div className="font-display font-bold text-sm text-ink truncate">{shop.name || 'Boutique'}</div>
            <div className="text-xs text-slate">{shop.area || 'Quartier'}</div>
          </div>
        </div>

        <div>
          <label className="text-[11px] font-bold uppercase tracking-wider text-slate">NINEA de la boutique</label>
          <input
            type="text" value={ninea} onChange={(e) => setNinea(e.target.value)}
            placeholder="0001234567"
            className="mt-1.5 w-full p-3 rounded-xl text-sm outline-none"
            style={{ background: '#F1F4F9' }}
            autoComplete="off"
          />
          <div className="text-[10px] text-slate mt-1">Numéro d'Identification Nationale des Entreprises et Associations</div>
        </div>

        <div>
          <label className="text-[11px] font-bold uppercase tracking-wider text-slate">Ton nom complet (propriétaire)</label>
          <input
            type="text" value={ownerName} onChange={(e) => setOwnerName(e.target.value)}
            placeholder="Modou Ndiaye"
            className="mt-1.5 w-full p-3 rounded-xl text-sm outline-none"
            style={{ background: '#F1F4F9' }}
          />
        </div>

        <div>
          <label className="text-[11px] font-bold uppercase tracking-wider text-slate">WhatsApp pour contact</label>
          <div className="mt-1.5 flex gap-2">
            <input type="text" value="+221" readOnly className="w-20 p-3 rounded-xl text-center font-semibold text-ink" style={{ background: '#F1F4F9' }} />
            <input
              type="tel" value={phone} onChange={(e) => setPhone(e.target.value)}
              placeholder="77 123 45 67"
              className="flex-1 p-3 rounded-xl text-base outline-none"
              style={{ background: '#F1F4F9' }}
              inputMode="numeric"
            />
          </div>
        </div>

        <div className="rounded-xl p-3 text-xs text-slate" style={{ background: '#FEEEE3' }}>
          <strong className="text-ink">Vérification sous 48h.</strong> On contrôle ton NINEA dans le registre du commerce, puis on te contacte sur WhatsApp.
        </div>

        <button
          onClick={submit}
          disabled={loading}
          className="w-full py-3.5 rounded-xl font-display font-bold text-white text-sm flex items-center justify-center gap-2 disabled:opacity-60"
          style={{ background: '#F4632B' }}
        >
          {loading ? <Loader2 size={16} className="animate-spin" /> : <Send size={16} />}
          Envoyer ma réclamation
        </button>
      </div>
    </ShComp>
  );
};

// ============================================================
// SHEET : SOS ORDONNANCE
// ============================================================
const SosOrdonnanceSheet = ({ open, onClose, state }) => {
  const { Camera, Send, Loader2, Check, Cross } = LuApp;
  const flash = ufApp();
  const [photo, setPhoto] = React.useState(null);
  const [photoPreview, setPhotoPreview] = React.useState(null);
  const [phone, setPhone] = React.useState('');
  const [loading, setLoading] = React.useState(false);
  const [done, setDone] = React.useState(false);
  const inputRef = React.useRef(null);

  React.useEffect(() => {
    if (!open) {
      setPhoto(null); setPhotoPreview(null); setLoading(false); setDone(false);
      if (!state.user.phone) setPhone('');
    } else if (state.user.phone) {
      setPhone(state.user.phone.replace(/^221/, ''));
    }
  }, [open, state.user.phone]);

  const handlePhoto = async (file) => {
    if (!file) return;
    const { compressImage } = window.OveyoState;
    const compressed = await compressImage(file, 1200, 0.8);
    setPhoto(compressed);
    setPhotoPreview(URL.createObjectURL(compressed));
  };

  const submit = async () => {
    if (!photo) { flash.show('error', 'Photo de l\'ordonnance requise'); return; }
    const cleanPhone = phone.startsWith('221') ? phone : '221' + phone.replace(/\D/g, '');
    if (cleanPhone.length < 11) { flash.show('error', 'Numéro WhatsApp invalide'); return; }

    setLoading(true);
    try {
      const fd = new FormData();
      fd.append('photo', photo, 'ordonnance.jpg');
      fd.append('phone', cleanPhone);
      const r = await fetch('/api/pwa/sos-prescription', { method: 'POST', body: fd });
      if (!r.ok) throw new Error('Upload failed');
      setDone(true);
      flash.show('success', '✅ Pharmacies de garde alertées');
    } catch (e) {
      flash.show('error', "Erreur d'envoi");
    } finally { setLoading(false); }
  };

  if (done) {
    return (
      <ShComp open={open} onClose={onClose} title="Alerte envoyée">
        <div className="text-center py-6">
          <div className="w-20 h-20 rounded-full mx-auto mb-4 flex items-center justify-center" style={{ background: '#E8F5E9' }}>
            <Check size={40} style={{ color: '#15803D' }} strokeWidth={2.5} />
          </div>
          <h3 className="font-display font-bold text-xl text-ink mb-2">Pharmacies prévenues</h3>
          <p className="text-sm text-slate leading-relaxed mb-6 px-4">
            Les pharmacies de garde reçoivent ta demande. Elles te contacteront sur WhatsApp si elles ont le médicament.
          </p>
          <button onClick={onClose} className="px-6 py-3 rounded-xl font-bold text-white" style={{ background: '#F4632B' }}>
            Fermer
          </button>
        </div>
      </ShComp>
    );
  }

  return (
    <ShComp open={open} onClose={onClose} title="SOS Ordonnance">
      <div className="space-y-4">
        <div className="rounded-xl p-3 flex items-start gap-2 text-xs text-ink" style={{ background: '#F1F4F9' }}>
          <Cross size={16} style={{ color: '#15803D' }} className="flex-shrink-0 mt-0.5" />
          <span>Photographie ton ordonnance. Les pharmacies de garde te diront qui a le médicament en stock.</span>
        </div>

        <button
          onClick={() => inputRef.current?.click()}
          className="w-full aspect-[4/3] max-h-64 rounded-2xl flex flex-col items-center justify-center gap-3 relative overflow-hidden"
          style={{ background: photoPreview ? `url(${photoPreview}) center/cover` : 'linear-gradient(135deg, #102A43 0%, #1f3a5f 100%)' }}
        >
          {!photoPreview && (
            <>
              <div className="w-16 h-16 rounded-full bg-white/15 backdrop-blur flex items-center justify-center">
                <Camera size={28} className="text-white" />
              </div>
              <div className="font-display font-bold text-white text-base">Photographier l'ordonnance</div>
            </>
          )}
        </button>
        <input ref={inputRef} type="file" accept="image/*" capture="environment" className="hidden"
               onChange={(e) => handlePhoto(e.target.files?.[0])} />

        {!state.user.phone && (
          <div>
            <label className="text-[11px] font-bold uppercase tracking-wider text-slate">WhatsApp pour réponse</label>
            <div className="mt-1.5 flex gap-2">
              <input type="text" value="+221" readOnly className="w-20 p-3 rounded-xl text-center font-semibold text-ink" style={{ background: '#F1F4F9' }} />
              <input type="tel" value={phone} onChange={(e) => setPhone(e.target.value)}
                     placeholder="77 123 45 67" className="flex-1 p-3 rounded-xl text-base outline-none"
                     style={{ background: '#F1F4F9' }} inputMode="numeric" />
            </div>
          </div>
        )}

        <button
          onClick={submit}
          disabled={loading || !photo}
          className="w-full py-3.5 rounded-xl font-display font-bold text-white text-sm flex items-center justify-center gap-2 disabled:opacity-60"
          style={{ background: '#F4632B' }}
        >
          {loading ? <Loader2 size={16} className="animate-spin" /> : <Send size={16} />}
          Alerter les pharmacies de garde
        </button>
      </div>
    </ShComp>
  );
};

// ============================================================
// SHEET : REJOINDRE GROUPE D'ACHAT
// ============================================================
const JoinGroupBuySheet = ({ open, onClose, data }) => {
  const { Users, Send, Loader2, Check } = LuApp;
  const flash = ufApp();
  const [loading, setLoading] = React.useState(false);
  const [phone, setPhone] = React.useState('');
  const [done, setDone] = React.useState(false);

  const group = data?.group || null;

  React.useEffect(() => {
    if (!open) { setPhone(''); setLoading(false); setDone(false); }
  }, [open]);

  const submit = async () => {
    const cleanPhone = '221' + phone.replace(/\D/g, '');
    if (cleanPhone.length < 11) { flash.show('error', 'Numéro WhatsApp invalide'); return; }
    if (!group) return;
    setLoading(true);
    try {
      const r = await apiApp(`/api/pwa/group-buys/${group.id}/join`, {
        method: 'POST',
        body: JSON.stringify({ phone: cleanPhone }),
      });
      setDone(true);
      flash.show('success', `✅ Inscrit ! Vous êtes ${r.current_quantity} sur ${r.target_quantity}`);
    } catch (e) {
      flash.show('error', "Erreur d'inscription");
    } finally { setLoading(false); }
  };

  if (!group) return null;

  if (done) {
    return (
      <ShComp open={open} onClose={onClose} title="Inscription confirmée">
        <div className="text-center py-6">
          <div className="w-20 h-20 rounded-full mx-auto mb-4 flex items-center justify-center" style={{ background: '#E8F5E9' }}>
            <Check size={40} style={{ color: '#15803D' }} strokeWidth={2.5} />
          </div>
          <h3 className="font-display font-bold text-xl text-ink mb-2">À bientôt !</h3>
          <p className="text-sm text-slate leading-relaxed mb-6 px-4">
            Tu seras alertée par WhatsApp dès que le groupe atteint sa cible. La livraison se fera à un point de retrait commun.
          </p>
          <button onClick={onClose} className="px-6 py-3 rounded-xl font-bold text-white" style={{ background: '#F4632B' }}>
            Fermer
          </button>
        </div>
      </ShComp>
    );
  }

  return (
    <ShComp open={open} onClose={onClose} title="Rejoindre le groupe">
      <div className="space-y-4">
        <div className="rounded-2xl p-4 text-white" style={{ background: 'linear-gradient(120deg, #FFA45B 0%, #F4632B 100%)' }}>
          <div className="flex items-center gap-3">
            <div className="w-12 h-12 rounded-xl bg-white/20 flex items-center justify-center backdrop-blur">
              <Users size={22} className="text-white" />
            </div>
            <div className="flex-1">
              <div className="text-[10px] font-bold uppercase tracking-wider opacity-90">{group.product_name}</div>
              <div className="font-display font-bold text-base leading-tight">
                {group.current_quantity} / {group.target_quantity} voisins
              </div>
              <div className="text-xs opacity-90 mt-0.5">{group.description}</div>
            </div>
          </div>
        </div>

        <div>
          <div className="text-[11px] font-bold uppercase tracking-wider text-slate mb-2">Comment ça marche ?</div>
          <ul className="space-y-2 text-sm text-ink">
            <li className="flex items-start gap-2"><Check size={16} className="text-success flex-shrink-0 mt-0.5" /><span>Tu rejoins gratuitement et sans engagement</span></li>
            <li className="flex items-start gap-2"><Check size={16} className="text-success flex-shrink-0 mt-0.5" /><span>Quand le groupe est complet, on négocie un prix de gros</span></li>
            <li className="flex items-start gap-2"><Check size={16} className="text-success flex-shrink-0 mt-0.5" /><span>Livraison groupée à un point de retrait commun</span></li>
            <li className="flex items-start gap-2"><Check size={16} className="text-success flex-shrink-0 mt-0.5" /><span>Tu paies à la livraison (pas avant)</span></li>
          </ul>
        </div>

        <div>
          <label className="text-[11px] font-bold uppercase tracking-wider text-slate">Ton WhatsApp</label>
          <div className="mt-1.5 flex gap-2">
            <input type="text" value="+221" readOnly className="w-20 p-3 rounded-xl text-center font-semibold text-ink" style={{ background: '#F1F4F9' }} />
            <input type="tel" value={phone} onChange={(e) => setPhone(e.target.value)}
                   placeholder="77 123 45 67" className="flex-1 p-3 rounded-xl text-base outline-none"
                   style={{ background: '#F1F4F9' }} inputMode="numeric" />
          </div>
        </div>

        <button
          onClick={submit}
          disabled={loading}
          className="w-full py-3.5 rounded-xl font-display font-bold text-white text-sm flex items-center justify-center gap-2 disabled:opacity-60"
          style={{ background: '#F4632B' }}
        >
          {loading ? <Loader2 size={16} className="animate-spin" /> : <Users size={16} />}
          Rejoindre le groupe
        </button>
      </div>
    </ShComp>
  );
};


// ============================================================
// SHOP DETAIL SHEET — fiche boutique cliquable (C2)
// ============================================================
const ShopDetailSheet = ({ open, onClose, data, state, onOpenSheet }) => {
  const {
    Loader2, MapPin, ShieldCheck, Store, Navigation, AlertTriangle,
    Camera, Check, ChevronRight,
  } = LuApp;
  const flash = ufApp();

  const shopId = data?.shop_id || null;
  const [info, setInfo] = React.useState(null);
  const [loading, setLoading] = React.useState(false);
  const [error, setError] = React.useState(null);

  // Charge les données quand le sheet s'ouvre
  React.useEffect(() => {
    if (!open || !shopId) {
      setInfo(null); setError(null);
      return;
    }
    let cancelled = false;
    const load = async () => {
      setLoading(true); setError(null);
      try {
        const r = await apiApp(`/api/pwa/shops/${shopId}`);
        if (!cancelled) setInfo(r);
      } catch (e) {
        if (!cancelled) {
          setError(e?.message === 'Boutique introuvable' ? 'Cette boutique n\'existe plus' : 'Impossible de charger la boutique');
        }
      } finally {
        if (!cancelled) setLoading(false);
      }
    };
    load();
    return () => { cancelled = true; };
  }, [open, shopId]);

  // Format prix
  const fmt = (n) => Number(n).toLocaleString('fr-FR') + ' F';

  const shop = info?.shop || null;
  const prices = info?.prices || [];
  const observations = info?.observations || [];
  const trust = info?.trust || null;
  const isClaimed = info?.is_claimed === true;

  // Distance si user géoloc dispo (Haversine simple)
  const userPos = state?.user?.geolocation || null;
  const distanceKm = (() => {
    if (!shop || !shop.lat || !shop.lng || !userPos?.lat || !userPos?.lng) return null;
    const R = 6371;
    const toRad = (d) => d * Math.PI / 180;
    const dLat = toRad(shop.lat - userPos.lat);
    const dLng = toRad(shop.lng - userPos.lng);
    const a = Math.sin(dLat/2) ** 2 +
      Math.cos(toRad(userPos.lat)) * Math.cos(toRad(shop.lat)) *
      Math.sin(dLng/2) ** 2;
    return Number((2 * R * Math.asin(Math.sqrt(a))).toFixed(1));
  })();

  // Action handlers
  const openWhatsApp = () => {
    if (!shop?.whatsapp_phone) return;
    const cleaned = String(shop.whatsapp_phone).replace(/\D/g, '');
    window.open(`https://wa.me/${cleaned}`, '_blank', 'noopener');
  };
  const openItinerary = () => {
    if (!shop?.lat || !shop?.lng) return;
    const url = `https://www.google.com/maps/dir/?api=1&destination=${shop.lat},${shop.lng}`;
    window.open(url, '_blank', 'noopener');
  };
  const goClaim = () => {
    onClose();
    // V3.5 — Bug #7 : 500ms pour ne pas chevaucher les animations de sheet.
    setTimeout(() => onOpenSheet?.('claim-shop', { shop }), 500);
  };
  const goReportFraud = () => {
    onClose();
    setTimeout(() => onOpenSheet?.('report-fraud', { shop }), 500);
  };
  const goAddPhoto = () => {
    flash.show('info', '📷 Upload photo bientôt disponible');
  };

  // Couleur du badge confiance
  const trustBg = trust?.label === 'fiable' ? '#E8F5E9'
    : trust?.label === 'à surveiller' ? '#FEF3C7'
    : trust?.label === 'abusif' ? '#FEEEE3' : '#F1F4F9';
  const trustColor = trust?.label === 'fiable' ? '#15803D'
    : trust?.label === 'à surveiller' ? '#92400E'
    : trust?.label === 'abusif' ? '#B91C1C' : '#6B7891';

  const sheetTitle = shop?.name || 'Boutique';

  return (
    <ShComp open={open} onClose={onClose} title={sheetTitle}>
      {loading && (
        <div className="py-12 text-center">
          <Loader2 size={28} className="animate-spin mx-auto" style={{ color: '#F4632B' }} />
        </div>
      )}

      {error && !loading && (
        <div className="rounded-xl p-4 text-sm" style={{ background: '#FEF3F2', color: '#B91C1C' }}>
          {error}
        </div>
      )}

      {!loading && !error && shop && (
        <div className="space-y-4">
          {/* Photo de façade ou placeholder */}
          {shop.photo_url ? (
            <div className="w-full h-44 rounded-2xl overflow-hidden bg-bg-2">
              <img src={shop.photo_url} alt={shop.name} className="w-full h-full object-cover" />
            </div>
          ) : (
            <button
              onClick={goAddPhoto}
              className="w-full h-44 rounded-2xl flex flex-col items-center justify-center gap-2 active:opacity-70"
              style={{ background: 'linear-gradient(135deg, #102A43 0%, #1f3a5f 100%)' }}
            >
              <div className="w-14 h-14 rounded-full bg-white/15 backdrop-blur flex items-center justify-center">
                <Camera size={26} className="text-white" />
              </div>
              <div className="text-white font-display font-bold text-sm">Ajouter une photo</div>
              <div className="text-xs text-white/70 px-6 text-center">Façade depuis la voie publique · modération avant publication</div>
            </button>
          )}

          {/* Header : nom + badges */}
          <div>
            <div className="flex items-start gap-2">
              <div className="flex-1">
                <h3 className="font-display font-bold text-xl text-ink leading-tight">{shop.name}</h3>
                <div className="flex items-center gap-1.5 text-xs text-slate mt-1 flex-wrap">
                  <MapPin size={12} />
                  <span>{shop.neighborhood_name || '—'}</span>
                  {shop.city_name && shop.city_name !== shop.neighborhood_name && (
                    <span>· {shop.city_name}</span>
                  )}
                  {distanceKm != null && <span>· {distanceKm} km</span>}
                </div>
              </div>
              {shop.verified ? (
                <div className="flex items-center gap-1 px-2 py-1 rounded-full text-[10px] font-bold" style={{ background: '#E8F5E9', color: '#15803D' }}>
                  <ShieldCheck size={11} strokeWidth={2.5} />
                  Vérifiée
                </div>
              ) : (
                <div className="px-2 py-1 rounded-full text-[10px] font-bold" style={{ background: '#F1F4F9', color: '#6B7891' }}>
                  Non vérifiée
                </div>
              )}
            </div>
            {shop.address_text && (
              <div className="text-[11px] text-slate mt-1">{shop.address_text}</div>
            )}
          </div>

          {/* Indice de confiance */}
          {trust && (
            <div className="rounded-xl p-3" style={{ background: trustBg }}>
              <div className="flex items-center justify-between">
                <div className="flex items-center gap-2">
                  <ShieldCheck size={16} style={{ color: trustColor }} />
                  <div>
                    <div className="text-[10px] uppercase tracking-wider font-bold" style={{ color: trustColor }}>
                      Indice de confiance
                    </div>
                    <div className="text-sm font-bold text-ink mt-0.5">
                      {trust.label === 'indéterminé'
                        ? 'Pas assez de données'
                        : trust.label.charAt(0).toUpperCase() + trust.label.slice(1)}
                    </div>
                  </div>
                </div>
                {trust.in_norm_pct != null && (
                  <div className="text-right">
                    <div className="font-display font-bold text-lg" style={{ color: trustColor }}>
                      {Math.round(trust.in_norm_pct)}%
                    </div>
                    <div className="text-[10px] text-slate">prix dans la norme</div>
                  </div>
                )}
              </div>
              <div className="text-[11px] text-slate mt-2">
                Calculé sur {trust.total_observations_30d} observation{trust.total_observations_30d > 1 ? 's' : ''} · 30 derniers jours
                {trust.nb_abuse_30d > 0 && ` · ${trust.nb_abuse_30d} signalement${trust.nb_abuse_30d > 1 ? 's' : ''} abus`}
              </div>
            </div>
          )}

          {/* Prix actuels (7j) */}
          <div>
            <div className="text-[11px] font-bold uppercase tracking-wider text-slate mb-2">
              Prix actuels · 7 derniers jours
            </div>
            {prices.length === 0 ? (
              <div className="text-sm text-slate text-center py-4 rounded-xl" style={{ background: '#F8FAFC' }}>
                Aucun prix relevé récemment
              </div>
            ) : (
              <div className="space-y-1.5">
                {prices.map((p) => {
                  const isOver = Boolean(p.is_overpriced || p.is_abuse);
                  const isOOS = p.stock === 'rupture';
                  return (
                    <div
                      key={p.product_id}
                      className="rounded-xl p-3 flex items-center gap-3"
                      style={{
                        background: isOver ? '#FEEEE3' : isOOS ? '#F1F4F9' : '#F8FAFC',
                        border: '1px solid ' + (isOver ? '#FECACA' : '#EAEEF4'),
                      }}
                    >
                      <div className="text-2xl flex-shrink-0">{p.product_icon || '📦'}</div>
                      <div className="flex-1 min-w-0">
                        <div className="font-bold text-sm text-ink truncate">{p.product_name}</div>
                        <div className="text-[11px] text-slate flex items-center gap-1.5 flex-wrap">
                          {/* V4.1 — quantité observée (préférée à p.unit si remontée) */}
                          {p.quantity_label
                            ? <span className="font-semibold text-ink">{p.quantity_label}</span>
                            : (p.unit && <span>{p.unit}</span>)}
                          {p.price_official ? (
                            <>
                              <span>·</span>
                              <span className="px-1.5 py-0.5 rounded font-bold" style={{ background: '#DBEAFE', color: '#1E40AF', fontSize: 10 }}>
                                DGCC
                              </span>
                              <span>{fmt(p.price_official)}</span>
                            </>
                          ) : (
                            <><span>·</span><span style={{ color: '#9A6F00' }}>prix libre</span></>
                          )}
                        </div>
                      </div>
                      <div className="text-right">
                        {isOOS ? (
                          <div className="text-[10px] font-bold uppercase tracking-wider px-2 py-1 rounded text-slate" style={{ background: '#E5E9F0' }}>
                            Rupture
                          </div>
                        ) : (
                          <>
                            <div className="font-display font-bold text-base" style={{ color: isOver ? '#B91C1C' : '#102A43' }}>
                              {fmt(p.price)}
                            </div>
                            {isOver && (
                              <div className="text-[10px] font-bold uppercase tracking-wider" style={{ color: '#B91C1C' }}>Abus</div>
                            )}
                          </>
                        )}
                      </div>
                    </div>
                  );
                })}
              </div>
            )}
          </div>

          {/* Derniers signalements */}
          {observations.length > 0 && (
            <div>
              <div className="text-[11px] font-bold uppercase tracking-wider text-slate mb-2">
                Derniers signalements
              </div>
              <div className="space-y-1">
                {observations.map((o, idx) => {
                  const dt = o.created_at ? new Date(o.created_at.replace(' ', 'T') + 'Z') : null;
                  const dateStr = dt
                    ? dt.toLocaleDateString('fr-FR', { day: '2-digit', month: 'short' })
                    : '—';
                  const obsAbuse = Boolean(o.is_overpriced || o.is_abuse);
                  return (
                    <div
                      key={idx}
                      className="flex items-center gap-2 p-2 rounded-lg text-xs"
                      style={{ background: '#F8FAFC' }}
                    >
                      <span className="text-base">{o.product_icon || '📦'}</span>
                      <span className="flex-1 truncate text-ink">{o.product_name}</span>
                      <span className="font-bold" style={{ color: obsAbuse ? '#B91C1C' : '#102A43' }}>
                        {fmt(o.price)}
                      </span>
                      <span className="text-slate text-[10px]">{dateStr}</span>
                      {obsAbuse && (
                        <AlertTriangle size={11} style={{ color: '#B91C1C' }} />
                      )}
                    </div>
                  );
                })}
              </div>
            </div>
          )}

          {/* Boutons d'action */}
          <div className="space-y-2 pt-1">
            {/* Ligne 1 : WhatsApp + Itinéraire (côte à côte si les 2 dispos) */}
            <div className="grid grid-cols-2 gap-2">
              <button
                onClick={openWhatsApp}
                disabled={!shop.whatsapp_phone}
                className="py-2.5 rounded-xl text-xs font-bold flex items-center justify-center gap-1.5 disabled:opacity-40 disabled:cursor-not-allowed"
                style={{ background: '#25D366', color: 'white' }}
              >
                <span aria-hidden="true">📱</span> WhatsApp
              </button>
              <button
                onClick={openItinerary}
                disabled={!shop.lat || !shop.lng}
                className="py-2.5 rounded-xl text-xs font-bold flex items-center justify-center gap-1.5 disabled:opacity-40 disabled:cursor-not-allowed"
                style={{ background: '#102A43', color: 'white' }}
              >
                <Navigation size={13} /> Itinéraire
              </button>
            </div>

            {/* Réclamer si pas claimée */}
            {!isClaimed && (
              <button
                onClick={goClaim}
                className="w-full py-2.5 rounded-xl text-xs font-bold flex items-center justify-center gap-1.5"
                style={{ background: '#FEEEE3', color: '#F4632B' }}
              >
                <Store size={13} /> Tu es propriétaire ? Réclame cette boutique
              </button>
            )}

            {/* Signaler un écart de prix */}
            <button
              onClick={goReportFraud}
              className="w-full py-2.5 rounded-xl text-xs font-bold flex items-center justify-center gap-1.5"
              style={{ background: '#F1F4F9', color: '#102A43' }}
            >
              <AlertTriangle size={13} /> Signaler un écart de prix
            </button>

            {/* Ajouter photo (placeholder C3) */}
            {!shop.photo_url && (
              <button
                onClick={goAddPhoto}
                className="w-full py-2.5 rounded-xl text-xs font-bold flex items-center justify-center gap-1.5"
                style={{ background: '#FFFBEB', color: '#92400E', border: '1px dashed #FCD34D' }}
              >
                <Camera size={13} /> Ajouter une photo de la boutique 🚧
              </button>
            )}
          </div>
        </div>
      )}
    </ShComp>
  );
};


// ============================================================
// PRICE COMPARE SHEET — comparaison prix d'un produit
// ============================================================
const PriceCompareSheet = ({ open, onClose, data, state }) => {
  const { Loader2, Search, MapPin, ChevronRight, AlertTriangle, Navigation, X, ShoppingBag } = LuApp;
  const flash = ufApp();
  const catalog = ucApp();

  // Mode "picker" si pas de produit fourni, sinon affiche directement la compare
  const initialSlug = data?.product_slug || null;
  const [productSlug, setProductSlug] = React.useState(initialSlug);
  const [search, setSearch] = React.useState('');
  const [compareData, setCompareData] = React.useState(null);
  const [loading, setLoading] = React.useState(false);
  const [error, setError] = React.useState(null);
  const [useGeo, setUseGeo] = React.useState(false);
  const [geoCoords, setGeoCoords] = React.useState(null);
  const [geoLoading, setGeoLoading] = React.useState(false);
  const [radiusKm, setRadiusKm] = React.useState(5);

  React.useEffect(() => {
    if (!open) {
      setProductSlug(initialSlug);
      setCompareData(null); setSearch(''); setError(null);
      setUseGeo(false); setGeoCoords(null); setRadiusKm(5);
      setGeoLoading(false);
    } else {
      setProductSlug(initialSlug);
    }
  }, [open]);

  // Charge la comparaison quand productSlug change
  React.useEffect(() => {
    if (!open || !productSlug) return;
    let cancelled = false;
    const load = async () => {
      setLoading(true); setError(null);
      try {
        const params = new URLSearchParams();
        params.set('days', '7');
        const nbSlug = state?.user?.neighborhood_slug;
        if (nbSlug && !useGeo) params.set('neighborhood_slug', nbSlug);
        if (useGeo && geoCoords) {
          params.set('lat', String(geoCoords.lat));
          params.set('lng', String(geoCoords.lng));
          params.set('radius_km', String(radiusKm));
        }
        const r = await apiApp(`/api/pwa/products/${encodeURIComponent(productSlug)}/compare?${params.toString()}`);
        if (!cancelled) setCompareData(r);
      } catch (e) {
        if (!cancelled) setError('Impossible de charger la comparaison');
      } finally {
        if (!cancelled) setLoading(false);
      }
    };
    load();
    return () => { cancelled = true; };
  }, [open, productSlug, useGeo, geoCoords, radiusKm]);

  const askGeolocation = () => {
    if (!navigator.geolocation) {
      flash.show('error', 'Géolocalisation non supportée par ton navigateur');
      return;
    }
    setGeoLoading(true);
    // Timeout côté JS pour ne pas rester bloqué éternellement
    let resolved = false;
    const fallbackTimer = setTimeout(() => {
      if (resolved) return;
      resolved = true;
      setGeoLoading(false);
      flash.show('error', 'Position trop longue à obtenir, réessaie');
    }, 12000);

    navigator.geolocation.getCurrentPosition(
      (pos) => {
        if (resolved) return;
        resolved = true;
        clearTimeout(fallbackTimer);
        setGeoCoords({ lat: pos.coords.latitude, lng: pos.coords.longitude });
        setUseGeo(true);
        setGeoLoading(false);
        flash.show('success', 'Position OK — recherche du moins cher');
      },
      (err) => {
        if (resolved) return;
        resolved = true;
        clearTimeout(fallbackTimer);
        setGeoLoading(false);
        const msg = err.code === 1
          ? 'Autorise la géolocalisation dans ton navigateur'
          : err.code === 2
          ? 'Position indisponible (réseau ou GPS)'
          : 'Erreur de géolocalisation';
        flash.show('error', msg);
      },
      // enableHighAccuracy:false → plus tolérant et plus rapide sur mobile
      { enableHighAccuracy: false, timeout: 10000, maximumAge: 60000 }
    );
  };

  // ---------- mode picker ----------
  if (!productSlug) {
    const products = catalog?.products || [];
    const filtered = search.trim()
      ? products.filter(p => {
          const label = (p.name_fr || p.name || p.slug || '').toLowerCase();
          return label.includes(search.trim().toLowerCase());
        })
      : products;
    return (
      <ShComp open={open} onClose={onClose} title="Comparer les prix">
        <div className="space-y-3">
          <div className="rounded-xl p-3 flex items-center gap-2" style={{ background: '#F1F4F9' }}>
            <Search size={18} style={{ color: '#5B6B7E' }} />
            <input
              type="text" value={search} onChange={(e) => setSearch(e.target.value)}
              placeholder="Chercher un produit (riz, huile, sucre...)"
              className="flex-1 bg-transparent text-sm outline-none"
              autoFocus={false}
            />
            {search && (
              <button onClick={() => setSearch('')} aria-label="Effacer">
                <X size={16} style={{ color: '#5B6B7E' }} />
              </button>
            )}
          </div>
          <div className="space-y-1.5 max-h-[60vh] overflow-y-auto">
            {filtered.length === 0 ? (
              <div className="text-center py-8 text-sm text-slate">Aucun produit trouvé</div>
            ) : filtered.map((p) => {
              const label = p.name_fr || p.name || p.slug;
              const officialPrice = p.price_official != null ? p.price_official : p.official_price;
              return (
                <button
                  key={p.slug}
                  onClick={() => setProductSlug(p.slug)}
                  className="w-full flex items-center gap-3 p-3 rounded-xl text-left active:opacity-70"
                  style={{ background: '#F8FAFC', border: '1px solid #EAEEF4' }}
                >
                  <div className="w-10 h-10 rounded-lg flex items-center justify-center text-xl" style={{ background: '#F1F4F9' }}>
                    {p.icon || '📦'}
                  </div>
                  <div className="flex-1 min-w-0">
                    <div className="font-bold text-sm text-ink truncate">{label}</div>
                    {officialPrice ? (
                      <div className="text-xs text-slate flex items-center gap-1.5 mt-0.5">
                        <span className="px-1.5 py-0.5 rounded font-bold" style={{ background: '#DBEAFE', color: '#1E40AF', fontSize: 10 }}>DGCC</span>
                        <span>{Number(officialPrice).toLocaleString('fr-FR')} F</span>
                      </div>
                    ) : (
                      <div className="text-xs text-slate">Prix libre du marché</div>
                    )}
                  </div>
                  <ChevronRight size={18} style={{ color: '#5B6B7E' }} />
                </button>
              );
            })}
          </div>
        </div>
      </ShComp>
    );
  }

  // ---------- mode compare ----------
  const stats = compareData?.stats || null;
  const offers = compareData?.items || compareData?.offers || [];
  const productName = compareData?.product?.name_fr || compareData?.product?.name || productSlug;
  const officialPrice = stats?.official || null;

  const fmt = (n) => Number(n).toLocaleString('fr-FR') + ' F';

  return (
    <ShComp open={open} onClose={onClose} title={productName}>
      <div className="space-y-3">
        {/* Bouton retour picker */}
        <button
          onClick={() => { setProductSlug(null); setCompareData(null); }}
          className="text-xs font-bold text-slate flex items-center gap-1"
        >
          ← Changer de produit
        </button>

        {loading && (
          <div className="py-8 text-center"><Loader2 size={24} className="animate-spin mx-auto" style={{ color: '#F4632B' }} /></div>
        )}

        {error && !loading && (
          <div className="rounded-xl p-3 text-sm" style={{ background: '#FEF3F2', color: '#B91C1C' }}>
            {error}
          </div>
        )}

        {!loading && !error && stats && (
          <>
            {/* Stats min/médiane/max */}
            <div className="grid grid-cols-3 gap-2">
              <div className="rounded-xl p-3 text-center" style={{ background: '#E8F5E9' }}>
                <div className="text-[10px] font-bold uppercase tracking-wider" style={{ color: '#15803D' }}>Min</div>
                <div className="font-display font-bold text-base text-ink mt-0.5">{stats.min ? fmt(stats.min) : '—'}</div>
              </div>
              <div className="rounded-xl p-3 text-center" style={{ background: '#F1F4F9' }}>
                <div className="text-[10px] font-bold uppercase tracking-wider text-slate">Médiane</div>
                <div className="font-display font-bold text-base text-ink mt-0.5">{stats.median ? fmt(stats.median) : '—'}</div>
              </div>
              <div className="rounded-xl p-3 text-center" style={{ background: '#FEF3F2' }}>
                <div className="text-[10px] font-bold uppercase tracking-wider" style={{ color: '#B91C1C' }}>Max</div>
                <div className="font-display font-bold text-base text-ink mt-0.5">{stats.max ? fmt(stats.max) : '—'}</div>
              </div>
            </div>

            {/* V4.4 — Visualisation graphique avec ligne DGCC toujours visible si prix officiel */}
            {!!officialPrice && stats.min && stats.max && (() => {
              // Calcul de l'échelle : on couvre du min citoyen (ou DGCC si plus bas)
              // jusqu'au max citoyen + un peu de marge.
              const minVal = Math.min(stats.min, officialPrice);
              const maxVal = Math.max(stats.max, officialPrice);
              const padding = (maxVal - minVal) * 0.12 || officialPrice * 0.1;
              const scaleMin = Math.max(0, minVal - padding);
              const scaleMax = maxVal + padding;
              const range = scaleMax - scaleMin || 1;
              const pct = (val) => Math.max(0, Math.min(100, ((val - scaleMin) / range) * 100));

              const dgccPct = pct(officialPrice);
              const minPct = pct(stats.min);
              const medianPct = pct(stats.median || ((stats.min + stats.max) / 2));
              const maxPct = pct(stats.max);
              const isMaxOver = stats.max > officialPrice * 1.1;
              const isMedianOver = stats.median && stats.median > officialPrice * 1.1;

              return (
                <div className="rounded-xl p-4" style={{ background: 'white', border: '1px solid #EAEEF4' }}>
                  <div className="flex items-center justify-between mb-3">
                    <div className="flex items-center gap-1.5">
                      <span className="px-1.5 py-0.5 rounded font-bold" style={{ background: '#DBEAFE', color: '#1E40AF', fontSize: 10 }}>DGCC</span>
                      <span className="text-xs text-slate">Référence officielle</span>
                    </div>
                    <span className="font-display font-bold text-sm text-ink">{fmt(officialPrice)}</span>
                  </div>

                  {/* Barre horizontale */}
                  <div className="relative" style={{ height: 56 }}>
                    {/* Track de fond */}
                    <div className="absolute top-1/2 left-0 right-0 rounded-full" style={{ height: 6, transform: 'translateY(-50%)', background: 'linear-gradient(to right, #DCFCE7 0%, #FEF3C7 50%, #FEE2E2 100%)' }} />

                    {/* Ligne DGCC verticale (la référence) */}
                    <div
                      className="absolute top-0 bottom-0 flex flex-col items-center"
                      style={{ left: `${dgccPct}%`, transform: 'translateX(-50%)' }}
                    >
                      <div style={{ width: 2, height: 32, background: '#1E40AF', marginTop: 4 }} />
                      <div className="text-[9px] font-bold mt-1" style={{ color: '#1E40AF' }}>DGCC</div>
                    </div>

                    {/* Min citoyen */}
                    <div
                      className="absolute top-1/2 rounded-full"
                      style={{
                        left: `${minPct}%`, transform: 'translate(-50%, -50%)',
                        width: 12, height: 12, background: '#15803D',
                        border: '2px solid white', boxShadow: '0 1px 3px rgba(0,0,0,.2)',
                      }}
                      title={`Min : ${fmt(stats.min)}`}
                    />
                    {/* Médiane */}
                    {!!stats.median && (
                      <div
                        className="absolute top-1/2 rounded-full"
                        style={{
                          left: `${medianPct}%`, transform: 'translate(-50%, -50%)',
                          width: 14, height: 14, background: isMedianOver ? '#F4632B' : '#102A43',
                          border: '2px solid white', boxShadow: '0 1px 3px rgba(0,0,0,.25)',
                        }}
                        title={`Médiane : ${fmt(stats.median)}`}
                      />
                    )}
                    {/* Max */}
                    <div
                      className="absolute top-1/2 rounded-full"
                      style={{
                        left: `${maxPct}%`, transform: 'translate(-50%, -50%)',
                        width: 12, height: 12, background: isMaxOver ? '#B91C1C' : '#15803D',
                        border: '2px solid white', boxShadow: '0 1px 3px rgba(0,0,0,.2)',
                      }}
                      title={`Max : ${fmt(stats.max)}`}
                    />
                  </div>

                  {/* Légende compacte */}
                  <div className="mt-3 flex items-center justify-center gap-3 text-[10px] text-slate flex-wrap">
                    <span className="flex items-center gap-1"><span style={{ width: 8, height: 8, background: '#15803D', borderRadius: '50%' }} />Min</span>
                    <span className="flex items-center gap-1"><span style={{ width: 8, height: 8, background: '#102A43', borderRadius: '50%' }} />Médiane</span>
                    <span className="flex items-center gap-1"><span style={{ width: 8, height: 8, background: isMaxOver ? '#B91C1C' : '#15803D', borderRadius: '50%' }} />Max</span>
                    <span className="flex items-center gap-1"><span style={{ width: 2, height: 12, background: '#1E40AF' }} />DGCC</span>
                  </div>

                  {isMaxOver && (
                    <div className="mt-3 text-[11px] px-2.5 py-1.5 rounded-lg" style={{ background: '#FEF3F2', color: '#991B1B' }}>
                      ⚠️ Le prix max relevé dépasse le seuil d'abus DGCC ({fmt(Math.round(officialPrice * 1.1))})
                    </div>
                  )}
                </div>
              );
            })()}

            {!!officialPrice && (
              <div className="text-xs text-slate text-center">
                Seuil abus DGCC (+10%) : <span className="font-bold" style={{ color: '#F4632B' }}>{fmt(Math.round(officialPrice * 1.1))}</span>
              </div>
            )}

            {/* Toggle géoloc */}
            <div>
              {!useGeo ? (
                <button
                  onClick={askGeolocation}
                  disabled={geoLoading}
                  className="w-full py-3 rounded-xl text-sm font-bold flex items-center justify-center gap-2 disabled:opacity-70"
                  style={{ background: '#102A43', color: 'white' }}
                >
                  {geoLoading ? (
                    <>
                      <Loader2 size={16} className="animate-spin" />
                      Localisation en cours...
                    </>
                  ) : (
                    <>
                      <Navigation size={16} />
                      Trouver le moins cher près de moi
                    </>
                  )}
                </button>
              ) : (
                <div>
                  <div className="flex items-center gap-1.5 text-xs text-slate mb-2">
                    <MapPin size={14} style={{ color: '#F4632B' }} />
                    <span className="font-bold text-ink">Recherche dans un rayon de {radiusKm} km</span>
                    <button
                      onClick={() => { setUseGeo(false); setGeoCoords(null); }}
                      className="ml-auto text-[11px] font-bold text-slate underline"
                    >Annuler</button>
                  </div>
                  <div className="grid grid-cols-3 gap-2">
                    {[1, 5, 20].map((km) => (
                      <button
                        key={km}
                        onClick={() => setRadiusKm(km)}
                        className="py-2 rounded-xl text-xs font-bold"
                        style={{
                          background: radiusKm === km ? '#F4632B' : '#F1F4F9',
                          color: radiusKm === km ? 'white' : '#0F1F36',
                        }}
                      >
                        {km} km
                      </button>
                    ))}
                  </div>
                </div>
              )}
            </div>

            {/* Liste des offres */}
            <div className="space-y-1.5">
              <div className="text-[11px] font-bold uppercase tracking-wider text-slate px-1">
                {stats.count} offre{stats.count > 1 ? 's' : ''} sur 7 jours
              </div>
              {offers.length === 0 ? (
                <div className="text-center py-6 text-sm text-slate">
                  Aucune offre récente. Sois la première à signaler !
                </div>
              ) : offers.map((o, idx) => {
                const isOver = o.is_overpriced;
                const isCheapest = idx === 0;
                return (
                  <div
                    key={`${o.shop_id || 'na'}-${idx}`}
                    className="rounded-xl p-3 flex items-center gap-3"
                    style={{
                      background: isOver ? '#FEF3F2' : (isCheapest ? '#E8F5E9' : '#F8FAFC'),
                      border: '1px solid ' + (isOver ? '#FECACA' : (isCheapest ? '#86EFAC' : '#EAEEF4')),
                    }}
                  >
                    <div className="flex-1 min-w-0">
                      <div className="flex items-center gap-1.5">
                        {isOver && <AlertTriangle size={14} style={{ color: '#B91C1C' }} />}
                        <div className="font-bold text-sm text-ink truncate">
                          {o.shop_name || 'Boutique anonyme'}
                        </div>
                      </div>
                      <div className="text-[11px] text-slate mt-0.5 flex items-center gap-1">
                        <MapPin size={11} />
                        <span className="truncate">{o.neighborhood_name || '—'}</span>
                        {o.distance_km != null && (
                          <span>· {o.distance_km.toFixed(1)} km</span>
                        )}
                      </div>
                      {o.justification && (
                        <div className="text-[11px] text-slate mt-0.5 italic truncate">"{o.justification}"</div>
                      )}
                    </div>
                    <div className="text-right">
                      <div className="font-display font-bold text-base" style={{ color: isOver ? '#B91C1C' : '#102A43' }}>
                        {fmt(o.price)}
                      </div>
                      {isCheapest && !isOver && (
                        <div className="text-[10px] font-bold uppercase tracking-wider" style={{ color: '#15803D' }}>Moins cher</div>
                      )}
                      {isOver && (
                        <div className="text-[10px] font-bold uppercase tracking-wider" style={{ color: '#B91C1C' }}>Abus</div>
                      )}
                    </div>
                  </div>
                );
              })}
            </div>
          </>
        )}
      </div>
    </ShComp>
  );
};


// ============================================================
// APP ROOT
// ============================================================
const App = () => {
  const [state, setState] = React.useState(lS);
  const [activeTab, setActiveTab] = React.useState('home');
  const [view, setView] = React.useState('main');
  const [sheet, setSheet] = React.useState(null);
  const [sheetData, setSheetData] = React.useState(null);
  const online = uOn();

  React.useEffect(() => { sS(state); }, [state]);

  const openSheet = (name, data) => { setSheetData(data || null); setSheet(name); };
  const closeSheet = () => { setSheet(null); setSheetData(null); };

  const handleTabChange = (tab) => {
    // V3.7 — Fermer tout sheet ouvert quand l'utilisateur change d'onglet.
    setSheet(null);
    setSheetData(null);
    setActiveTab(tab);
    window.scrollTo({ top: 0, behavior: 'smooth' });
  };

  const handleLoginSuccess = (userData) => {
    // V3.5 — Bug #5 : on lit l'intention AVANT de fermer le sheet (closeSheet
    // remet sheetData à null). Si le user a cliqué sur "Espace Commerçant"
    // sans être connecté, on bascule directement en vue merchant après login,
    // au lieu de le laisser sur la page Profil (où il devait recliquer).
    const intent = sheetData?.redirect;
    setState({
      ...state,
      user: {
        phone: userData.phone, pseudonym: userData.pseudonym,
        neighborhood_slug: userData.neighborhood_slug,
        language: state.user.language || 'fr',
        geolocation: state.user.geolocation || null,
        session_token: userData.session_token || null,
        is_merchant: !!userData.is_merchant,
      },
      persona: { ...state.persona, ambassador_validated: !!userData.is_ambassador },
    });
    closeSheet();
    if (intent === 'merchant') {
      setView('merchant');
    } else if (intent === 'become-ambassador') {
      // après login depuis la CTA "Devenir Ambassadrice"
      setTimeout(() => openSheet('become-ambassador'), 250);
    }
  };

  // Bloc commun de tous les sheets — utilisé en main view ET en merchant view
  const sheets = (
    <>
      <LoginSheet open={sheet === 'login'} onClose={closeSheet} onSuccess={handleLoginSuccess} />
      <ReportFraudSheet open={sheet === 'report-fraud'} onClose={closeSheet} state={state} onOpenSheet={openSheet} />
      <MyReportsSheet open={sheet === 'my-reports'} onClose={closeSheet} state={state} />
      <MyBadgesSheet open={sheet === 'my-badges'} onClose={closeSheet} state={state} />
      <OfflineModeSheet open={sheet === 'offline-mode'} onClose={closeSheet} />
      <NotificationsSheet open={sheet === 'notifications' || sheet === 'push-info'} onClose={closeSheet} state={state} onOpenSheet={openSheet} />
      <RewardSheet open={sheet === 'reward'} onClose={closeSheet} data={sheetData} />
      <BecomeAmbassadorSheet open={sheet === 'become-ambassador'} onClose={closeSheet} state={state} onOpenSheet={openSheet} />
      <ClaimShopSheet open={sheet === 'claim-shop'} onClose={closeSheet} data={sheetData} />
      <SosOrdonnanceSheet open={sheet === 'sos-prescription'} onClose={closeSheet} state={state} />
      <JoinGroupBuySheet open={sheet === 'join-group-buy'} onClose={closeSheet} data={sheetData} />
      <PriceCompareSheet open={sheet === 'price-compare'} onClose={closeSheet} data={sheetData} state={state} />
      <ShopDetailSheet open={sheet === 'shop-detail'} onClose={closeSheet} data={sheetData} state={state} onOpenSheet={openSheet} />
    </>
  );

  if (view === 'merchant') {
    return (
      <div className="min-h-screen" style={{ background: '#F1F4F9' }}>
        <MerchantView
          state={state}
          setState={setState}
          onBack={() => setView('main')}
          onOpenSheet={openSheet}
        />
        {sheets}
      </div>
    );
  }

  return (
    <div className="min-h-screen pb-24 max-w-[480px] mx-auto" style={{ background: '#F1F4F9' }}>
      {activeTab === 'home' && <TopBar state={state} onBellClick={() => openSheet('push-info')} />}

      {!online && (
        <div className="px-4 py-2 text-center text-xs font-semibold text-white" style={{ background: '#F4632B' }}>
          📡 Hors ligne — tes prix seront partagés au retour du réseau
        </div>
      )}

      {activeTab === 'home' && <HomePage state={state} setActiveTab={handleTabChange} onOpenSheet={openSheet} />}
      {activeTab === 'map' && <MapPage state={state} setActiveTab={handleTabChange} onOpenSheet={openSheet} />}
      {activeTab === 'report' && (
        <ReportPage state={state} setState={setState} setActiveTab={handleTabChange} onLoginRequired={() => openSheet('login')} />
      )}
      {activeTab === 'pharmacy' && <PharmacyPage state={state} onOpenSheet={openSheet} />}
      {activeTab === 'profile' && (
        <ProfilePage state={state} setState={setState} onOpenSheet={openSheet} onSwitchView={(v) => setView(v)} />
      )}

      <TabBar active={activeTab} onChange={handleTabChange} />

      {sheets}
    </div>
  );
};

const Root = () => (<FP><App /></FP>);
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Root />);
