// WatchEagle — production SPA
// React 18 + Babel standalone — no build step required

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

// ── Feature flag ─────────────────────────────────────────────────────────────
// Mettre à true pour ré-activer la marketplace (grille annonces, alertes, favoris)
const MARKETPLACE_ENABLED = false;

/* ============================================================
   CONFIG
   ============================================================ */
// API_BASE routing:
//   - Railway / localhost:8000  → chemin relatif "/api/v1"
//   - Vercel / autre domaine    → URL Railway complète
//   - Serveur statique local    → localhost:8000
const _host = window.location.hostname;
const _port = window.location.port;
const RAILWAY_API = "https://web-production-a06ef.up.railway.app/api/v1";
const API_BASE = (() => {
  if (_port && _port !== "80" && _port !== "443" && _port !== "8000")
    return "http://localhost:8000/api/v1";          // python -m http.server 5501
  if (_host === "localhost" || _host.includes("railway.app"))
    return "/api/v1";                               // même origine
  return RAILWAY_API;                               // Vercel ou domaine custom
})();

/* ============================================================
   API CLIENT
   ============================================================ */
const api = {
  _token:   () => localStorage.getItem("we_token"),
  _refresh: () => localStorage.getItem("we_refresh"),

  // Tente de rafraîchir le token. Retourne true si succès, false sinon.
  async _tryRefresh() {
    const rt = api._refresh();
    if (!rt) return false;
    try {
      const res = await fetch(API_BASE + "/auth/refresh", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ refresh_token: rt }),
      });
      if (!res.ok) return false;
      const d = await res.json();
      localStorage.setItem("we_token", d.access_token);
      if (d.refresh_token) localStorage.setItem("we_refresh", d.refresh_token);
      return true;
    } catch { return false; }
  },

  async _req(method, path, body, isForm, _retried = false) {
    const headers = {};
    const tok = api._token();
    if (tok) headers["Authorization"] = `Bearer ${tok}`;
    let bodyData;
    if (body && !isForm) {
      headers["Content-Type"] = "application/json";
      bodyData = JSON.stringify(body);
    } else if (isForm) {
      bodyData = body;
    }
    let res;
    try {
      res = await fetch(API_BASE + path, { method, headers, body: bodyData });
    } catch (networkErr) {
      throw new Error("Connexion impossible. Vérifiez votre réseau et réessayez.");
    }

    // Token expiré → on tente le refresh une seule fois
    if (res.status === 401 && !_retried) {
      const ok = await api._tryRefresh();
      if (ok) return api._req(method, path, body, isForm, true);
      if (tok) {
        // L'utilisateur avait un token → vraie expiration de session
        ["we_token","we_refresh","we_user"].forEach(k => localStorage.removeItem(k));
        window.location.hash = "#login";
        throw new Error("Session expirée, veuillez vous reconnecter");
      }
      // Pas de token (ex: mauvais mdp au login) → laisser passer l'erreur réelle
    }

    if (res.status === 204) return null;
    const data = await res.json().catch(() => ({}));
    if (!res.ok) {
      let msg = data.detail || "";
      // Nettoyer les messages d'erreur backend trop techniques
      if (typeof msg === "string") {
        msg = msg.replace(/max_listings.*?(\d+)/i, "limite du plan atteinte ($1 annonces max)");
        msg = msg.replace(/\('.*?',\)/g, ""); // tuple Python
        msg = msg.replace(/Utilisateur introuvable/i, "Compte introuvable. Vérifiez votre email.");
        msg = msg.replace(/IntegrityError.*$/i, "Cette entrée existe déjà.");
      } else if (Array.isArray(msg)) {
        msg = msg.map(e => e.msg || e).join(", ");
      }
      if (!msg) msg = `Erreur ${res.status}`;
      throw new Error(msg);
    }
    return data;
  },

  get:    (path)        => api._req("GET",    path),
  post:   (path, body)  => api._req("POST",   path, body),
  patch:  (path, body)  => api._req("PATCH",  path, body),
  delete: (path)        => api._req("DELETE", path),
  upload: (path, form)  => api._req("POST",   path, form, true),
};

/* ============================================================
   STATIC DATA / HELPERS
   ============================================================ */
const CONDITIONS   = ["new", "very_good", "good", "acceptable"];
// FR labels → API values (legacy support for old stored data)
const COND_TO_API  = { "Neuf":"new", "Très bon":"very_good", "Bon":"good", "Acceptable":"acceptable" };
// Normalize any value (FR label or API key) to API key
const condApiKey = v => COND_TO_API[v] !== undefined ? COND_TO_API[v] : v;
const PRICE_MIN    = 0;
const PRICE_MAX    = 100000;
const CITIES       = ["Paris","Lyon","Genève","Bruxelles","Lille","Marseille","Nice","Bordeaux","Strasbourg"];
const SORT_OPTS    = [
  { id:"recent",     label:"Plus récent" },
  { id:"price_asc",  label:"Prix croissant" },
  { id:"price_desc", label:"Prix décroissant" },
  { id:"distance",   label:"Plus proche" },
];
const BRANDS_LIST  = [
  "Rolex","Omega","Audemars Piguet","Patek Philippe","Cartier","Tudor",
  "TAG Heuer","Breitling","IWC","Jaeger-LeCoultre","Panerai",
  "Vacheron Constantin","Hublot","Longines","Zenith","Seiko","Autre",
];

const DEFAULT_FILTERS = () => ({
  brands:[], priceMin:PRICE_MIN, priceMax:PRICE_MAX, conditions:[], box:false, papers:false, serviced:false,
});

function eur(n) {
  if (n == null) return "—";
  return Number(n).toLocaleString("fr-FR") + " €";
}
function statusColor(s) {
  return ({ available:"var(--success)", reserved:"var(--warning)", sold:"var(--muted)", archived:"var(--error)" })[s] || "var(--muted)";
}
function brandHue(brand) {
  if (!brand) return 200;
  let h = 0;
  for (let i = 0; i < brand.length; i++) h = (h * 31 + brand.charCodeAt(i)) % 360;
  return h;
}
// Résout les URLs relatives (stockage local Railway) vers une URL absolue.
// Utilisée à la fois pour afficher les images et pour stocker des URLs stables en DB.
function resolveImgUrl(url) {
  if (!url) return null;
  if (url.startsWith("blob:") || url.startsWith("http")) return url;
  const origin = API_BASE.replace(/\/api\/v1\/?$/, "");
  return origin + url;
}
const toAbsoluteImgUrl = resolveImgUrl;
function firstPhoto(listing) {
  try { const p = JSON.parse(listing.photos_json || "[]"); return resolveImgUrl(p[0] || null); }
  catch { return null; }
}
function allPhotos(listing) {
  try { return JSON.parse(listing.photos_json || "[]").map(resolveImgUrl); }
  catch { return []; }
}

/* ============================================================
   ICONS
   ============================================================ */
const ICON_PATHS = {
  search:   "M11 2a9 9 0 100 18A9 9 0 0011 2zM19 19l-3.5-3.5",
  pin:      "M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5a2.5 2.5 0 110-5 2.5 2.5 0 010 5z",
  chevron:  "M6 9l6 6 6-6",
  chevronR: "M9 18l6-6-6-6",
  heart:    "M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z",
  bell:     "M18 8A6 6 0 006 8c0 7-3 9-3 9h18s-3-2-3-9M13.73 21a2 2 0 01-3.46 0",
  user:     "M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2M12 11a4 4 0 100-8 4 4 0 000 8z",
  close:    "M18 6L6 18M6 6l12 12",
  check:    "M20 6L9 17l-5-5",
  arrowL:   "M19 12H5M12 19l-7-7 7-7",
  arrowR:   "M5 12h14M12 5l7 7-7 7",
  grid:     "M3 3h7v7H3zM14 3h7v7h-7zM14 14h7v7h-7zM3 14h7v7H3z",
  map:      "M1 6v16l7-4 8 4 7-4V2l-7 4-8-4-7 4zm7-4v16M16 6v16",
  box:      "M21 16V8a2 2 0 00-1-1.73l-7-4a2 2 0 00-2 0l-7 4A2 2 0 003 8v8a2 2 0 001 1.73l7 4a2 2 0 002 0l7-4A2 2 0 0021 16zM3.27 6.96L12 12.01l8.73-5.05M12 22.08V12",
  papers:   "M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8zM14 2v6h6M16 13H8M16 17H8M10 9H8",
  phone:    "M22 16.92v3a2 2 0 01-2.18 2 19.79 19.79 0 01-8.63-3.07A19.5 19.5 0 013.07 9.8 19.79 19.79 0 013 1.18 2 2 0 015 0h3a2 2 0 012 1.72 12.84 12.84 0 00.7 2.81 2 2 0 01-.45 2.11L9.09 7.91a16 16 0 006 6l1.27-1.27a2 2 0 012.11-.45 12.84 12.84 0 002.81.7A2 2 0 0122 16.92z",
  mail:     "M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2zM22 6l-10 7L2 6",
  route:    "M3 11l19-9-9 19-2-8-8-2z",
  shield:   "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z",
  plus:     "M12 5v14M5 12h14",
  edit:     "M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z",
  trash:    "M3 6h18M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a1 1 0 011-1h4a1 1 0 011 1v2",
  tag:      "M20.59 13.41l-7.17 7.17a2 2 0 01-2.83 0L2 12V2h10l8.59 8.59a2 2 0 010 2.82zM7 7h.01",
  settings: "M12 15a3 3 0 100-6 3 3 0 000 6zM19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-2 2 2 2 0 01-2-2v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06A1.65 1.65 0 004.68 15a1.65 1.65 0 00-1.51-1H3a2 2 0 01-2-2 2 2 0 012-2h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 010-2.83 2 2 0 012.83 0l.06.06A1.65 1.65 0 009 4.68a1.65 1.65 0 001-1.51V3a2 2 0 012-2 2 2 0 012 2v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 0 2 2 0 010 2.83l-.06.06A1.65 1.65 0 0019.4 9a1.65 1.65 0 001.51 1H21a2 2 0 012 2 2 2 0 01-2 2h-.09a1.65 1.65 0 00-1.51 1z",
  card:     "M1 4h22v15H1zM1 9h22",
  logout:   "M9 21H5a2 2 0 01-2-2V5a2 2 0 012-2h4M16 17l5-5-5-5M21 12H9",
  eye:      "M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8zM12 9a3 3 0 100 6 3 3 0 000-6z",
  eyeOff:   "M17.94 17.94A10.07 10.07 0 0112 20c-7 0-11-8-11-8a18.45 18.45 0 015.06-5.94M9.9 4.24A9.12 9.12 0 0112 4c7 0 11 8 11 8a18.5 18.5 0 01-2.16 3.19m-6.72-1.07a3 3 0 11-4.24-4.24M1 1l22 22",
  upload:   "M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M17 8l-5-5-5 5M12 3v12",
  share:    "M4 12v8a2 2 0 002 2h12a2 2 0 002-2v-8M16 6l-4-4-4 4M12 2v13",
  image:    "M21 19V5a2 2 0 00-2-2H5a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2zM8.5 10a1.5 1.5 0 110-3 1.5 1.5 0 010 3zM21 15l-5-5L5 21",
  sliders:  "M4 21v-7M4 10V3M12 21v-9M12 8V3M20 21v-5M20 12V3M1 14h6M9 8h6M17 16h6",
  reset:    "M23 4v6h-6M1 20v-6h6M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15",
  locate:   "M12 2a7 7 0 017 7c0 5.25-7 13-7 13S5 14.25 5 9a7 7 0 017-7zm0 4a3 3 0 100 6 3 3 0 000-6zM2 12h3M19 12h3M12 2v3M12 19v3",
  calendar: "M3 4h18v18H3zM16 2v4M8 2v4M3 10h18",
  sun:  "M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42M12 5a7 7 0 100 14A7 7 0 0012 5z",
  moon: "M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z",
};

/* ============================================================
   I18N
   ============================================================ */
const I18nContext = React.createContext({ lang: 'fr', t: k => k, setLanguage: () => {} });

function I18nProvider({ children }) {
  const [lang, setLang] = React.useState(() => {
    const s = localStorage.getItem('we_lang');
    if (s && ['fr','en'].includes(s)) return s;
    const nav = (navigator.language || '').slice(0, 2).toLowerCase();
    return ['fr','en'].includes(nav) ? nav : 'fr';
  });
  const t = (key, vars = {}) => {
    const dicts = window.WE_TRANSLATIONS || {};
    const dict = dicts[lang] || {};
    const fallback = dicts['fr'] || {};
    let str = dict[key] !== undefined ? dict[key] : (fallback[key] !== undefined ? fallback[key] : key);
    Object.entries(vars).forEach(([k, v]) => {
      str = str.replace(new RegExp('\\{' + k + '\\}', 'g'), String(v));
    });
    return str;
  };
  const setLanguage = l => { setLang(l); localStorage.setItem('we_lang', l); };
  return <I18nContext.Provider value={{ lang, t, setLanguage }}>{children}</I18nContext.Provider>;
}

function useTranslation() { return React.useContext(I18nContext); }

function useTheme() {
  const [light, setLight] = useState(() => document.documentElement.classList.contains('light'));
  const toggle = () => {
    const html = document.documentElement;
    html.classList.add('no-transition');
    const next = !html.classList.contains('light');
    html.classList.toggle('light', next);
    localStorage.setItem('we_theme', next ? 'light' : 'dark');
    setLight(next);
    requestAnimationFrame(() => requestAnimationFrame(() => html.classList.remove('no-transition')));
  };
  return [light, toggle];
}

/* ── SEO meta helper ──────────────────────────────────────────── */
const _setMetaTag = (nameOrProp, content) => {
  if (!content) return;
  let el = document.querySelector(`meta[name="${nameOrProp}"], meta[property="${nameOrProp}"]`);
  if (!el) {
    el = document.createElement("meta");
    const attr = nameOrProp.startsWith("og:") || nameOrProp.startsWith("twitter:") ? "property" : "name";
    el.setAttribute(attr, nameOrProp);
    document.head.appendChild(el);
  }
  el.setAttribute("content", content);
};

const _setCanonical = (url) => {
  let el = document.querySelector("link[rel='canonical']");
  if (!el) { el = document.createElement("link"); el.rel = "canonical"; document.head.appendChild(el); }
  el.href = url;
};

const DEFAULT_DESC_FR = "Trouvez votre prochaine montre de collection chez des dealers horlogers certifiés près de chez vous.";
const DEFAULT_DESC_EN = "Find your next collector watch from certified watchmakers near you.";
const SITE_NAME = "WatchEagle";
const OG_DEFAULT_IMAGE = "https://watch-eagle.com/img/og-default.svg";

function useMeta({ title, description, image, type, noindex } = {}) {
  const { lang } = useTranslation();
  useEffect(() => {
    document.documentElement.lang = lang;
    const defaultDesc = lang === "en" ? DEFAULT_DESC_EN : DEFAULT_DESC_FR;
    const fullTitle = title ? `${title} — ${SITE_NAME}` : `${SITE_NAME} – Montres de seconde main`;
    const desc = description || defaultDesc;
    const img = image || OG_DEFAULT_IMAGE;
    const url = window.location.href;

    document.title = fullTitle;
    _setMetaTag("description", desc);
    _setMetaTag("og:title", fullTitle);
    _setMetaTag("og:description", desc);
    _setMetaTag("og:image", img);
    _setMetaTag("og:url", url);
    _setMetaTag("og:type", type || "website");
    _setMetaTag("og:site_name", SITE_NAME);
    _setMetaTag("twitter:card", "summary_large_image");
    _setMetaTag("twitter:title", fullTitle);
    _setMetaTag("twitter:description", desc);
    _setMetaTag("twitter:image", img);
    _setCanonical(url);
  }, [title, description, image, lang, type]);

  useEffect(() => {
    if (!noindex) return;
    let el = document.querySelector("meta[name='robots']");
    if (!el) { el = document.createElement("meta"); el.name = "robots"; document.head.appendChild(el); }
    el.content = "noindex, nofollow";
    return () => { const r = document.querySelector("meta[name='robots']"); if (r) r.remove(); };
  }, [noindex]);
}

function LangSwitcher() {
  const { lang, setLanguage } = useTranslation();
  return (
    <div className="langsw">
      {['fr','en'].map(l => (
        <button key={l} className={'langsw__btn' + (l === lang ? ' on' : '')}
          onClick={() => setLanguage(l)}>
          {l.toUpperCase()}
        </button>
      ))}
    </div>
  );
}

function Icon({ name, size = 20, sw = 2, fill = "none", style, className }) {
  const d = ICON_PATHS[name] || "";
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill={fill}
      stroke="currentColor" strokeWidth={sw} strokeLinecap="round" strokeLinejoin="round"
      style={style} className={className}>
      <path d={d} />
    </svg>
  );
}

/* ============================================================
   BASE UI
   ============================================================ */
function EagleMark({ size = 57 }) {
  return <img src="/img/eagle-mark.svg" width={size} height={size} alt="" aria-hidden="true" style={{ display: "block" }} />;
}

function Glass({ as: Tag = "div", className = "", children, onClick, style }) {
  return <Tag className={"glass " + className} onClick={onClick} style={style}>{children}</Tag>;
}

function Stars({ value = 0 }) {
  const full = Math.round(value);
  return (
    <span className="stars">
      {[1,2,3,4,5].map(i => (
        <svg key={i} width="12" height="12" viewBox="0 0 24 24"
          fill={i <= full ? "var(--gold)" : "none"} stroke="var(--gold)" strokeWidth="2">
          <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/>
        </svg>
      ))}
    </span>
  );
}

function DialPlaceholder({ hue = 200, brand = "", label = "" }) {
  return (
    <div className="dial-ph" style={{ "--h": hue }}>
      <div className="dial-ph__rings" />
      <div className="dial-ph__glow" />
      <div className="dial-ph__mono">{brand}</div>
      {label && <div className="dial-ph__tag">{label}</div>}
    </div>
  );
}

/* ============================================================
   TOASTS
   ============================================================ */
function Toasts({ toasts }) {
  return (
    <div className="toasts">
      {toasts.map(t => (
        <div key={t.id} className={"toast toast--" + t.type}>
          <Icon name={t.type === "error" ? "close" : t.type === "info" ? "bell" : "check"} size={16} />
          <span>{t.msg}</span>
        </div>
      ))}
    </div>
  );
}

function useToasts() {
  const [toasts, setToasts] = useState([]);
  const addToast = useCallback((msg, type = "success") => {
    const id = Math.random().toString(36).slice(2);
    setToasts(p => [...p, { id, msg, type }]);
    setTimeout(() => setToasts(p => p.filter(x => x.id !== id)), 4000);
  }, []);
  return { toasts, addToast };
}

/* ============================================================
   RECENTLY VIEWED
   ============================================================ */
const RECENT_KEY = "we_recent";
const MAX_RECENT = 8;

function useRecent() {
  const addRecent = useCallback((watch) => {
    try {
      const list = JSON.parse(localStorage.getItem(RECENT_KEY) || "[]");
      const updated = [watch, ...list.filter(w => w.id !== watch.id)].slice(0, MAX_RECENT);
      localStorage.setItem(RECENT_KEY, JSON.stringify(updated));
    } catch {}
  }, []);
  const getRecent = () => {
    try { return JSON.parse(localStorage.getItem(RECENT_KEY) || "[]"); } catch { return []; }
  };
  return { addRecent, getRecent };
}

/* ============================================================
   CONDITION GUIDE
   ============================================================ */
function ConditionBadgeGuide() {
  const { t } = useTranslation();
  const [open, setOpen] = useState(false);
  return (
    <span style={{ position:"relative", display:"inline-flex", alignItems:"center" }}>
      <button type="button"
        onClick={e => { e.stopPropagation(); setOpen(v => !v); }}
        style={{ background:"none", border:"1px solid rgba(255,255,255,0.18)", color:"var(--muted)",
                 borderRadius:"50%", width:15, height:15, fontSize:9, cursor:"pointer",
                 display:"inline-flex", alignItems:"center", justifyContent:"center", flexShrink:0 }}>
        ?
      </button>
      {open && (
        <>
          <div style={{ position:"fixed", inset:0, zIndex:199 }} onClick={() => setOpen(false)} />
          <div onClick={e => e.stopPropagation()}
            style={{ position:"absolute", top:20, left:0, zIndex:200, width:272,
                     background:"var(--surface,#13131f)", border:"1px solid rgba(255,255,255,0.1)",
                     borderRadius:12, padding:16, boxShadow:"0 8px 32px rgba(0,0,0,.6)" }}>
            <div style={{ fontSize:11, fontWeight:700, color:"var(--muted)", textTransform:"uppercase",
                          letterSpacing:".07em", marginBottom:12 }}>{t('cond.guide_title')}</div>
            {CONDITIONS.map(key => (
              <div key={key} style={{ marginBottom:10, paddingBottom:10,
                borderBottom:"1px solid rgba(255,255,255,0.05)" }}>
                <div style={{ fontSize:13, fontWeight:600, color:"var(--gold)", marginBottom:3 }}>{t('cond.' + key)}</div>
                <div style={{ fontSize:12, color:"var(--muted)", lineHeight:1.55 }}>{t('cond.guide.' + key)}</div>
              </div>
            ))}
          </div>
        </>
      )}
    </span>
  );
}

/* ============================================================
   CONFIRM MODAL  (#8 — remplace confirm() natif)
   ============================================================ */
function ConfirmModal({ msg, confirmLabel = "Supprimer", onConfirm, onCancel }) {
  return (
    <div className="sheet-overlay"
      style={{ display:"flex", alignItems:"center", justifyContent:"center", zIndex:1000 }}
      onClick={onCancel}>
      <Glass onClick={e => e.stopPropagation()}
        style={{ maxWidth:360, width:"calc(100% - 40px)", padding:"28px 24px", borderRadius:16 }}>
        <p style={{ margin:"0 0 24px", fontSize:15, color:"var(--ink)", lineHeight:1.6 }}>{msg}</p>
        <div style={{ display:"flex", gap:10, justifyContent:"flex-end" }}>
          <button className="btn btn--glass" onClick={onCancel}>Annuler</button>
          <button className="btn btn--gold" onClick={onConfirm}
            style={{ background:"rgba(239,68,68,0.85)", color:"#fff", borderColor:"transparent" }}>
            {confirmLabel}
          </button>
        </div>
      </Glass>
    </div>
  );
}

function useConfirm() {
  const [state, setState] = useState(null); // { msg, label, resolve }
  const askConfirm = (msg, label) => new Promise(resolve => setState({ msg, label, resolve }));
  const handleConfirm = () => { if (state) { state.resolve(true);  setState(null); } };
  const handleCancel  = () => { if (state) { state.resolve(false); setState(null); } };
  const confirmModal = state
    ? <ConfirmModal msg={state.msg} confirmLabel={state.label} onConfirm={handleConfirm} onCancel={handleCancel} />
    : null;
  return { askConfirm, confirmModal };
}

/* ============================================================
   AUTH
   ============================================================ */
function useAuth() {
  const [user, setUser] = useState(() => {
    try { return JSON.parse(localStorage.getItem("we_user")); } catch { return null; }
  });
  const login = (userData, accessToken, refreshToken) => {
    localStorage.setItem("we_token", accessToken);
    if (refreshToken) localStorage.setItem("we_refresh", refreshToken);
    localStorage.setItem("we_user", JSON.stringify(userData));
    setUser(userData);
  };
  const logout = () => {
    ["we_token","we_refresh","we_user"].forEach(k => localStorage.removeItem(k));
    setUser(null);
  };
  const updateVerified = () => {
    const stored = JSON.parse(localStorage.getItem("we_user") || "{}");
    const updated = { ...stored, is_verified: true };
    localStorage.setItem("we_user", JSON.stringify(updated));
    setUser(updated);
  };
  return { user, login, logout, updateVerified };
}

/* ============================================================
   ROUTER (hash-based: #page / #page/param)
   ============================================================ */
function useRouter() {
  const parse = () => {
    const raw = window.location.pathname.slice(1) || "dealers";
    const [page, param] = raw.split("/");
    return { page: page || "dealers", param };
  };
  const [route, setRoute] = useState(parse);
  useEffect(() => {
    const handler = () => setRoute(parse());
    window.addEventListener("popstate", handler);
    return () => window.removeEventListener("popstate", handler);
  }, []);
  const go = (page, param) => {
    const url = param ? `/${page}/${param}` : `/${page}`;
    const from = window.location.pathname.slice(1).split("/")[0] || "dealers";
    history.pushState({ from }, "", url);
    window.dispatchEvent(new Event("popstate"));
    window.scrollTo(0, 0);
  };
  return { route, go };
}

/* ============================================================
   TOP BAR
   ============================================================ */
function TopBar({ q, setQ, city, setCity, radius, setRadius, geo, setGeo, favCount, onFav, onAlerts, go, user, onLogout, addToast }) {
  const { t } = useTranslation();
  const [cityOpen, setCityOpen] = useState(false);
  const [menuOpen, setMenuOpen] = useState(false);
  const [light, toggleTheme] = useTheme();
  const [cityInput, setCityInput] = useState(city || "");
  const [gpsLoading, setGpsLoading] = useState(false);

  const locLabel = geo ? t('loc.my_pos') : city || t('loc.everywhere');
  const locIcon  = geo ? "locate" : "pin";
  const radLabel = radius >= 9999 ? t('loc.everywhere') : radius + " km";

  const requestGps = () => {
    if (!navigator.geolocation) { addToast(t('loc.gps_unavailable'), "error"); return; }
    setGpsLoading(true);
    navigator.geolocation.getCurrentPosition(
      pos => {
        setGeo({ lat: pos.coords.latitude, lng: pos.coords.longitude });
        setCity("");
        setCityInput("");
        setGpsLoading(false);
        setCityOpen(false);
        if (radius >= 9999) setRadius(50); // active un rayon par défaut
      },
      () => { addToast(t('loc.gps_error'), "error"); setGpsLoading(false); }
    );
  };

  const applyCity = () => {
    setCity(cityInput.trim());
    setGeo(null);
    setCityOpen(false);
  };

  const clearLoc = () => {
    setCity(""); setCityInput(""); setGeo(null); setRadius(9999);
    setCityOpen(false);
  };

  return (
    <header className="topbar">
      <button className="topbar__brand" onClick={() => go("dealers")}>
        <EagleMark size={57} />
        <span className="wordmark">WATCH<span className="wordmark__gold">EAGLE</span></span>
      </button>

      <div className="searchwrap">
        {/* location pill — toujours visible */}
        <Glass className="locpill" as="button" onClick={() => setCityOpen(v => !v)}>
          <Icon name={locIcon} size={16} style={{ color: geo ? "var(--gold)" : "currentColor" }} />
          <span className="locpill__city">{locLabel}</span>
          {/* masquer le rayon quand aucune localisation : évite « Partout · Partout » */}
          {(geo || city) && <span className="locpill__rad">· {radLabel}</span>}
          <Icon name="chevron" size={14} style={{ opacity: 0.6 }} />
          {cityOpen && (
            <div className="locmenu" onClick={e => e.stopPropagation()}>

              {/* GPS button */}
              <button className={"locmenu__done " + (geo ? "locmenu__done--active" : "")}
                style={{ marginBottom:10, display:"flex", alignItems:"center", gap:8,
                         background: geo ? "rgba(201,168,76,.18)" : undefined,
                         color: geo ? "var(--gold)" : undefined }}
                onClick={requestGps} disabled={gpsLoading}>
                <Icon name="locate" size={15} />
                {gpsLoading ? t('loc.locating') : geo ? t('loc.my_pos_active') : t('loc.my_pos')}
              </button>

              {/* Séparateur */}
              <div style={{ fontSize:11, color:"var(--muted)", marginBottom:6 }}>{t('loc.or_city')}</div>

              {/* Saisie libre */}
              <div style={{ display:"flex", gap:6, marginBottom:12 }}>
                <input
                  className="input"
                  style={{ flex:1, fontSize:13, padding:"6px 10px" }}
                  placeholder={t('loc.city_placeholder')}
                  value={cityInput}
                  onChange={e => setCityInput(e.target.value)}
                  onKeyDown={e => e.key === "Enter" && applyCity()}
                />
                <button className="btn btn--gold" style={{ padding:"6px 12px", fontSize:13 }}
                  onClick={applyCity}>OK</button>
              </div>

              {/* Rayon */}
              <div className="locmenu__lbl">{t('loc.radius')}</div>
              <div className="locmenu__cities" style={{ marginBottom:10 }}>
                {[10,25,50,100,200].map(r => (
                  <button key={r} className={"chip " + (r === radius ? "chip--on" : "")}
                    onClick={() => setRadius(r)}>{r} km</button>
                ))}
                <button className={"chip " + (radius >= 9999 ? "chip--on" : "")}
                  onClick={() => setRadius(9999)}>{t('loc.everywhere')}</button>
              </div>

              {/* Effacer */}
              {(geo || city) && (
                <button onClick={clearLoc}
                  style={{ background:"none", border:"none", color:"var(--muted)",
                           fontSize:12, cursor:"pointer", padding:"4px 0" }}>
                  {t('loc.clear')}
                </button>
              )}
            </div>
          )}
        </Glass>

        {/* search box — masqué en mode annuaire */}
        {MARKETPLACE_ENABLED && (
          <Glass className="searchbox">
            <Icon name="search" size={18} style={{ opacity: 0.7 }} />
            <input className="searchbox__input" value={q} onChange={e => setQ(e.target.value)}
              placeholder={t('nav.search_placeholder')} />
            {q && (
              <button className="searchbox__clear" onClick={() => setQ("")}>
                <Icon name="close" size={15} />
              </button>
            )}
          </Glass>
        )}
      </div>

      <div className="topbar__actions">
        {MARKETPLACE_ENABLED && (
          <button className="iconbtn" onClick={() => go("favorites")} title={t('nav.my_favs')}>
            <Icon name="heart" size={19} />
            {favCount > 0 && <span className="iconbtn__badge">{favCount}</span>}
          </button>
        )}
        <button className="iconbtn iconbtn--theme" onClick={toggleTheme} title={light ? "Passer en mode sombre" : "Passer en mode clair"}>
          <Icon name={light ? "moon" : "sun"} size={18} />
        </button>
        <button className="iconbtn" onClick={() => go("alerts")} title={t('nav.my_alerts')}>
          <Icon name="bell" size={19} />
        </button>
        {user ? (
          <div style={{ position: "relative" }}>
            <button className="acctbtn" onClick={() => setMenuOpen(v => !v)}>
              <Icon name="user" size={17} />
              <span>{(user.email || "").split("@")[0]}</span>
            </button>
            {menuOpen && (
              <div className="locmenu" style={{ right: 0, left: "auto", width: 200 }}
                onClick={e => e.stopPropagation()}>
                <button className="locmenu__done" style={{ marginBottom: 8 }}
                  onClick={() => { setMenuOpen(false); go("account"); }}>
                  {t('nav.account')}
                </button>
                <button className="locmenu__done" style={{ marginBottom: 8 }}
                  onClick={() => { setMenuOpen(false); go("dashboard"); }}>
                  {t('nav.dashboard')}
                </button>
                <button className="locmenu__done"
                  style={{ background: "rgba(255,255,255,.08)", color: "var(--ink)" }}
                  onClick={() => { setMenuOpen(false); onLogout(); }}>
                  {t('nav.logout')}
                </button>
              </div>
            )}
          </div>
        ) : (
          <button className="acctbtn" onClick={() => go("login")}>
            <Icon name="user" size={17} /><span>{t('nav.login')}</span>
          </button>
        )}
        <LangSwitcher />
      </div>
    </header>
  );
}

/* ============================================================
   FILTERS SIDEBAR
   ============================================================ */
function PriceRange({ value, onChange }) {
  const [lo, hi] = value;
  const pct = v => ((v - PRICE_MIN) / (PRICE_MAX - PRICE_MIN)) * 100;
  return (
    <div className="range">
      <div className="range__track">
        <div className="range__fill" style={{ left: pct(lo) + "%", right: 100 - pct(hi) + "%" }} />
        <input type="range" min={PRICE_MIN} max={PRICE_MAX} step={500} value={lo}
          onChange={e => onChange([Math.min(+e.target.value, hi - 500), hi])} />
        <input type="range" min={PRICE_MIN} max={PRICE_MAX} step={500} value={hi}
          onChange={e => onChange([lo, Math.max(+e.target.value, lo + 500)])} />
      </div>
      <div className="range__vals">
        <span>{eur(lo)}</span>
        <span>{hi >= PRICE_MAX ? eur(PRICE_MAX) + "+" : eur(hi)}</span>
      </div>
    </div>
  );
}

function FilterGroup({ title, defaultOpen = false, children }) {
  const [open, setOpen] = useState(defaultOpen);
  return (
    <div className="fgroup">
      <button className="fgroup__head" onClick={() => setOpen(v => !v)}>
        <span>{title}</span>
        <Icon name="chevron" size={16}
          style={{ transform: open ? "none" : "rotate(-90deg)", transition: ".2s", opacity: 0.7 }} />
      </button>
      {open && <div className="fgroup__body">{children}</div>}
    </div>
  );
}

function Filters({ f, set, brandCounts, onReset }) {
  const { t } = useTranslation();
  const toggle = (key, val) => {
    const cur = f[key];
    set(key, cur.includes(val) ? cur.filter(x => x !== val) : [...cur, val]);
  };
  return (
    <div className="filters">
      <div className="filters__head">
        <span className="filters__title">{t('filter.title')}</span>
        <button className="filters__reset" onClick={onReset}>
          <Icon name="reset" size={14} /> {t('filter.reset')}
        </button>
      </div>

      <FilterGroup title={t('filter.brand')} defaultOpen>
        <div className="brandlist">
          {brandCounts.map(b => (
            <label key={b.name} className={"checkrow " + (f.brands.includes(b.name) ? "checkrow--on" : "")}>
              <span className="checkbox">
                {f.brands.includes(b.name) && <Icon name="check" size={12} sw={2.4} />}
              </span>
              <input type="checkbox" style={{ display: "none" }}
                checked={f.brands.includes(b.name)} onChange={() => toggle("brands", b.name)} />
              <span className="checkrow__name">{b.name}</span>
              <span className="checkrow__count">{b.count}</span>
            </label>
          ))}
        </div>
      </FilterGroup>

      <FilterGroup title={t('listing.price')} defaultOpen>
        <PriceRange value={[f.priceMin, f.priceMax]}
          onChange={([a, b]) => { set("priceMin", a); set("priceMax", b); }} />
      </FilterGroup>

      <FilterGroup title={<span style={{ display:"flex", alignItems:"center", gap:6 }}>{t('filter.condition')} <ConditionBadgeGuide /></span>} defaultOpen>
        <div className="chipwrap">
          {CONDITIONS.map(c => (
            <button key={c} className={"chip " + (f.conditions.includes(c) ? "chip--on" : "")}
              onClick={() => toggle("conditions", c)}>{t('cond.' + c)}</button>
          ))}
        </div>
      </FilterGroup>

      <FilterGroup title={t('filter.accessories')} defaultOpen>
        <button className="togglerow" onClick={() => set("box", !f.box)}>
          <Icon name="box" size={17} style={{ opacity: 0.8 }} />
          <span className="togglerow__lbl">{t('filter.has_box')}</span>
          <span className={"switch " + (f.box ? "switch--on" : "")}><span className="switch__dot" /></span>
        </button>
        <button className="togglerow" onClick={() => set("papers", !f.papers)}>
          <Icon name="papers" size={17} style={{ opacity: 0.8 }} />
          <span className="togglerow__lbl">{t('filter.has_papers')}</span>
          <span className={"switch " + (f.papers ? "switch--on" : "")}><span className="switch__dot" /></span>
        </button>
        <button className="togglerow" onClick={() => set("serviced", !f.serviced)}>
          <Icon name="check" size={17} style={{ opacity: 0.8 }} />
          <span className="togglerow__lbl">{t('filter.recently_serviced')}</span>
          <span className={"switch " + (f.serviced ? "switch--on" : "")}><span className="switch__dot" /></span>
        </button>
      </FilterGroup>
    </div>
  );
}

/* ============================================================
   WATCH CARD
   ============================================================ */
function WatchCard({ w, fav, onFav, onOpen }) {
  const { t } = useTranslation();
  const hue   = brandHue(w.brand);
  const photo = firstPhoto(w);
  const d     = w.dealer || {};

  return (
    <Glass className="card" as="article" onClick={() => onOpen(w)}>
      <div className="card__media">
        {photo
          ? <img src={photo} alt={w.brand + " " + w.model}
              style={{ position:"absolute",inset:0,width:"100%",height:"100%",objectFit:"cover" }} />
          : <DialPlaceholder hue={hue} brand={w.brand} />}
        {w.created_at && (Date.now() - new Date(w.created_at).getTime() < 48*3600*1000) && <span className="card__new">{t('card.new')}</span>}
        <button className={"card__fav " + (fav ? "card__fav--on" : "")}
          onClick={e => { e.stopPropagation(); onFav(w.id); }}>
          <Icon name="heart" size={17} fill={fav ? "var(--gold)" : "none"} />
        </button>
        <span className="card__cond">{t('cond.' + condApiKey(w.condition))}</span>
      </div>
      <div className="card__body">
        <div className="card__brandrow">
          <span className="card__brand">{w.brand}</span>
          {d.is_verified && (
            <span className="vbadge" title={t('dealer.verified_tooltip')}>
              <Icon name="shield" size={12} sw={1.8} />
            </span>
          )}
        </div>
        <div className="card__model">{w.model}</div>
        <div className="card__ref">{t('card.ref')} {w.reference} · {w.year}</div>
        <div className="card__meta">
          <span className="card__loc">
            <Icon name="pin" size={13} />
            {d.city || "—"}
            {w.distance_km != null ? " · " + Math.round(w.distance_km) + " km" : ""}
          </span>
          <span className="card__acc">
            <Icon name="box"    size={14} style={{ opacity: w.has_box    ? 1 : 0.22 }} />
            <Icon name="papers" size={14} style={{ opacity: w.has_papers ? 1 : 0.22 }} />
          </span>
        </div>
        <div className="card__price">{w.price != null ? eur(w.price) : t('card.price_on_request')}</div>
      </div>
    </Glass>
  );
}

/* ============================================================
   MAP VIEW (decorative – no real tiles needed for MVP)
   ============================================================ */
function MapView({ list, onOpen, go }) {
  const { t } = useTranslation();
  const containerRef = useRef(null);
  const mapObj       = useRef(null);
  const [selGroup,   setSelGroup]   = useState(null);
  const [leafletOk,  setLeafletOk]  = useState(!!window.L);

  /* ── Group watches by dealer (only dealers with coords) ──────────────── */
  const groups = useMemo(() => {
    const m = {};
    for (const w of list) {
      const d = w.dealer;
      if (!d?.lat || !d?.lng) continue;
      if (!m[d.id]) m[d.id] = { dealer: d, watches: [] };
      m[d.id].watches.push(w);
    }
    return Object.values(m);
  }, [list]);

  const noGeo = useMemo(() => list.filter(w => !w.dealer?.lat || !w.dealer?.lng), [list]);

  /* ── Load Leaflet lazily if not already in page ─────────────────────── */
  useEffect(() => {
    if (window.L) { setLeafletOk(true); return; }
    if (!document.getElementById("leaflet-css")) {
      const lnk = document.createElement("link");
      lnk.id = "leaflet-css"; lnk.rel = "stylesheet";
      lnk.href = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.css";
      document.head.appendChild(lnk);
    }
    if (!document.getElementById("leaflet-js")) {
      const s = document.createElement("script");
      s.id = "leaflet-js";
      s.src = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.js";
      s.onload = () => setLeafletOk(true);
      document.head.appendChild(s);
    }
  }, []);

  /* ── Init / refresh Leaflet map ──────────────────────────────────────── */
  useEffect(() => {
    if (!leafletOk || !containerRef.current) return;
    const L = window.L;

    if (mapObj.current) { mapObj.current.remove(); mapObj.current = null; }

    const map = L.map(containerRef.current, { zoomControl: false });
    L.control.zoom({ position: "topright" }).addTo(map);
    mapObj.current = map;

    /* CartoDB Dark Matter — free, no API key, matches dark theme */
    L.tileLayer("https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png", {
      attribution: "© <a href='https://openstreetmap.org'>OSM</a> © <a href='https://carto.com'>CARTO</a>",
      subdomains: "abcd",
      maxZoom: 19,
    }).addTo(map);

    const bounds = [];
    for (const g of groups) {
      const { dealer, watches } = g;
      const icon = L.divIcon({
        className: "",
        html: `<div class="mappin" style="position:static;transform:none;white-space:nowrap;">${watches.length} pièce${watches.length > 1 ? "s" : ""}</div>`,
        iconAnchor: [30, 28],
      });
      const marker = L.marker([dealer.lat, dealer.lng], { icon });
      marker.on("click", () => setSelGroup(g));
      marker.addTo(map);
      bounds.push([dealer.lat, dealer.lng]);
    }

    if (bounds.length === 0) {
      map.setView([46.8, 2.3], 5);
    } else if (bounds.length === 1) {
      map.setView(bounds[0], 13);
    } else {
      map.fitBounds(bounds, { padding: [60, 60] });
    }

    return () => { if (mapObj.current) { mapObj.current.remove(); mapObj.current = null; } };
  }, [leafletOk, groups]);

  return (
    <div style={{ position:"relative", borderRadius:"var(--radius)", overflow:"hidden",
                  border:"1px solid var(--line)", height:640 }}>
      {/* Leaflet container */}
      <div ref={containerRef} style={{ position:"absolute", inset:0 }} />

      {/* Loading overlay while Leaflet boots */}
      {!leafletOk && (
        <div style={{ position:"absolute", inset:0, display:"flex", alignItems:"center",
                      justifyContent:"center", background:"#08080b" }}>
          <div className="spinner" />
        </div>
      )}

      {/* No-geo badge */}
      {noGeo.length > 0 && (
        <div style={{ position:"absolute", top:14, left:14, zIndex:1000,
                      background:"rgba(12,12,16,0.88)", backdropFilter:"blur(8px)",
                      padding:"5px 11px", borderRadius:8, fontSize:12,
                      color:"var(--muted)", border:"1px solid var(--line)" }}>
          {noGeo.length} pièce{noGeo.length > 1 ? "s" : ""} sans localisation
        </div>
      )}

      {/* Dealer popup card */}
      {selGroup && (
        <Glass style={{ position:"absolute", left:18, bottom:18, width:300, zIndex:1000,
                        display:"flex", flexDirection:"column", gap:8, padding:14 }}>
          <div style={{ display:"flex", alignItems:"flex-start", justifyContent:"space-between", gap:8 }}>
            <div style={{ minWidth:0 }}>
              <div style={{ fontWeight:700, fontSize:15, color:"#fff",
                            overflow:"hidden", textOverflow:"ellipsis", whiteSpace:"nowrap" }}>
                {selGroup.dealer.name}
              </div>
              <div style={{ fontSize:12, color:"var(--muted)", marginTop:2 }}>
                📍 {selGroup.dealer.city || "—"}
              </div>
            </div>
            <button className="mapcard__close" onClick={() => setSelGroup(null)}>
              <Icon name="close" size={14} />
            </button>
          </div>

          <div style={{ fontSize:12, color:"var(--muted)",
                        borderTop:"1px solid var(--line)", paddingTop:8 }}>
            {t(selGroup.watches.length === 1 ? 'map.watches_one' : 'map.watches', {n: selGroup.watches.length})}
          </div>

          <div style={{ display:"flex", flexDirection:"column", gap:5 }}>
            {selGroup.watches.slice(0, 3).map(w => (
              <button key={w.id}
                style={{ display:"flex", justifyContent:"space-between", alignItems:"center",
                         padding:"6px 9px", borderRadius:7, background:"rgba(255,255,255,0.04)",
                         border:"1px solid var(--line)", cursor:"pointer", textAlign:"left",
                         width:"100%" }}
                onClick={() => { setSelGroup(null); onOpen(w); }}>
                <span style={{ fontSize:13, overflow:"hidden", textOverflow:"ellipsis", whiteSpace:"nowrap" }}>
                  <span style={{ color:"var(--gold)", fontWeight:600 }}>{w.brand}</span> {w.model}
                </span>
                <span style={{ fontSize:12, color:"var(--muted)", whiteSpace:"nowrap", marginLeft:8 }}>
                  {eur(w.price)}
                </span>
              </button>
            ))}
            {selGroup.watches.length > 3 && (
              <div style={{ fontSize:12, color:"var(--muted)", textAlign:"center", paddingTop:2 }}>
                {t('map.more', {n: selGroup.watches.length - 3})}
              </div>
            )}
          </div>

          {selGroup.dealer.slug && (
            <button className="btn btn--gold" style={{ marginTop:4, width:"100%", fontSize:13 }}
              onClick={() => { setSelGroup(null); go("dealer", selGroup.dealer.slug); }}>
              {t('map.view_shop')}
            </button>
          )}
        </Glass>
      )}
    </div>
  );
}

/* ============================================================
   SEARCH PAGE
   ============================================================ */
/* ============================================================
   DEALERS MAP — carte dédiée aux fiches dealer
   ============================================================ */
function DealerMapView({ dealers, go, geo }) {
  const containerRef = useRef(null);
  const mapObj = useRef(null);
  const [leafletOk, setLeafletOk] = useState(!!window.L);

  useEffect(() => {
    if (window.L) { setLeafletOk(true); return; }
    if (!document.getElementById("leaflet-css")) {
      const lnk = document.createElement("link");
      lnk.id = "leaflet-css"; lnk.rel = "stylesheet";
      lnk.href = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.css";
      document.head.appendChild(lnk);
    }
    if (!document.getElementById("leaflet-js")) {
      const s = document.createElement("script");
      s.id = "leaflet-js";
      s.src = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.js";
      s.onload = () => setLeafletOk(true);
      document.head.appendChild(s);
    }
  }, []);

  useEffect(() => {
    if (!leafletOk || !containerRef.current) return;
    const L = window.L;
    if (mapObj.current) { mapObj.current.remove(); mapObj.current = null; }
    const map = L.map(containerRef.current, { zoomControl: false });
    L.control.zoom({ position: "topright" }).addTo(map);
    mapObj.current = map;
    L.tileLayer("https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png", {
      attribution: "© <a href='https://openstreetmap.org'>OSM</a> © <a href='https://carto.com'>CARTO</a>",
      subdomains: "abcd", maxZoom: 19,
    }).addTo(map);

    const withCoords = dealers.filter(d => d.lat && d.lng);
    const bounds = [];
    for (const d of withCoords) {
      const icon = L.divIcon({
        className: "",
        html: `<div class="mappin" style="position:static;transform:none;white-space:nowrap;">${d.name}</div>`,
        iconAnchor: [30, 28],
      });
      const marker = L.marker([d.lat, d.lng], { icon });
      marker.on("click", () => go("dealer", d.slug));
      marker.addTo(map);
      bounds.push([d.lat, d.lng]);
    }
    if (geo) {
      map.setView([geo.lat, geo.lng], 12);
    } else if (bounds.length === 0) {
      map.setView([46.8, 2.3], 5);
    } else if (bounds.length === 1) {
      map.setView(bounds[0], 13);
    } else {
      map.fitBounds(bounds, { padding: [50, 50] });
    }

    return () => { if (mapObj.current) { mapObj.current.remove(); mapObj.current = null; } };
  }, [leafletOk, dealers, geo]);

  return (
    <div style={{ position:"relative", isolation:"isolate" }}>
    <div style={{ position:"relative", borderRadius:"var(--radius)", overflow:"hidden",
                  border:"1px solid var(--line)", height: window.innerWidth < 640 ? 336 : 420 }}>
      <div ref={containerRef} style={{ position:"absolute", inset:0 }} />
      {!leafletOk && (
        <div style={{ position:"absolute", inset:0, display:"flex", alignItems:"center",
                      justifyContent:"center", background:"#08080b" }}>
          <div className="spinner" />
        </div>
      )}
    </div>
    </div>
  );
}

function DealerCard({ d, go }) {
  return (
    <div onClick={() => go("dealer", d.slug)}
      style={{ cursor:"pointer", borderRadius:12, border:"1px solid var(--line)",
               background:"rgba(255,255,255,.03)", overflow:"hidden", transition:"border-color .15s" }}
      onMouseEnter={e => e.currentTarget.style.borderColor="var(--gold)"}
      onMouseLeave={e => e.currentTarget.style.borderColor="var(--line)"}>
      {d.logo_url && (
        <div style={{ height:80, background:"rgba(255,255,255,.05)", display:"flex",
                      alignItems:"center", justifyContent:"center", position:"relative" }}>
          <img src={d.logo_url} alt={d.name}
            style={{ maxHeight:60, maxWidth:"80%", objectFit:"contain" }} />
          {d.is_verified && (
            <span style={{ position:"absolute", top:8, right:8, fontSize:10,
                           background:"rgba(201,168,76,.15)", color:"var(--gold)",
                           border:"1px solid rgba(201,168,76,.3)", borderRadius:20,
                           padding:"2px 8px", fontWeight:600 }}>Vérifié</span>
          )}
        </div>
      )}
      <div style={{ padding:"12px 14px 14px" }}>
        <div style={{ display:"flex", alignItems:"center", gap:6, marginBottom:4 }}>
          <span style={{ fontWeight:700, color:"var(--text)", fontSize:15,
                        overflow:"hidden", textOverflow:"ellipsis", whiteSpace:"nowrap" }}>{d.name}</span>
          {d.is_verified && !d.logo_url && (
            <span style={{ flexShrink:0, fontSize:10, background:"rgba(201,168,76,.15)",
                           color:"var(--gold)", border:"1px solid rgba(201,168,76,.3)",
                           borderRadius:20, padding:"2px 8px", fontWeight:600 }}>Vérifié</span>
          )}
        </div>
        <div style={{ fontSize:13, color:"var(--muted)" }}>
          📍 {[d.city, d.country].filter(Boolean).join(", ")}
        </div>
        {d.phone && (
          <div style={{ fontSize:12, color:"var(--faint)", marginTop:4 }}>📞 {d.phone}</div>
        )}
        {d.categories && d.categories.length > 0 && (
          <div style={{ display:"flex", flexWrap:"wrap", gap:4, marginTop:8 }}>
            {d.categories.map(c => (
              <span key={c.id} style={{ fontSize:10, padding:"2px 7px", borderRadius:20,
                background:"rgba(201,168,76,.1)", color:"var(--gold)",
                border:"1px solid rgba(201,168,76,.25)", fontWeight:500 }}>
                {c.label_fr}
              </span>
            ))}
          </div>
        )}
      </div>
    </div>
  );
}

function DealersPage({ go, user, favIds, onFav, addToast, onLogout }) {
  const { t } = useTranslation();
  useMeta({ title: t('dealers.title'), description: t('dealers.sub') });

  const [dealers, setDealers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [city, setCity] = useState("");
  const [geo, setGeo] = useState(null);
  const [radius, setRadius] = useState(9999);
  const [catFilter, setCatFilter] = useState(null);
  const [mapView, setMapView] = useState(false);
  const isMobile = window.innerWidth < 768;

  // Recharge les dealers quand la ville change (filtrage serveur)
  useEffect(() => {
    setLoading(true);
    const p = city ? `?city=${encodeURIComponent(city)}` : "";
    api.get("/dealers" + p)
      .then(d => setDealers(d))
      .catch(() => {})
      .finally(() => setLoading(false));
  }, [city]);

  const haversineKm = (lat1, lng1, lat2, lng2) => {
    const R = 6371, dLat = (lat2-lat1)*Math.PI/180, dLng = (lng2-lng1)*Math.PI/180;
    const a = Math.sin(dLat/2)**2 + Math.cos(lat1*Math.PI/180)*Math.cos(lat2*Math.PI/180)*Math.sin(dLng/2)**2;
    return R*2*Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
  };

  const allCategories = React.useMemo(() => {
    const seen = new Map();
    dealers.forEach(d => (d.categories || []).forEach(c => { if (!seen.has(c.id)) seen.set(c.id, c); }));
    return [...seen.values()].sort((a, b) => a.label_fr.localeCompare(b.label_fr));
  }, [dealers]);

  let visibleDealers = dealers;
  if (geo && radius < 9999) {
    visibleDealers = visibleDealers.filter(d =>
      d.lat && d.lng && haversineKm(geo.lat, geo.lng, d.lat, d.lng) <= radius
    );
  }
  if (catFilter) {
    visibleDealers = visibleDealers.filter(d => (d.categories || []).some(c => c.id === catFilter));
  }
  const withCoords = visibleDealers.filter(d => d.lat && d.lng);

  return (
    <div className="app app--desktop">
      <TopBar q="" setQ={() => {}} city={city} setCity={setCity} radius={radius} setRadius={setRadius}
        geo={geo} setGeo={setGeo} favCount={favIds.length} onFav={() => go("favorites")}
        onAlerts={() => go("alerts")} go={go} user={user} onLogout={onLogout} addToast={addToast} />

      <div className="page" style={isMobile && mapView ? { padding:0, overflow:"hidden" } : {}}>

        {/* Filtre catégories — masqué en vue carte mobile */}
        {!(isMobile && mapView) && allCategories.length > 0 && (
          <div style={{ display:"flex", flexWrap:"wrap", gap:6, marginBottom:20 }}>
            <button onClick={() => setCatFilter(null)}
              style={{ fontSize:12, padding:"4px 12px", borderRadius:20, border:"1px solid", cursor:"pointer",
                borderColor: catFilter === null ? "var(--gold)" : "var(--line)",
                background: catFilter === null ? "rgba(201,168,76,.12)" : "transparent",
                color: catFilter === null ? "var(--gold)" : "var(--muted)" }}>
              Tous
            </button>
            {allCategories.map(cat => (
              <button key={cat.id} onClick={() => setCatFilter(catFilter === cat.id ? null : cat.id)}
                style={{ fontSize:12, padding:"4px 12px", borderRadius:20, border:"1px solid", cursor:"pointer",
                  borderColor: catFilter === cat.id ? "var(--gold)" : "var(--line)",
                  background: catFilter === cat.id ? "rgba(201,168,76,.12)" : "transparent",
                  color: catFilter === cat.id ? "var(--gold)" : "var(--muted)" }}>
                {cat.label_fr}
              </button>
            ))}
          </div>
        )}

        {/* Desktop : carte toujours au-dessus de la grille */}
        {!isMobile && withCoords.length > 0 && (
          <div style={{ marginBottom:28 }}><DealerMapView dealers={withCoords} go={go} /></div>
        )}

        {/* Mobile : carte plein écran quand mapView = true */}
        {isMobile && mapView && withCoords.length > 0 && (
          <div style={{ height:"calc(100vh - 60px - 72px)" }}>
            <DealerMapView dealers={withCoords} go={go} />
          </div>
        )}

        {/* Grille — masquée en vue carte mobile */}
        {!(isMobile && mapView) && (
          loading ? (
            <div className="empty"><div className="spinner" /></div>
          ) : visibleDealers.length === 0 ? (
            <div className="empty">
              <Icon name="pin" size={38} />
              <div className="empty__title">
                {(city || geo) ? "Aucun horloger trouvé dans cette zone." : "Aucun horloger disponible pour l'instant."}
              </div>
            </div>
          ) : (
            <div style={{ display:"grid", gridTemplateColumns:"repeat(auto-fill,minmax(260px,1fr))", gap:16 }}>
              {visibleDealers.map(d => <DealerCard key={d.id} d={d} go={go} />)}
            </div>
          )
        )}

        {/* Bouton flottant toggle carte/liste (mobile uniquement) */}
        {isMobile && withCoords.length > 0 && (
          <button onClick={() => setMapView(v => !v)}
            style={{ position:"fixed", bottom:84, left:"50%", transform:"translateX(-50%)",
                     zIndex:65, background: mapView ? "#1a1a1f" : "var(--gold)",
                     color: mapView ? "var(--text)" : "#000",
                     border: mapView ? "1px solid var(--line)" : "none",
                     borderRadius:24, padding:"10px 22px", fontWeight:600, fontSize:14,
                     cursor:"pointer", display:"flex", alignItems:"center", gap:7,
                     boxShadow:"0 4px 20px rgba(0,0,0,.55)", whiteSpace:"nowrap" }}>
            {mapView ? "← Liste" : "🗺️ Carte"}
          </button>
        )}
      </div>
    </div>
  );
}

function SearchPage({ go, user, favIds, onFav, addToast, onLogout }) {
  const { t } = useTranslation();
  useMeta({ title: t('search.seo_title'), description: t('search.seo_desc') });
  const [watches,  setWatches]  = useState([]);
  const [loading,  setLoading]  = useState(true);
  const [q,        setQ]        = useState("");
  const [city,     setCity]     = useState("");
  const [radius,   setRadius]   = useState(9999);
  const [geo,      setGeo]      = useState(null); // { lat, lng } from GPS
  const [f,        setFState]   = useState(DEFAULT_FILTERS());
  const [sort,     setSort]     = useState("recent");
  const [view,     setView]     = useState("dealers");
  const [sheet,    setSheet]    = useState(false);
  const [hasMore,  setHasMore]  = useState(false);
  const [loadingMore, setLoadingMore] = useState(false);
  const { addRecent, getRecent } = useRecent();
  const [recent,   setRecent]   = useState(() => getRecent());
  const [dealers,  setDealers]  = useState([]);
  const [dealersLoading, setDealersLoading] = useState(true);
  const [dealerCatFilter, setDealerCatFilter] = useState(null);

  const setF = (k, v) => setFState(p => ({ ...p, [k]: v }));
  const resetFilters = () => { setFState(DEFAULT_FILTERS()); setQ(""); };

  // Charger les dealers pour la carte
  useEffect(() => {
    api.get("/dealers").then(d => setDealers(d)).catch(() => {}).finally(() => setDealersLoading(false));
  }, []);

  // Basculer automatiquement vers les annonces dès que l'user tape
  useEffect(() => {
    if (MARKETPLACE_ENABLED && q.trim()) setView("grid");
  }, [q]);

  // Navigue vers alertes avec filtres courants pré-remplis
  const gotoAlertPrefilled = () => {
    const prefill = {};
    if (f.brands.length)        prefill.keywords = f.brands.join(" ");
    else if (q.trim())          prefill.keywords = q.trim();
    if (f.priceMax < PRICE_MAX) prefill.max_price = f.priceMax;
    try { sessionStorage.setItem("we_alert_prefill", JSON.stringify(prefill)); } catch {}
    go("alerts");
  };

  const activeFilterCount = f.brands.length + f.conditions.length + (f.serviced ? 1 : 0) +
    (f.box ? 1 : 0) + (f.papers ? 1 : 0) +
    (f.priceMin > PRICE_MIN || f.priceMax < PRICE_MAX ? 1 : 0);

  // Debounced API fetch
  useEffect(() => {
    setHasMore(false);
    const ctrl = new AbortController();
    const t = setTimeout(async () => {
      setLoading(true);
      try {
        const p = new URLSearchParams();
        if (q.trim())          p.set("q",          q.trim());
        f.brands.forEach(b => p.append("brands", b)); // multi-marques
        if (f.priceMin > 0)    p.set("min_price",  f.priceMin);
        if (f.priceMax < PRICE_MAX) p.set("max_price", f.priceMax);
        f.conditions.forEach(c => p.append("conditions", condApiKey(c)));
        if (f.box)             p.set("has_box",    "true");
        if (f.papers)          p.set("has_papers", "true");
        if (f.serviced)        p.set("recently_serviced", "true");
        // GPS coords (prioritaire) ou ville texte
        if (geo) {
          p.set("lat", geo.lat);
          p.set("lng", geo.lng);
          if (radius < 9999) p.set("radius_km", radius);
        } else if (city) {
          p.set("city", city);
          if (radius < 9999) p.set("radius_km", radius);
        }
        p.set("sort",  sort);
        p.set("limit", "24");

        const data = await api.get("/search/watches?" + p.toString());
        if (!ctrl.signal.aborted) {
          const arr = data.items || data || [];
          setWatches(arr);
          setHasMore(arr.length === 24);
        }
      } catch (e) {
        if (!ctrl.signal.aborted) addToast(e.message, "error");
      } finally {
        if (!ctrl.signal.aborted) setLoading(false);
      }
    }, 350);
    return () => { clearTimeout(t); ctrl.abort(); };
  }, [q, f, city, radius, geo, sort]);

  // Brand counts from current results
  const brandCounts = useMemo(() => {
    const map = {};
    watches.forEach(w => { map[w.brand] = (map[w.brand] || 0) + 1; });
    return Object.keys(map).sort().map(b => ({ name: b, count: map[b] }));
  }, [watches]);

  const results = watches;

  const loadMore = async () => {
    setLoadingMore(true);
    try {
      const p = new URLSearchParams();
      if (q.trim())          p.set("q",          q.trim());
      f.brands.forEach(b => p.append("brands", b));
      if (f.priceMin > 0)    p.set("min_price",  f.priceMin);
      if (f.priceMax < PRICE_MAX) p.set("max_price", f.priceMax);
      f.conditions.forEach(c => p.append("conditions", condApiKey(c)));
      if (f.box)             p.set("has_box",    "true");
      if (f.papers)          p.set("has_papers", "true");
      if (f.serviced)        p.set("recently_serviced", "true");
      if (geo) {
        p.set("lat", geo.lat); p.set("lng", geo.lng);
        if (radius < 9999) p.set("radius_km", radius);
      } else if (city) {
        p.set("city", city);
        if (radius < 9999) p.set("radius_km", radius);
      }
      p.set("sort", sort);
      p.set("limit", "24");
      p.set("offset", watches.length);
      const data = await api.get("/search/watches?" + p.toString());
      const arr = data.items || data || [];
      setWatches(prev => [...prev, ...arr]);
      setHasMore(arr.length === 24);
    } catch (e) { addToast(e.message, "error"); }
    finally { setLoadingMore(false); }
  };

  return (
    <div className="app app--desktop">
      <TopBar q={q} setQ={setQ} city={city} setCity={setCity} radius={radius} setRadius={setRadius}
        geo={geo} setGeo={setGeo}
        favCount={favIds.length} onFav={() => go("favorites")} onAlerts={() => go("alerts")}
        go={go} user={user} onLogout={onLogout} addToast={addToast} />

      {/* Vue carte dealers */}
      {view === "dealers" && (
        <div className="page">
          {MARKETPLACE_ENABLED && (
            <div style={{ display:"flex", justifyContent:"flex-end", marginBottom:16 }}>
              <div className="viewtoggle">
                <button className="on" title={t('dealers.nav')}>
                  <Icon name="pin" size={16} />
                </button>
                <button onClick={() => setView("grid")} title={t('search.toggle.grid')}>
                  <Icon name="grid" size={16} />
                </button>
              </div>
            </div>
          )}
          {dealersLoading ? (
            <div className="empty"><div className="spinner" /></div>
          ) : dealers.length === 0 ? (
            <div className="empty">
              <Icon name="pin" size={38} />
              <div className="empty__title">{t('dealers.all_empty')}</div>
            </div>
          ) : (() => {
            const allDealerCats = (() => {
              const seen = new Map();
              dealers.forEach(d => (d.categories || []).forEach(c => { if (!seen.has(c.id)) seen.set(c.id, c); }));
              return [...seen.values()].sort((a, b) => a.label_fr.localeCompare(b.label_fr));
            })();
            const filteredDealers = dealerCatFilter
              ? dealers.filter(d => (d.categories || []).some(c => c.id === dealerCatFilter))
              : dealers;
            const cityFiltered = filteredDealers.filter(d =>
              !city || d.city?.toLowerCase().includes(city.toLowerCase())
            );
            return (
              <>
                {allDealerCats.length > 0 && (
                  <div style={{ display:"flex", flexWrap:"wrap", gap:6, marginBottom:16 }}>
                    <button onClick={() => setDealerCatFilter(null)}
                      style={{ fontSize:12, padding:"4px 12px", borderRadius:20, border:"1px solid",
                        borderColor: dealerCatFilter === null ? "var(--gold)" : "var(--line)",
                        background: dealerCatFilter === null ? "rgba(201,168,76,.12)" : "transparent",
                        color: dealerCatFilter === null ? "var(--gold)" : "var(--muted)", cursor:"pointer" }}>
                      {t('dealers.filter.all')}
                    </button>
                    {allDealerCats.map(cat => (
                      <button key={cat.id} onClick={() => setDealerCatFilter(dealerCatFilter === cat.id ? null : cat.id)}
                        style={{ fontSize:12, padding:"4px 12px", borderRadius:20, border:"1px solid",
                          borderColor: dealerCatFilter === cat.id ? "var(--gold)" : "var(--line)",
                          background: dealerCatFilter === cat.id ? "rgba(201,168,76,.12)" : "transparent",
                          color: dealerCatFilter === cat.id ? "var(--gold)" : "var(--muted)", cursor:"pointer" }}>
                        {cat.label_fr}
                      </button>
                    ))}
                  </div>
                )}
                <div style={{ marginBottom:24 }}>
                  <DealerMapView
                    dealers={cityFiltered.filter(d => d.lat && d.lng)}
                    go={go}
                    geo={geo}
                  />
                </div>
                <div style={{ display:"grid", gridTemplateColumns:"repeat(auto-fill,minmax(240px,1fr))", gap:14 }}>
                  {filteredDealers.map(d => (
                    <div key={d.id} onClick={() => go("dealer", d.slug)}
                      style={{ cursor:"pointer", borderRadius:12, border:"1px solid var(--line)",
                               background:"rgba(255,255,255,.03)", overflow:"hidden", transition:"border-color .15s" }}
                      onMouseEnter={e => e.currentTarget.style.borderColor="var(--gold)"}
                      onMouseLeave={e => e.currentTarget.style.borderColor="var(--line)"}>
                      {d.logo_url && (
                        <div style={{ height:72, background:"rgba(255,255,255,.05)", display:"flex",
                                      alignItems:"center", justifyContent:"center" }}>
                          <img src={d.logo_url} alt={d.name} style={{ maxHeight:52, maxWidth:"80%", objectFit:"contain" }} />
                        </div>
                      )}
                      <div style={{ padding:"10px 12px 12px" }}>
                        <div style={{ display:"flex", alignItems:"center", gap:6, marginBottom:3 }}>
                          <span style={{ fontWeight:700, color:"var(--text)", fontSize:14,
                                        overflow:"hidden", textOverflow:"ellipsis", whiteSpace:"nowrap" }}>{d.name}</span>
                          {d.is_verified && !d.logo_url && (
                            <span style={{ flexShrink:0, fontSize:10, background:"rgba(201,168,76,.15)",
                                           color:"var(--gold)", border:"1px solid rgba(201,168,76,.3)",
                                           borderRadius:20, padding:"2px 7px", fontWeight:600 }}>{t('dealers.verified')}</span>
                          )}
                        </div>
                        <div style={{ fontSize:12, color:"var(--muted)" }}>📍 {[d.city, d.country].filter(Boolean).join(", ")}</div>
                        {d.categories && d.categories.length > 0 && (
                          <div style={{ display:"flex", flexWrap:"wrap", gap:4, marginTop:6 }}>
                            {d.categories.map(c => (
                              <span key={c.id} style={{ fontSize:10, padding:"2px 7px", borderRadius:20,
                                background:"rgba(201,168,76,.1)", color:"var(--gold)",
                                border:"1px solid rgba(201,168,76,.25)", fontWeight:500 }}>
                                {c.label_fr}
                              </span>
                            ))}
                          </div>
                        )}
                      </div>
                    </div>
                  ))}
                </div>
              </>
            );
          })()}
        </div>
      )}

      {/* Vue annonces — masquée en mode annuaire */}
      {MARKETPLACE_ENABLED && view !== "dealers" && <div className="layout">
        <aside className="sidebar">
          <Filters f={f} set={setF} brandCounts={brandCounts} onReset={resetFilters} />
        </aside>

        <main className="content">
          {/* Sort bar */}
          <div className="sortbar">
            <div className="sortbar__count">
              <strong>{results.length}{hasMore ? "+" : ""}</strong> {t(results.length === 1 ? 'search.results_one' : 'search.results', { n: results.length }).replace(/^\d+ /, '')}
            </div>
            <div className="sortbar__right">
              <button className="sortbar__filterbtn" onClick={() => setSheet(true)}>
                <Icon name="sliders" size={16} /> {t('filter.title')}
                {activeFilterCount > 0 && <span className="sortbar__fbadge">{activeFilterCount}</span>}
              </button>
              <div className="selectwrap">
                <select className="select" value={sort} onChange={e => {
                  if (e.target.value === "distance" && !geo && !city) {
                    addToast(t('loc.or_city'), "info");
                  }
                  setSort(e.target.value);
                }}>
                  {SORT_OPTS.map(o => (
                    <option key={o.id} value={o.id}
                      disabled={o.id === "distance" && !geo && !city}>
                      {t('sort.' + o.id)}{o.id === "distance" && !geo && !city ? " ↑" : ""}
                    </option>
                  ))}
                </select>
                <Icon name="chevron" size={15} className="selectwrap__chev" />
              </div>
              {MARKETPLACE_ENABLED && <div className="viewtoggle">
                <button className={view === "dealers" ? "on" : ""} onClick={() => setView("dealers")} title={t('dealers.nav')}>
                  <Icon name="pin" size={16} />
                </button>
                <button className={view === "grid" ? "on" : ""} onClick={() => setView("grid")} title={t('search.toggle.grid')}>
                  <Icon name="grid" size={16} />
                </button>
              </div>}
            </div>
          </div>

          {loading ? (
            <div className="empty"><div className="spinner" /><p className="empty__sub">{t('search.loading')}</p></div>
          ) : view === "grid" ? (
            results.length ? (
              <>
                <div className="grid">
                  {results.map(w => (
                    <WatchCard key={w.id} w={w} fav={favIds.includes(w.id)}
                      onFav={onFav} onOpen={w => { addRecent(w); go("watch", w.id); }} />
                  ))}
                </div>
                {hasMore && (
                  <div style={{ textAlign:"center", marginTop:24 }}>
                    <button className="btn btn--glass" onClick={loadMore} disabled={loadingMore}>
                      {loadingMore ? <span className="spinner" style={{ width:16, height:16 }} /> : t('search.load_more')}
                    </button>
                  </div>
                )}
                {/* CTA alerte discret en bas des résultats */}
                {MARKETPLACE_ENABLED && !hasMore && watches.length > 0 && (
                  <div style={{ marginTop:32, padding:"14px 20px", borderRadius:12,
                                background:"rgba(255,255,255,.03)", border:"1px solid var(--line)",
                                display:"flex", alignItems:"center", justifyContent:"space-between",
                                gap:12, flexWrap:"wrap" }}>
                    <div style={{ fontSize:13, color:"var(--muted)" }}>
                      <Icon name="bell" size={14} style={{ marginRight:6, verticalAlign:"middle" }} />
                      {t('search.alert_inline')}
                    </div>
                    <button className="btn btn--gold" style={{ fontSize:12, padding:"6px 16px", whiteSpace:"nowrap" }}
                      onClick={gotoAlertPrefilled}>
                      {t('search.alert_cta')}
                    </button>
                  </div>
                )}
              </>
            ) : (
              <div className="empty">
                <Icon name="bell" size={46} style={{ color:"var(--gold)", opacity:.6 }} />
                <div className="empty__title">{t('search.empty.title')}</div>
                <div className="empty__sub">{t('search.empty.sub')}</div>
                {MARKETPLACE_ENABLED && (
                  <div style={{ background:"rgba(201,168,76,.08)", border:"1px solid rgba(201,168,76,.25)",
                                borderRadius:12, padding:"14px 20px", margin:"8px 0 20px",
                                maxWidth:360, textAlign:"center" }}>
                    <div style={{ fontSize:13, color:"var(--muted)", marginBottom:10 }}>
                      {t('search.empty.alert_hint')}
                    </div>
                    <button className="btn btn--gold" onClick={gotoAlertPrefilled}>
                      <Icon name="bell" size={15} /> {t('search.empty.cta')}
                    </button>
                  </div>
                )}
                {activeFilterCount > 0 && (
                  <button className="btn btn--glass" onClick={resetFilters}>{t('search.reset_filters')}</button>
                )}
              </div>
            )
          ) : (
            <MapView list={results} go={go} onOpen={w => { addRecent(w); go("watch", w.id); }} />
          )}

          {/* Récemment consultées */}
          {recent.length > 0 && !loading && (
            <div style={{ marginTop:40, paddingTop:28, borderTop:"1px solid rgba(255,255,255,0.06)" }}>
              <div style={{ fontSize:13, fontWeight:600, color:"var(--muted)", textTransform:"uppercase",
                            letterSpacing:".08em", marginBottom:16 }}>{t('search.recent')}</div>
              <div style={{ display:"flex", gap:14, overflowX:"auto", paddingBottom:8,
                            scrollbarWidth:"none", msOverflowStyle:"none" }}>
                {recent.map(w => (
                  <div key={w.id} style={{ flexShrink:0, width:160, cursor:"pointer" }}
                    onClick={() => { addRecent(w); go("watch", w.id); }}>
                    <Glass style={{ padding:0, overflow:"hidden", borderRadius:12 }}>
                      <div style={{ height:110, position:"relative", background:"rgba(255,255,255,0.04)" }}>
                        {firstPhoto(w)
                          ? <img src={firstPhoto(w)} alt={w.brand}
                              style={{ position:"absolute",inset:0,width:"100%",height:"100%",objectFit:"cover" }} />
                          : <DialPlaceholder hue={brandHue(w.brand)} brand={w.brand} />}
                      </div>
                      <div style={{ padding:"8px 10px 10px" }}>
                        <div style={{ fontSize:11, fontWeight:700, color:"var(--gold)", marginBottom:2 }}>{w.brand}</div>
                        <div style={{ fontSize:12, color:"var(--ink)", fontWeight:500, whiteSpace:"nowrap",
                                      overflow:"hidden", textOverflow:"ellipsis" }}>{w.model}</div>
                        <div style={{ fontSize:12, color:"var(--muted)", marginTop:2 }}>{eur(w.price)}</div>
                      </div>
                    </Glass>
                  </div>
                ))}
              </div>
            </div>
          )}
        </main>
      </div>}

      {/* Mobile filter sheet */}
      {sheet && (
        <div className="sheet-overlay" onClick={() => setSheet(false)}>
          <Glass className="sheet" onClick={e => e.stopPropagation()}>
            <div className="sheet__handle" />
            <div className="sheet__scroll">
              <Filters f={f} set={setF} brandCounts={brandCounts} onReset={resetFilters} />
            </div>
            <button className="btn btn--gold sheet__apply" onClick={() => setSheet(false)}>
              {t(results.length === 1 ? 'filter.apply_one' : 'filter.apply', { n: results.length })}
            </button>
          </Glass>
        </div>
      )}

      <SiteFooter go={go} />
    </div>
  );
}

/* ============================================================
   SITE FOOTER
   ============================================================ */
function SiteFooter({ go }) {
  const { t } = useTranslation();
  return (
    <footer className="site-footer">
      <span>© {new Date().getFullYear()} WatchEagle</span>
      <span className="site-footer__sep">·</span>
      <a className="site-footer__link" href="/blog" style={{ textDecoration:"none" }}>Blog</a>
      <span className="site-footer__sep">·</span>
      <button className="site-footer__link" onClick={() => go("legal")}>{t('footer.legal')}</button>
      <span className="site-footer__sep">·</span>
      <button className="site-footer__link" onClick={() => go("cgu")}>CGU</button>
      <span className="site-footer__sep">·</span>
      <button className="site-footer__link" onClick={() => go("privacy")}>{t('footer.privacy')}</button>
    </footer>
  );
}

/* ============================================================
   COOKIE BANNER
   ============================================================ */
const COOKIE_KEY = "we_cookie_consent";

function CookieBanner({ go }) {
  const { t } = useTranslation();
  const [visible, setVisible] = React.useState(() => !localStorage.getItem(COOKIE_KEY));

  const accept = () => { localStorage.setItem(COOKIE_KEY, "accepted"); setVisible(false); };
  const refuse = () => { localStorage.setItem(COOKIE_KEY, "refused");  setVisible(false); };

  if (!visible) return null;

  return (
    <div className="cookie-banner" role="dialog" aria-label="Gestion des cookies">
      <div className="cookie-banner__text">
        {t('cookie.text')}{" "}
        <button className="cookie-banner__more" onClick={() => { refuse(); go("privacy"); }}>
          {t('cookie.more')}
        </button>
      </div>
      <div className="cookie-banner__btns">
        <button className="btn btn--glass btn--sm" onClick={refuse}>{t('cookie.refuse')}</button>
        <button className="btn btn--gold btn--sm"  onClick={accept}>{t('cookie.accept')}</button>
      </div>
    </div>
  );
}

/* ============================================================
   MENTIONS LÉGALES
   ============================================================ */
function LegalPage({ go }) {
  const { lang } = useTranslation();
  useMeta({ title: lang === 'en' ? "Legal Notice" : "Mentions légales", noindex: true });
  if (lang === 'en') return (
    <div className="app app--desktop">
      <PageHeader backTo="search" go={go} />
      <div className="page legal-page">
        <h1 className="page__h1">Legal Notice</h1>

        <section className="legal-section">
          <h2>Publisher</h2>
          <p>
            The website <strong>WatchEagle</strong> (available at <strong>watch-eagle.com</strong>) is published by:<br />
            <strong>WatchEagle</strong><br />
            Email: <a href="mailto:info@watch-eagle.com">info@watch-eagle.com</a>
          </p>
          <p>Publication director: WatchEagle</p>
        </section>

        <section className="legal-section">
          <h2>Hosting</h2>
          <p>
            <strong>Frontend</strong> — Vercel Inc., 340 Pine Street, Suite 701, San Francisco, CA 94104, United States.<br />
            <strong>Backend &amp; database</strong> — Railway Corp., 340 Pine Street Suite 501, San Francisco, CA 94104, United States.
          </p>
        </section>

        <section className="legal-section">
          <h2>Intellectual property</h2>
          <p>
            All content on WatchEagle (text, images, logo, code) is the exclusive property of the publisher, unless otherwise stated.
            Any reproduction, even partial, is prohibited without prior written authorisation.
          </p>
          <p>
            Watch photos published in listings are the sole responsibility of the dealers who uploaded them.
          </p>
        </section>

        <section className="legal-section">
          <h2>Limitation of liability</h2>
          <p>
            WatchEagle is a platform connecting buyers with independent watch dealers.
            WatchEagle cannot be held liable for transactions between users or for the accuracy of information provided by dealers.
          </p>
        </section>

        <section className="legal-section">
          <h2>Applicable law</h2>
          <p>This legal notice is governed by French law. Any dispute shall be submitted to the competent jurisdiction.</p>
        </section>

        <SiteFooter go={go} />
      </div>
    </div>
  );
  return (
    <div className="app app--desktop">
      <PageHeader backTo="search" go={go} />
      <div className="page legal-page">
        <h1 className="page__h1">Mentions légales</h1>

        <section className="legal-section">
          <h2>Éditeur du site</h2>
          <p>
            Le site <strong>WatchEagle</strong> (accessible à l'adresse <strong>watch-eagle.com</strong>) est édité par :<br />
            <strong>WatchEagle</strong><br />
            Email : <a href="mailto:info@watch-eagle.com">info@watch-eagle.com</a>
          </p>
          <p>Directeur de la publication : WatchEagle</p>
        </section>

        <section className="legal-section">
          <h2>Hébergement</h2>
          <p>
            <strong>Frontend</strong> — Vercel Inc., 340 Pine Street, Suite 701, San Francisco, CA 94104, États-Unis.<br />
            <strong>Backend &amp; base de données</strong> — Railway Corp., 340 Pine Street Suite 501, San Francisco, CA 94104, États-Unis.
          </p>
        </section>

        <section className="legal-section">
          <h2>Propriété intellectuelle</h2>
          <p>
            L'ensemble des contenus présents sur WatchEagle (textes, images, logo, code) est la propriété exclusive de l'éditeur, sauf mention contraire.
            Toute reproduction, même partielle, est interdite sans autorisation préalable écrite.
          </p>
          <p>
            Les photos de montres publiées dans les annonces sont sous la responsabilité des dealers qui les ont téléversées.
          </p>
        </section>

        <section className="legal-section">
          <h2>Limitation de responsabilité</h2>
          <p>
            WatchEagle est une plateforme de mise en relation entre acheteurs et dealers horlogers indépendants.
            WatchEagle ne saurait être tenu responsable des transactions effectuées entre les utilisateurs, ni de l'exactitude des informations fournies par les dealers.
          </p>
        </section>

        <section className="legal-section">
          <h2>Droit applicable</h2>
          <p>Les présentes mentions légales sont régies par le droit français. Tout litige sera soumis à la juridiction compétente.</p>
        </section>

        <SiteFooter go={go} />
      </div>
    </div>
  );
}

/* ============================================================
   CONDITIONS GÉNÉRALES D'UTILISATION
   ============================================================ */
function CguPage({ go }) {
  const { lang } = useTranslation();
  useMeta({ title: lang === 'en' ? "Terms of Service" : "Conditions générales d'utilisation", noindex: true });
  if (lang === 'en') return (
    <div className="app app--desktop">
      <PageHeader backTo="search" go={go} />
      <div className="page legal-page">
        <h1 className="page__h1">Terms of Service</h1>
        <p className="page__lead">Last updated: June 2026</p>

        <section className="legal-section">
          <h2>1. Purpose</h2>
          <p>
            These Terms of Service govern access to and use of the <strong>WatchEagle</strong> platform (watch-eagle.com),
            a geolocated directory of pre-owned watch dealers. WatchEagle connects private buyers with independent watch dealers.
            WatchEagle does not participate in transactions between parties and is neither a seller nor a buyer.
          </p>
        </section>

        <section className="legal-section">
          <h2>2. Acceptance</h2>
          <p>
            Using WatchEagle constitutes unconditional acceptance of these Terms. Anyone who does not accept these terms
            must stop using the platform.
          </p>
        </section>

        <section className="legal-section">
          <h2>3. Access to the platform</h2>
          <p>
            Browsing listings is free and does not require registration. An account is required to: set up search alerts,
            save listings as favourites, publish listings (dealers only).
          </p>
          <p>
            Registration is open to any adult natural person or legal entity. Each user is responsible for the
            confidentiality of their credentials.
          </p>
        </section>

        <section className="legal-section">
          <h2>4. Dealer obligations</h2>
          <p>By publishing listings on WatchEagle, the dealer agrees to:</p>
          <ul>
            <li>Provide accurate, complete, and up-to-date information about the watches offered (reference, condition, price).</li>
            <li>Publish only authentic photos of watches actually in stock.</li>
            <li>Not list counterfeit, stolen, or dubiously sourced watches.</li>
            <li>Remove or archive listings as soon as a watch is sold.</li>
            <li>Not use the platform for unsolicited commercial solicitation.</li>
            <li>Comply with all regulations applicable to reseller activity (VAT, traceability, etc.).</li>
          </ul>
        </section>

        <section className="legal-section">
          <h2>5. Buyer obligations</h2>
          <p>Buyers agree to:</p>
          <ul>
            <li>Use dealer contact details solely in the context of a genuine purchase enquiry.</li>
            <li>Not use the platform for commercial prospecting or solicitation.</li>
            <li>Not attempt to gain unauthorised access to WatchEagle's systems.</li>
          </ul>
        </section>

        <section className="legal-section">
          <h2>6. Prohibited content</h2>
          <p>It is prohibited to publish on WatchEagle any content that is:</p>
          <ul>
            <li>Unlawful, fraudulent, misleading, or infringing third-party rights.</li>
            <li>Related to counterfeit watches or illegal transactions.</li>
            <li>Unsolicited advertising or spam.</li>
            <li>Damaging to the reputation of WatchEagle or its users.</li>
          </ul>
          <p>WatchEagle reserves the right to remove any non-compliant content without notice.</p>
        </section>

        <section className="legal-section">
          <h2>7. WatchEagle's liability</h2>
          <p>
            WatchEagle is a content host under applicable law. WatchEagle does not systematically review published listings
            and cannot guarantee their accuracy. WatchEagle cannot be held liable for:
          </p>
          <ul>
            <li>Transactions concluded between buyers and dealers.</li>
            <li>The actual condition of watches described in listings.</li>
            <li>Temporary service interruptions for maintenance or force majeure.</li>
          </ul>
        </section>

        <section className="legal-section">
          <h2>8. Intellectual property</h2>
          <p>
            The code, design, logo, and editorial content of WatchEagle are the exclusive property of the publisher.
            Photos published by dealers remain their property; by uploading them, they grant WatchEagle a non-exclusive
            display licence on the platform.
          </p>
        </section>

        <section className="legal-section">
          <h2>9. Suspension and termination</h2>
          <p>
            WatchEagle reserves the right to suspend or delete any account that violates these Terms, without notice or
            compensation. Users may close their account at any time from their profile settings or by contacting{' '}
            <a href="mailto:info@watch-eagle.com">info@watch-eagle.com</a>.
          </p>
        </section>

        <section className="legal-section">
          <h2>10. Changes to these Terms</h2>
          <p>
            WatchEagle reserves the right to modify these Terms at any time. Users will be informed of material changes
            by email (if an account is linked). Continued use of the platform constitutes acceptance of the updated terms.
          </p>
        </section>

        <section className="legal-section">
          <h2>11. Applicable law</h2>
          <p>
            These Terms are governed by French law. Any dispute shall be submitted to the exclusive jurisdiction of the
            competent courts, after an attempt at amicable resolution.
          </p>
        </section>

        <SiteFooter go={go} />
      </div>
    </div>
  );
  return (
    <div className="app app--desktop">
      <PageHeader backTo="search" go={go} />
      <div className="page legal-page">
        <h1 className="page__h1">Conditions générales d'utilisation</h1>
        <p className="page__lead">Dernière mise à jour : juin 2026</p>

        <section className="legal-section">
          <h2>1. Objet</h2>
          <p>
            Les présentes Conditions Générales d'Utilisation (CGU) régissent l'accès et l'utilisation de la plateforme
            <strong> WatchEagle</strong> (watch-eagle.com), annuaire géolocalisé de revendeurs de montres de seconde main.
            WatchEagle met en relation des acheteurs particuliers avec des dealers horlogers indépendants. WatchEagle
            n'intervient pas dans les transactions entre les parties et n'est ni vendeur ni acheteur.
          </p>
        </section>

        <section className="legal-section">
          <h2>2. Acceptation</h2>
          <p>
            L'utilisation de WatchEagle implique l'acceptation sans réserve des présentes CGU. Toute personne qui
            n'accepte pas ces conditions doit cesser d'utiliser la plateforme.
          </p>
        </section>

        <section className="legal-section">
          <h2>3. Accès à la plateforme</h2>
          <p>
            La consultation des annonces est libre et gratuite, sans inscription. La création d'un compte est nécessaire
            pour : configurer des alertes de recherche, mettre des annonces en favoris, publier des annonces (dealers uniquement).
          </p>
          <p>
            L'inscription est ouverte à toute personne physique majeure ou entité juridique. Chaque utilisateur est
            responsable de la confidentialité de ses identifiants.
          </p>
        </section>

        <section className="legal-section">
          <h2>4. Obligations des dealers</h2>
          <p>En publiant des annonces sur WatchEagle, le dealer s'engage à :</p>
          <ul>
            <li>Fournir des informations exactes, complètes et à jour sur les montres proposées (référence, état, prix).</li>
            <li>Publier uniquement des photos authentiques des montres effectivement en stock.</li>
            <li>Ne pas publier de montres contrefaites, volées, ou dont la provenance est douteuse.</li>
            <li>Retirer ou archiver les annonces dès qu'une montre est vendue.</li>
            <li>Ne pas utiliser la plateforme à des fins de démarchage commercial non sollicité.</li>
            <li>Respecter l'ensemble des réglementations applicables à l'activité de revendeur (TVA, traçabilité, etc.).</li>
          </ul>
        </section>

        <section className="legal-section">
          <h2>5. Obligations des acheteurs</h2>
          <p>L'acheteur s'engage à :</p>
          <ul>
            <li>Utiliser les coordonnées des dealers uniquement dans le cadre d'une démarche d'achat sérieuse.</li>
            <li>Ne pas utiliser la plateforme à des fins de prospection commerciale ou de démarchage.</li>
            <li>Ne pas tenter d'accéder de façon non autorisée aux systèmes de WatchEagle.</li>
          </ul>
        </section>

        <section className="legal-section">
          <h2>6. Contenu interdit</h2>
          <p>Il est interdit de publier sur WatchEagle tout contenu :</p>
          <ul>
            <li>Illicite, frauduleux, trompeur ou portant atteinte aux droits de tiers.</li>
            <li>Relatif à des montres contrefaites ou à des transactions illégales.</li>
            <li>À caractère publicitaire non autorisé ou de spam.</li>
            <li>Portant atteinte à la réputation de WatchEagle ou de ses utilisateurs.</li>
          </ul>
          <p>WatchEagle se réserve le droit de supprimer tout contenu non conforme sans préavis.</p>
        </section>

        <section className="legal-section">
          <h2>7. Responsabilité de WatchEagle</h2>
          <p>
            WatchEagle est un hébergeur de contenu au sens de la loi. WatchEagle ne contrôle pas systématiquement
            les annonces publiées et ne peut garantir leur exactitude. WatchEagle ne saurait être tenu responsable :
          </p>
          <ul>
            <li>Des transactions conclues entre acheteurs et dealers.</li>
            <li>De l'état réel des montres décrites dans les annonces.</li>
            <li>Des interruptions temporaires de service pour maintenance ou cas de force majeure.</li>
          </ul>
        </section>

        <section className="legal-section">
          <h2>8. Propriété intellectuelle</h2>
          <p>
            Le code, le design, le logo et les contenus éditoriaux de WatchEagle sont la propriété exclusive de
            l'éditeur. Les photos publiées par les dealers restent leur propriété ; en les téléversant, ils accordent
            à WatchEagle une licence non exclusive d'affichage sur la plateforme.
          </p>
        </section>

        <section className="legal-section">
          <h2>9. Suspension et résiliation</h2>
          <p>
            WatchEagle se réserve le droit de suspendre ou supprimer tout compte en cas de violation des présentes CGU,
            sans préavis ni indemnité. L'utilisateur peut clôturer son compte à tout moment depuis les paramètres de
            son profil ou en contactant <a href="mailto:info@watch-eagle.com">info@watch-eagle.com</a>.
          </p>
        </section>

        <section className="legal-section">
          <h2>10. Modification des CGU</h2>
          <p>
            WatchEagle se réserve le droit de modifier les présentes CGU à tout moment. Les utilisateurs seront
            informés des modifications substantielles par email (si un compte est associé). La poursuite de l'utilisation
            de la plateforme vaut acceptation des nouvelles conditions.
          </p>
        </section>

        <section className="legal-section">
          <h2>11. Droit applicable</h2>
          <p>
            Les présentes CGU sont régies par le droit français. Tout litige sera soumis à la compétence exclusive
            des tribunaux compétents, après tentative de résolution amiable.
          </p>
        </section>

        <SiteFooter go={go} />
      </div>
    </div>
  );
}

/* ============================================================
   POLITIQUE DE CONFIDENTIALITÉ
   ============================================================ */
function PrivacyPage({ go }) {
  const { lang } = useTranslation();
  useMeta({ title: lang === 'en' ? "Privacy Policy" : "Politique de confidentialité", noindex: true });
  if (lang === 'en') return (
    <div className="app app--desktop">
      <PageHeader backTo="search" go={go} />
      <div className="page legal-page">
        <h1 className="page__h1">Privacy Policy</h1>
        <p className="page__lead">Last updated: June 2026</p>

        <section className="legal-section">
          <h2>1. Data controller</h2>
          <p>
            <strong>WatchEagle</strong><br />
            Contact: <a href="mailto:info@watch-eagle.com">info@watch-eagle.com</a>
          </p>
        </section>

        <section className="legal-section">
          <h2>2. Data collected</h2>
          <p>We collect only the data strictly necessary for the service to function:</p>
          <ul>
            <li><strong>Account data</strong>: email address, hashed password (argon2).</li>
            <li><strong>Dealer profile</strong> (if applicable): shop name, address, phone, logo, listing photos.</li>
            <li><strong>Search alerts</strong>: search criteria (brand, max price, city).</li>
            <li><strong>Push subscriptions</strong>: endpoint and encryption keys provided by your browser for notifications (if enabled).</li>
            <li><strong>Technical data</strong>: server logs (IP address, user-agent) retained for 30 days.</li>
          </ul>
          <p>We use <strong>no advertising cookies, no tracking pixels, and no ad networks</strong>.</p>
        </section>

        <section className="legal-section">
          <h2>3. Cookies and local storage</h2>
          <p>WatchEagle uses only essential local storage (<em>localStorage</em>) required for the service to work:</p>
          <ul>
            <li><strong>Authentication token</strong> (we_token) — keeps your session active. Duration: 8 hours.</li>
            <li><strong>Favourites</strong> (we_favs) — local cache synced with our database when you are signed in (table <em>favorites</em>: user ID + listing ID, no behavioural data).</li>
            <li><strong>Cookie consent</strong> (we_cookie_consent) — remembers your choice on the consent banner.</li>
          </ul>
          <p>No third-party cookies are set. You can clear this data at any time from your browser settings.</p>
          <p>WatchEagle uses <strong>Vercel Web Analytics</strong>, a cookie-free audience measurement tool that collects no personally identifiable data (no fingerprinting, no cross-referencing). No consent is required for this tool.</p>
        </section>

        <section className="legal-section">
          <h2>4. Purposes of processing</h2>
          <ul>
            <li>Creating and managing your account.</li>
            <li>Sending email or push notifications when a listing matches your alerts.</li>
            <li>Geolocated display of dealers on the map (GPS coordinates only, never shared with third parties).</li>
            <li>Anonymous listing statistics — each view and each click on "Contact" increments an aggregate counter visible to the relevant dealer. These counters are not linked to any identifiable user profile.</li>
            <li>Security and abuse prevention.</li>
          </ul>
        </section>

        <section className="legal-section">
          <h2>5. Data sharing</h2>
          <p>
            Your data is never sold or transferred to third parties for commercial purposes.
            It may be transmitted to the following technical sub-processors, solely for the purpose of delivering the service:
          </p>
          <ul>
            <li><strong>Neon</strong> (PostgreSQL database) — EU hosting.</li>
            <li><strong>Railway</strong> (application server) — US hosting, covered by GDPR standard contractual clauses.</li>
            <li><strong>Vercel</strong> (frontend) — US hosting, covered by GDPR standard contractual clauses.</li>
            <li><strong>Resend</strong> (transactional emails) — used only for alerts and welcome emails.</li>
          </ul>
        </section>

        <section className="legal-section">
          <h2>6. Retention periods</h2>
          <ul>
            <li>Account data: until account deletion or 3 years of inactivity.</li>
            <li>Listings: deleted on dealer request or 2 years after archiving.</li>
            <li>Push subscriptions: automatically deleted if the browser revokes the subscription, or upon request.</li>
            <li>Technical logs: 30 rolling days.</li>
          </ul>
        </section>

        <section className="legal-section">
          <h2>7. Your rights (GDPR)</h2>
          <p>Under the General Data Protection Regulation (GDPR — EU 2016/679), you have the following rights:</p>
          <ul>
            <li><strong>Access</strong>: obtain a copy of your personal data.</li>
            <li><strong>Rectification</strong>: correct inaccurate or incomplete data.</li>
            <li><strong>Erasure</strong> ("right to be forgotten"): request deletion of your account and data.</li>
            <li><strong>Objection</strong>: object to the processing of your data.</li>
            <li><strong>Portability</strong>: receive your data in a structured format.</li>
            <li><strong>Withdrawal of consent</strong>: revoke your consent to push notifications at any time.</li>
          </ul>
          <p>
            To exercise these rights, contact us at <a href="mailto:info@watch-eagle.com">info@watch-eagle.com</a>.
            We commit to responding within 30 days.
            You may also lodge a complaint with your local data protection authority (in France: <strong>CNIL</strong> — cnil.fr).
          </p>
        </section>

        <section className="legal-section">
          <h2>8. Security</h2>
          <p>
            Passwords are hashed with the argon2 algorithm (state of the art).
            All communications are encrypted via TLS/HTTPS.
            VAPID keys for push notifications use end-to-end encryption.
          </p>
        </section>

        <SiteFooter go={go} />
      </div>
    </div>
  );
  return (
    <div className="app app--desktop">
      <PageHeader backTo="search" go={go} />
      <div className="page legal-page">
        <h1 className="page__h1">Politique de confidentialité</h1>
        <p className="page__lead">Dernière mise à jour : juin 2026</p>

        <section className="legal-section">
          <h2>1. Responsable du traitement</h2>
          <p>
            <strong>WatchEagle</strong><br />
            Contact : <a href="mailto:info@watch-eagle.com">info@watch-eagle.com</a>
          </p>
        </section>

        <section className="legal-section">
          <h2>2. Données collectées</h2>
          <p>Nous collectons uniquement les données strictement nécessaires au fonctionnement du service :</p>
          <ul>
            <li><strong>Données de compte</strong> : adresse email, mot de passe chiffré (argon2).</li>
            <li><strong>Profil dealer</strong> (si applicable) : nom de la boutique, adresse, téléphone, logo, photos d'annonces.</li>
            <li><strong>Alertes de recherche</strong> : critères de recherche (marque, prix max, ville).</li>
            <li><strong>Abonnements push</strong> : endpoint et clés de chiffrement fournis par votre navigateur pour les notifications (si activées).</li>
            <li><strong>Données techniques</strong> : logs serveur (adresse IP, user-agent) conservés 30 jours.</li>
          </ul>
          <p>Nous n'utilisons <strong>aucun cookie publicitaire, aucun pixel de tracking, aucune régie publicitaire</strong>.</p>
        </section>

        <section className="legal-section">
          <h2>3. Cookies et stockage local</h2>
          <p>WatchEagle utilise uniquement des données de stockage local (<em>localStorage</em>) essentielles au fonctionnement :</p>
          <ul>
            <li><strong>Jeton d'authentification</strong> (we_token) — conserve votre session. Durée : 8 heures.</li>
            <li><strong>Favoris</strong> (we_favs) — cache local synchronisé avec notre base de données lorsque vous êtes connecté (table <em>favorites</em> : identifiant utilisateur + identifiant annonce, sans donnée comportementale).</li>
            <li><strong>Consentement cookies</strong> (we_cookie_consent) — mémorise votre choix sur ce bandeau.</li>
          </ul>
          <p>Aucun cookie tiers n'est déposé. Vous pouvez effacer ces données à tout moment depuis les paramètres de votre navigateur.</p>
          <p>WatchEagle utilise <strong>Vercel Web Analytics</strong>, un outil de mesure d'audience sans cookie et sans collecte de données personnelles identifiables (pas de fingerprinting, pas de croisement de données). Aucun consentement n'est requis pour cet outil.</p>
        </section>

        <section className="legal-section">
          <h2>4. Finalités du traitement</h2>
          <ul>
            <li>Création et gestion de votre compte.</li>
            <li>Envoi de notifications par email ou push lorsqu'une annonce correspond à vos alertes.</li>
            <li>Affichage géolocalisé des dealers sur la carte (coordonnées GPS uniquement, jamais partagées avec des tiers).</li>
            <li>Statistiques anonymes des annonces — chaque vue et chaque clic sur « Contacter » incrémentent un compteur agrégé visible par le dealer concerné. Ces compteurs ne sont liés à aucun profil utilisateur identifiable.</li>
            <li>Sécurité et prévention des abus.</li>
          </ul>
        </section>

        <section className="legal-section">
          <h2>5. Partage des données</h2>
          <p>
            Vos données ne sont jamais vendues ni cédées à des tiers à des fins commerciales.
            Elles peuvent être transmises aux sous-traitants techniques suivants, dans le strict cadre de l'exécution du service :
          </p>
          <ul>
            <li><strong>Neon</strong> (base de données PostgreSQL) — hébergement UE.</li>
            <li><strong>Railway</strong> (serveur applicatif) — hébergement États-Unis, couvert par les clauses contractuelles types RGPD.</li>
            <li><strong>Vercel</strong> (frontend) — hébergement États-Unis, couvert par les clauses contractuelles types RGPD.</li>
            <li><strong>Resend</strong> (emails transactionnels) — utilisé uniquement pour l'envoi des alertes et emails de bienvenue.</li>
          </ul>
        </section>

        <section className="legal-section">
          <h2>6. Durée de conservation</h2>
          <ul>
            <li>Données de compte : jusqu'à suppression du compte ou 3 ans d'inactivité.</li>
            <li>Annonces : supprimées sur demande du dealer ou 2 ans après leur archivage.</li>
            <li>Abonnements push : supprimés automatiquement si le navigateur révoque l'abonnement, ou sur demande.</li>
            <li>Logs techniques : 30 jours glissants.</li>
          </ul>
        </section>

        <section className="legal-section">
          <h2>7. Vos droits (RGPD)</h2>
          <p>Conformément au Règlement Général sur la Protection des Données (RGPD — UE 2016/679), vous disposez des droits suivants :</p>
          <ul>
            <li><strong>Accès</strong> : obtenir une copie de vos données personnelles.</li>
            <li><strong>Rectification</strong> : corriger vos données inexactes ou incomplètes.</li>
            <li><strong>Effacement</strong> (« droit à l'oubli ») : demander la suppression de votre compte et de vos données.</li>
            <li><strong>Opposition</strong> : vous opposer à un traitement de vos données.</li>
            <li><strong>Portabilité</strong> : recevoir vos données dans un format structuré.</li>
            <li><strong>Retrait du consentement</strong> : révoquer à tout moment votre consentement aux notifications push.</li>
          </ul>
          <p>
            Pour exercer ces droits, contactez-nous à <a href="mailto:info@watch-eagle.com">info@watch-eagle.com</a>.
            Nous nous engageons à répondre dans un délai de 30 jours.
            Vous pouvez également introduire une réclamation auprès de la <strong>CNIL</strong> (cnil.fr).
          </p>
        </section>

        <section className="legal-section">
          <h2>8. Sécurité</h2>
          <p>
            Les mots de passe sont chiffrés avec l'algorithme argon2 (état de l'art).
            Les communications sont chiffrées en TLS/HTTPS.
            Les clés VAPID pour les notifications push utilisent un chiffrement de bout en bout.
          </p>
        </section>

        <SiteFooter go={go} />
      </div>
    </div>
  );
}

/* ============================================================
   INNER PAGE HEADER
   ============================================================ */
function PageHeader({ backTo, backLabel, go, user, children }) {
  const { t } = useTranslation();
  return (
    <header className="topbar topbar--inner">
      <button className="backbtn" onClick={() => go(backTo)}>
        <Icon name="arrowL" size={18} /><span>{backLabel || t('btn.back')}</span>
      </button>
      <button className="topbar__brand" onClick={() => go("search")} style={{ background: "none" }}>
        <EagleMark size={57} />
        <span className="wordmark">WATCH<span className="wordmark__gold">EAGLE</span></span>
      </button>
      <div className="topbar__actions">
        {children}
        {!user && (
          <button className="acctbtn" onClick={() => go("login")}>
            <Icon name="user" size={17} /><span>{t('nav.login')}</span>
          </button>
        )}
      </div>
    </header>
  );
}

/* ============================================================
   LIGHTBOX
   ============================================================ */
function Lightbox({ shots, startIdx, onClose }) {
  const [idx,    setIdx]    = useState(startIdx);
  const [zoomed, setZoomed] = useState(false);
  const touchStartX = useRef(null);

  const total = shots.length;
  const prev  = () => { setZoomed(false); setIdx(i => (i - 1 + total) % total); };
  const next  = () => { setZoomed(false); setIdx(i => (i + 1) % total); };

  /* Keyboard navigation */
  useEffect(() => {
    const handler = e => {
      if (e.key === "ArrowLeft")  prev();
      if (e.key === "ArrowRight") next();
      if (e.key === "Escape")     onClose();
    };
    window.addEventListener("keydown", handler);
    return () => window.removeEventListener("keydown", handler);
  }, []);

  /* Body scroll lock */
  useEffect(() => {
    document.body.style.overflow = "hidden";
    return () => { document.body.style.overflow = ""; };
  }, []);

  /* Swipe detection */
  const onTouchStart = e => { touchStartX.current = e.touches[0].clientX; };
  const onTouchEnd   = e => {
    if (touchStartX.current === null) return;
    const dx = e.changedTouches[0].clientX - touchStartX.current;
    if (dx >  50) prev();
    if (dx < -50) next();
    touchStartX.current = null;
  };

  return (
    <div className="lightbox" onClick={e => { if (e.target === e.currentTarget) onClose(); }}>
      {/* Header controls */}
      <button className="lightbox__close" onClick={onClose} aria-label="Fermer">
        <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
          <path d="M4 4l12 12M16 4L4 16" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round"/>
        </svg>
      </button>
      {total > 1 && (
        <span className="lightbox__counter">{idx + 1} / {total}</span>
      )}

      {/* Main image */}
      <div
        className={"lightbox__stage" + (zoomed ? " lightbox__stage--zoom" : "")}
        onTouchStart={onTouchStart}
        onTouchEnd={onTouchEnd}
      >
        <img
          className="lightbox__img"
          src={shots[idx]}
          alt=""
          onClick={() => setZoomed(z => !z)}
          draggable={false}
        />
      </div>

      <span className="lightbox__zoom-hint">{t(zoomed ? 'lb.zoom_out' : 'lb.zoom_in')}</span>

      {/* Prev / Next arrows (desktop) */}
      {total > 1 && (
        <>
          <button className="lightbox__nav lightbox__nav--l" onClick={prev} aria-label="Précédent">
            <svg width="18" height="18" viewBox="0 0 18 18" fill="none">
              <path d="M11 4L6 9l5 5" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
            </svg>
          </button>
          <button className="lightbox__nav lightbox__nav--r" onClick={next} aria-label="Suivant">
            <svg width="18" height="18" viewBox="0 0 18 18" fill="none">
              <path d="M7 4l5 5-5 5" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
            </svg>
          </button>
        </>
      )}

      {/* Dot indicators */}
      {total > 1 && (
        <div className="lightbox__dots">
          {shots.map((_, i) => (
            <button key={i} className={"lightbox__dot" + (i === idx ? " lightbox__dot--on" : "")}
              onClick={() => { setZoomed(false); setIdx(i); }} aria-label={"Photo " + (i+1)} />
          ))}
        </div>
      )}
    </div>
  );
}

/* ============================================================
   DETAIL PAGE
   ============================================================ */
function DetailPage({ id, go, user, favIds, onFav, addToast }) {
  const { t } = useTranslation();
  const [watch,   setWatch]   = useState(null);
  const [loading, setLoading] = useState(true);
  const [imgIdx,  setImgIdx]  = useState(0);
  const [lbOpen,  setLbOpen]  = useState(false);
  const [related, setRelated] = useState([]);
  const { addRecent } = useRecent();

  // SEO meta + JSON-LD
  const watchPhoto = watch ? firstPhoto(watch) : null;
  useMeta({
    title: watch ? `${watch.brand} ${watch.model} ${watch.reference ? '– ' + watch.reference : ''}`.trim() : null,
    description: watch ? `${watch.brand} ${watch.model} · ${t('cond.' + condApiKey(watch.condition))} · ${eur(watch.price)}${watch.dealer?.city ? ' · ' + watch.dealer.city : ''}` : null,
    image: watchPhoto || undefined,
    type: "product",
  });

  useEffect(() => {
    if (!watch) return;
    const conditionMap = {
      "new":        "https://schema.org/NewCondition",
      "very_good":  "https://schema.org/UsedCondition",
      "good":       "https://schema.org/UsedCondition",
      "acceptable": "https://schema.org/DamagedCondition",
    };
    const product = {
      "@type": "Product",
      "name": `${watch.brand} ${watch.model}`,
      "description": watch.description || `${watch.brand} ${watch.model} ${watch.reference || ""}`.trim(),
      "image": watchPhoto ? [watchPhoto] : [],
      "brand": { "@type": "Brand", "name": watch.brand },
      "sku": watch.reference || String(watch.id),
      "url": window.location.href,
      "itemCondition": conditionMap[watch.condition] || "https://schema.org/UsedCondition",
      "offers": {
        "@type": "Offer",
        "price": watch.price ?? undefined,
        "priceCurrency": watch.currency || "EUR",
        "url": window.location.href,
        "availability": watch.status === "available"
          ? "https://schema.org/InStock"
          : "https://schema.org/OutOfStock",
        "seller": watch.dealer?.name ? {
          "@type": "LocalBusiness",
          "name": watch.dealer.name,
          ...(watch.dealer.slug ? { "url": `${window.location.origin}/dealer/${watch.dealer.slug}` } : {}),
        } : undefined,
      },
    };
    const breadcrumb = {
      "@type": "BreadcrumbList",
      "itemListElement": [
        { "@type": "ListItem", "position": 1, "name": "WatchEagle", "item": window.location.origin },
        { "@type": "ListItem", "position": 2, "name": watch.brand, "item": `${window.location.origin}/search?brand=${encodeURIComponent(watch.brand)}` },
        { "@type": "ListItem", "position": 3, "name": `${watch.brand} ${watch.model}`, "item": window.location.href },
      ],
    };
    const jsonld = { "@context": "https://schema.org", "@graph": [product, breadcrumb] };
    let el = document.getElementById("we-jsonld");
    if (!el) { el = document.createElement("script"); el.type = "application/ld+json"; el.id = "we-jsonld"; document.head.appendChild(el); }
    el.textContent = JSON.stringify(jsonld);
    return () => { const s = document.getElementById("we-jsonld"); if (s) s.remove(); };
  }, [watch]);

  useEffect(() => {
    if (!id) return;
    setLoading(true);
    api.get("/search/watches/" + id)
      .then(d => { setWatch(d); setImgIdx(0); addRecent(d); })
      .catch(e => addToast(e.message, "error"))
      .finally(() => setLoading(false));
  }, [id]);

  // Montres similaires (même marque, excluant la courante)
  useEffect(() => {
    if (!watch) return;
    api.get("/search/watches?brands=" + encodeURIComponent(watch.brand) + "&limit=6")
      .then(data => {
        const list = (data.items || data || []).filter(w => w.id !== watch.id).slice(0, 4);
        setRelated(list);
      })
      .catch(() => {});
  }, [watch?.brand]);

  const shareWatch = async () => {
    const url = window.location.href;
    try {
      await navigator.clipboard.writeText(url);
      addToast(t('toast.copied'), "success");
    } catch {
      addToast(t('toast.copy_fail'), "error");
    }
  };

  const contactClick = async type => {
    try { await api.post("/search/watches/" + id + "/contact-click", { contact_type: type }); } catch {}
    const d = (watch || {}).dealer || {};
    if (type === "phone") {
      if (d.phone) window.open("tel:" + d.phone);
      else addToast(t('toast.phone_missing'), "info");
    }
    if (type === "email") {
      if (d.email) window.open("mailto:" + d.email);
      else addToast(t('toast.email_missing'), "info");
    }
    if (type === "route") {
      if (d.maps_url) {
        // Lien Google Maps explicitement renseigné par le dealer
        window.open(d.maps_url, "_blank");
      } else if (d.address || d.city) {
        // Adresse texte = la plus précise (saisie par le dealer), Google Maps la géocode côté client
        const dest = encodeURIComponent([d.address, d.city, d.country].filter(Boolean).join(", "));
        window.open(`https://www.google.com/maps/dir/?api=1&destination=${dest}`, "_blank");
      } else if (d.lat && d.lng) {
        // Fallback : coordonnées GPS (moins précises si géocodées depuis la ville seulement)
        window.open(`https://www.google.com/maps/dir/?api=1&destination=${d.lat},${d.lng}`, "_blank");
      } else {
        addToast(t('toast.addr_missing'), "info");
      }
    }
  };

  const backTo = history.state?.from || "search";

  if (loading) return (
    <div className="app app--desktop">
      <PageHeader backTo={backTo} go={go} user={user} />
      <div className="page page--detail"><div className="empty"><div className="spinner" /></div></div>
    </div>
  );
  if (!watch) return (
    <div className="app app--desktop">
      <PageHeader backTo={backTo} go={go} user={user} />
      <div className="page page--detail">
        <div className="empty"><div className="empty__title">{t('detail.not_found')}</div>
          <button className="btn btn--gold" onClick={() => go("search")}>{t('detail.back_search')}</button>
        </div>
      </div>
    </div>
  );

  const d      = watch.dealer || {};
  const fav    = favIds.includes(watch.id);
  const photos = allPhotos(watch);
  const hue    = brandHue(watch.brand);
  const shots  = photos.length > 0 ? photos : [null, null, null, null];

  return (
    <div className="app app--desktop">
      <PageHeader backTo={backTo} go={go} user={user} />
      <div className="page page--detail">
        <nav className="breadcrumb">
          <button onClick={() => go("search")}>{t('detail.search')}</button>
          <Icon name="chevronR" size={13} />
          <span>{watch.brand}</span>
          <Icon name="chevronR" size={13} />
          <span className="breadcrumb__cur">{watch.model}</span>
        </nav>

        {lbOpen && shots.filter(Boolean).length > 0 && (
          <Lightbox shots={shots.filter(Boolean)} startIdx={imgIdx} onClose={() => setLbOpen(false)} />
        )}

        <div className="detail">
          {/* Gallery */}
          <div className="detail__gallery">
            <div className={"detail__hero" + (shots[imgIdx] ? " detail__hero--clickable" : "")}
              onClick={() => shots[imgIdx] && setLbOpen(true)}>
              {shots[imgIdx]
                ? <img src={shots[imgIdx]} alt={watch.brand + " " + watch.model}
                    style={{ position:"absolute",inset:0,width:"100%",height:"100%",objectFit:"cover" }} />
                : <DialPlaceholder hue={hue} brand={watch.brand} />}
              <span className="detail__statusbadge" style={{ "--c": statusColor(watch.status) }}>
                {t('status.' + watch.status)}
              </span>
            </div>
            <div className="detail__thumbs">
              {shots.slice(0, 4).map((src, i) => (
                <button key={i} className={"detail__thumb " + (i === imgIdx ? "detail__thumb--on" : "")}
                  onClick={() => setImgIdx(i)}>
                  {src
                    ? <img src={src} alt="" style={{ position:"absolute",inset:0,width:"100%",height:"100%",objectFit:"cover" }} />
                    : <DialPlaceholder hue={hue + i * 22} brand={watch.brand} />}
                </button>
              ))}
            </div>
          </div>

          {/* Info */}
          <div className="detail__info">
            <div className="detail__titlerow">
              <div style={{ flex: 1, minWidth: 0 }}>
                <div className="card__brand">{watch.brand}</div>
                <h1 className="detail__title">{watch.model}</h1>
                <div className="detail__sub">{watch.year} · {t('card.ref')} {watch.reference}</div>
              </div>
              <div style={{ display:"flex", gap:8 }}>
                <button className="iconbtn" title="Partager" onClick={shareWatch}
                  style={{ width:38, height:38 }}>
                  <Icon name="share" size={17} />
                </button>
                <button className={"card__fav card__fav--static " + (fav ? "card__fav--on" : "")}
                  onClick={() => onFav(watch.id)}>
                  <Icon name="heart" size={20} fill={fav ? "var(--gold)" : "none"} />
                </button>
              </div>
            </div>

            <div className="detail__pricerow">
              <span className="detail__price">{eur(watch.price)}</span>
              <span className="pill pill--ghost">{watch.currency || "EUR"}</span>
              {watch.price_negotiable && <span className="pill pill--ghost">Négociable</span>}
              {watch.appointment_only && (
                <span className="pill pill--rdv">
                  Sur RDV
                </span>
              )}
            </div>

            <div className="condrow">
              <span className={"condrow__item " + (watch.has_box        ? "on"      : "off")}>
                <Icon name={watch.has_box        ? "check" : "close"} size={13} /> {t('detail.box')}</span>
              <span className={"condrow__item " + (watch.has_papers     ? "on"      : "off")}>
                <Icon name={watch.has_papers     ? "check" : "close"} size={13} /> {t('detail.papers')}</span>
              <span className={"condrow__item " + (watch.recently_serviced ? "on"   : "off")}>
                <Icon name={watch.recently_serviced ? "check" : "close"} size={13} /> {t('detail.serviced')}</span>
              <span className="condrow__item neutral" style={{ display:"flex", alignItems:"center", gap:5 }}>
                <Icon name="shield" size={13} /> {t('cond.' + condApiKey(watch.condition))} <ConditionBadgeGuide /></span>
            </div>

            {watch.description && <p className="detail__desc">{watch.description}</p>}

            <div className="detail__divider" />

            <Glass className="dealerblock">
              <div className="dealerblock__head">
                <div className="dealerblock__logo">
                  {d.logo_url
                    ? <img src={d.logo_url} alt={d.name}
                        style={{ width:"100%",height:"100%",objectFit:"cover" }} />
                    : <EagleMark size={57} />}
                </div>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div className="dealerblock__name">
                    {d.name}
                    {d.is_verified && <span className="vbadge" title={t('dealer.verified_tooltip')}><Icon name="shield" size={11} sw={1.8} /></span>}
                  </div>
                  {d.gmaps_rating && (
                    <div className="dealerblock__rate">
                      <Stars value={d.gmaps_rating} />
                      <span>{d.gmaps_rating} · {d.gmaps_reviews} {t('dealer.gmaps_reviews')}</span>
                    </div>
                  )}
                </div>
              </div>
              {d.city && (
                <div className="dealerblock__addr">
                  <Icon name="pin" size={14} />
                  {' '}
                  {d.address_masked
                    ? [d.address || d.city, d.country].filter(Boolean).join(", ")
                    : [d.address, d.city, d.country].filter(Boolean).join(", ")
                  }
                  {d.address_masked && (
                    <span className="pill pill--rdv" style={{ marginLeft:6, fontSize:10, verticalAlign:"middle" }}>
                      {t('detail.addr_on_rdv')}
                    </span>
                  )}
                </div>
              )}
              <div className="dealerblock__cta">
                {watch.appointment_only ? (
                  <>
                    <button className="btn btn--gold"
                      onClick={() => contactClick(/Mobi|Android|iPhone/i.test(navigator.userAgent) ? "phone" : "email")}>
                      <Icon name="calendar" size={16} /> {t('detail.request_rdv')}
                    </button>
                  </>
                ) : (
                  <>
                    <button className="btn btn--gold" onClick={() => contactClick("phone")}>
                      <Icon name="phone" size={16} /> {t('detail.contact_phone')}
                    </button>
                    <button className="btn btn--glass" onClick={() => contactClick("email")}>
                      <Icon name="mail" size={16} /> {t('detail.contact_email')}
                    </button>
                    <button className="btn btn--glass" onClick={() => contactClick("route")}>
                      <Icon name="route" size={16} /> {t('detail.itineraire')}
                    </button>
                  </>
                )}
              </div>
            </Glass>

            {d.slug && (
              <button className="rowlink" onClick={() => go("dealer", d.slug)}>
                <span>{t('detail.see_all', { name: d.name })}</span>
                <Icon name="arrowR" size={16} />
              </button>
            )}
          </div>
        </div>

        {/* Vous aimerez aussi */}
        {related.length > 0 && (
          <div style={{ marginTop:48, paddingTop:28, borderTop:"1px solid rgba(255,255,255,0.06)" }}>
            <h2 style={{ fontSize:16, fontWeight:600, marginBottom:18 }}>
              {t('detail.related', { brand: watch.brand })}
            </h2>
            <div className="grid grid--auto">
              {related.map(w => (
                <WatchCard key={w.id} w={w} fav={favIds.includes(w.id)}
                  onFav={onFav} onOpen={w => { addRecent(w); go("watch", w.id); }} />
              ))}
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

/* ============================================================
   FAVORITES PAGE
   ============================================================ */
function FavoritesPage({ go, user, favIds, onFav, addToast }) {
  const { t } = useTranslation();
  const [watches, setWatches] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    if (!user) { setLoading(false); return; }
    api.get("/listings/favorites")
      .then(d => setWatches(d || []))
      .catch(e => addToast(e.message, "error"))
      .finally(() => setLoading(false));
  }, [user]);

  return (
    <div className="app app--desktop">
      <PageHeader backTo="search" go={go} user={user} />
      <div className="page">
        <h1 className="page__h1">{t('fav.title')}</h1>

        {!user ? (
          <div className="empty">
            <Icon name="heart" size={38} />
            <div className="empty__title">{t('fav.login.title')}</div>
            <div className="empty__sub">{t('fav.login.sub')}</div>
            <div style={{ display:"flex", gap:10, flexWrap:"wrap", justifyContent:"center" }}>
              <button className="btn btn--gold" onClick={() => go("register")}>{t('fav.create_account')}</button>
              <button className="btn btn--glass" onClick={() => go("login")}>{t('nav.login')}</button>
            </div>
          </div>
        ) : loading ? (
          <div className="empty"><div className="spinner" /></div>
        ) : watches.length ? (
          <>
            <p className="page__lead">
              {t(watches.length === 1 ? 'fav.pieces_one' : 'fav.pieces', { n: watches.length })}
            </p>
            <div className="grid grid--auto" style={{ marginTop: 22 }}>
              {watches.map(w => (
                <WatchCard key={w.id} w={w} fav={true} onFav={onFav}
                  onOpen={w => go("watch", w.id)} />
              ))}
            </div>
          </>
        ) : (
          <div className="empty">
            <Icon name="heart" size={38} />
            <div className="empty__title">{t('fav.empty.title')}</div>
            <div className="empty__sub">{t('fav.empty.sub')}</div>
            <button className="btn btn--gold" onClick={() => go("search")}>{t('fav.empty.cta')}</button>
          </div>
        )}
      </div>
    </div>
  );
}

/* ── Web Push helpers ──────────────────────────────────────────────────────── */
function urlBase64ToUint8Array(b64) {
  const pad = "=".repeat((4 - b64.length % 4) % 4);
  const b = (b64 + pad).replace(/-/g, "+").replace(/_/g, "/");
  const raw = atob(b);
  return Uint8Array.from([...raw].map(c => c.charCodeAt(0)));
}

function PushNotifBanner({ user, addToast }) {
  const { t } = useTranslation();
  // status: 'loading' | 'unsupported' | 'denied' | 'idle' | 'subscribed' | 'working'
  const [status, setStatus] = React.useState("loading");

  useEffect(() => {
    if (!user) { setStatus("unsupported"); return; }
    if (!("serviceWorker" in navigator) || !("PushManager" in window)) {
      setStatus("unsupported"); return;
    }
    if (Notification.permission === "denied") { setStatus("denied"); return; }
    // Check if we already have a subscription registered
    navigator.serviceWorker.getRegistration("/sw.js").then(reg => {
      if (!reg) { setStatus("idle"); return; }
      return reg.pushManager.getSubscription().then(sub => {
        setStatus(sub ? "subscribed" : "idle");
      });
    }).catch(() => setStatus("idle"));
  }, [user]);

  const enable = async () => {
    setStatus("working");
    try {
      const { public_key } = await api.get("/push/vapid-key");
      const reg = await navigator.serviceWorker.register("/sw.js", { scope: "/" });
      await navigator.serviceWorker.ready;
      const permission = await Notification.requestPermission();
      if (permission !== "granted") { setStatus("denied"); return; }
      const sub = await reg.pushManager.subscribe({
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array(public_key),
      });
      const j = sub.toJSON();
      await api.post("/push/subscribe", {
        endpoint: j.endpoint,
        p256dh: j.keys.p256dh,
        auth: j.keys.auth,
      });
      setStatus("subscribed");
      addToast(t('push.enabled'), "success");
    } catch (err) {
      setStatus("idle");
      if (err.message && err.message.includes("503")) {
        addToast(t('push.error_config'), "error");
      } else {
        addToast("Impossible d'activer les notifications : " + err.message, "error");
      }
    }
  };

  const disable = async () => {
    setStatus("working");
    try {
      const reg = await navigator.serviceWorker.getRegistration("/sw.js");
      if (reg) {
        const sub = await reg.pushManager.getSubscription();
        if (sub) {
          const j = sub.toJSON();
          await api.post("/push/unsubscribe", {
            endpoint: j.endpoint,
            p256dh: j.keys.p256dh,
            auth: j.keys.auth,
          });
          await sub.unsubscribe();
        }
      }
      setStatus("idle");
      addToast(t('push.disabled'), "info");
    } catch (err) {
      setStatus("subscribed");
      addToast("Erreur : " + err.message, "error");
    }
  };

  if (!user || status === "loading" || status === "unsupported") return null;

  return (
    <Glass className="push-banner">
      <div className="push-banner__icon">
        <Icon name="bell" size={20} />
      </div>
      <div className="push-banner__text">
        <div className="push-banner__title">
          {status === "subscribed" ? t('push.title_on') : t('push.title')}
        </div>
        <div className="push-banner__sub">
          {status === "denied"
            ? t('push.sub_denied')
            : status === "subscribed"
            ? t('push.sub_on')
            : t('push.sub')}
        </div>
      </div>
      <div className="push-banner__action">
        {status === "denied" ? (
          <span className="push-banner__blocked">{t('push.blocked')}</span>
        ) : status === "subscribed" ? (
          <button className="btn btn--glass btn--sm" onClick={disable} disabled={status === "working"}>
            {t('push.disable')}
          </button>
        ) : (
          <button className="btn btn--gold btn--sm" onClick={enable} disabled={status === "working"}>
            {status === "working" ? "…" : t('push.enable')}
          </button>
        )}
      </div>
    </Glass>
  );
}

/* ============================================================
   ALERTS PAGE
   ============================================================ */
function AlertsPage({ go, user, addToast }) {
  const { t } = useTranslation();
  useMeta({ title: t('alerts.title'), noindex: true });
  const [alerts,   setAlerts]   = useState([]);
  const [loading,  setLoading]  = useState(true);
  const [showForm, setShowForm] = useState(false);
  const [form,     setForm]     = useState({ keywords:"", max_price:"", city:"", radius_km:50 });
  const setF = (k, v) => setForm(p => ({ ...p, [k]: v }));

  useEffect(() => {
    if (!user) { setLoading(false); return; }
    api.get("/alerts")
      .then(d => setAlerts(d || []))
      .catch(e => addToast(e.message, "error"))
      .finally(() => setLoading(false));
  }, [user]);

  // Pré-remplir le formulaire si on arrive depuis "Créer une alerte" sur la recherche vide
  useEffect(() => {
    try {
      const raw = sessionStorage.getItem("we_alert_prefill");
      if (!raw) return;
      const prefill = JSON.parse(raw);
      sessionStorage.removeItem("we_alert_prefill");
      if (prefill.keywords) setF("keywords", prefill.keywords);
      if (prefill.max_price) setF("max_price", String(prefill.max_price));
      setShowForm(true);
    } catch {}
  }, []);

  const toggle = async id => {
    try {
      await api.patch("/alerts/" + id + "/toggle");
      setAlerts(p => p.map(a => a.id === id ? { ...a, is_active: !a.is_active } : a));
    } catch (e) { addToast(e.message, "error"); }
  };

  const del = async id => {
    try {
      await api.delete("/alerts/" + id);
      setAlerts(p => p.filter(a => a.id !== id));
      addToast(t('alerts.deleted'), "info");
    } catch (e) { addToast(e.message, "error"); }
  };

  const create = async () => {
    try {
      const body = { keywords: form.keywords, city: form.city, radius_km: +form.radius_km };
      if (form.max_price) body.max_price = +form.max_price;
      const a = await api.post("/alerts", body);
      setAlerts(p => [...p, a]);
      setShowForm(false);
      setForm({ keywords:"", max_price:"", city:"", radius_km:50 });
      addToast(t('alerts.created'), "success");
    } catch (e) { addToast(e.message, "error"); }
  };

  return (
    <div className="app app--desktop">
      <PageHeader backTo="search" go={go} user={user} />
      <div className="page">
        {!user ? (
          <div className="empty">
            <Icon name="bell" size={38} />
            <div className="empty__title">{t('alerts.login.title')}</div>
            <div className="empty__sub">{t('alerts.login.sub')}</div>
            <div style={{ display:"flex", gap:10, flexWrap:"wrap", justifyContent:"center" }}>
              <button className="btn btn--gold" onClick={() => go("register")}>{t('alerts.create_account')}</button>
              <button className="btn btn--glass" onClick={() => go("login")}>{t('nav.login')}</button>
            </div>
          </div>
        ) : (
          <>
            <div className="page__headrow">
              <div>
                <h1 className="page__h1">{t('alerts.title')}</h1>
                <p className="page__lead">{t('alerts.lead', { n: alerts.length })}</p>
              </div>
              <button className="btn btn--gold" disabled={alerts.length >= 5}
                onClick={() => setShowForm(v => !v)}>
                <Icon name="plus" size={16} /> {t('alerts.new')}
              </button>
            </div>

            <PushNotifBanner user={user} addToast={addToast} />

            {showForm && (
              <Glass style={{ padding: 22, margin: "18px 0" }}>
                <div className="field__row" style={{ marginBottom: 12 }}>
                  <div className="field">
                    <label className="field__label">{t('alerts.form.keywords')}</label>
                    <input className="input" placeholder={t('alerts.form.keywords_ph')}
                      value={form.keywords} onChange={e => setF("keywords", e.target.value)} />
                  </div>
                  <div className="field">
                    <label className="field__label">{t('alerts.form.price_max')}</label>
                    <input className="input" type="number" placeholder={t('alerts.form.price_ph')}
                      value={form.max_price} onChange={e => setF("max_price", e.target.value)} />
                  </div>
                </div>
                <div className="field__row" style={{ marginBottom: 18 }}>
                  <div className="field">
                    <label className="field__label">{t('alerts.form.city')}</label>
                    <input className="input" value={form.city}
                      onChange={e => setF("city", e.target.value)}
                      placeholder={t('loc.city_placeholder')} />
                  </div>
                  <div className="field">
                    <label className="field__label">{t('alerts.form.radius')}</label>
                    <input className="input" type="number" value={form.radius_km}
                      onChange={e => setF("radius_km", e.target.value)} />
                  </div>
                </div>
                <div style={{ display:"flex", gap:10 }}>
                  <button className="btn btn--gold" onClick={create}>{t('alerts.create')}</button>
                  <button className="btn btn--glass" onClick={() => setShowForm(false)}>{t('btn.cancel')}</button>
                </div>
              </Glass>
            )}

            {loading ? <div className="empty"><div className="spinner" /></div> : (
              <div className="alertlist">
                {alerts.length === 0 && !showForm && (
                  <div className="empty">
                    <Icon name="bell" size={38} />
                    <div className="empty__title">{t('alerts.empty.title')}</div>
                    <div className="empty__sub">{t('alerts.empty.sub')}</div>
                  </div>
                )}
                {alerts.map(a => (
                  <Glass key={a.id} className="alertcard">
                    <div className="alertcard__main">
                      <div className="alertcard__kw">{a.keywords || a.brand || "Alerte"}</div>
                      <div className="alertcard__meta">
                        {a.max_price ? "≤ " + eur(a.max_price) + " · " : ""}
                        {a.city} · {a.radius_km} km
                        {a.trigger_count > 0 && (
                          <span className="alertcard__hits">
                            {t(a.trigger_count === 1 ? 'alerts.triggers_one' : 'alerts.triggers', { n: a.trigger_count })}
                          </span>
                        )}
                      </div>
                    </div>
                    <div className="alertcard__actions">
                      <button className={"switch " + (a.is_active ? "switch--on" : "")}
                        onClick={() => toggle(a.id)}>
                        <span className="switch__dot" />
                      </button>
                      <button className="iconbtn iconbtn--sm" onClick={() => del(a.id)}>
                        <Icon name="trash" size={16} />
                      </button>
                    </div>
                  </Glass>
                ))}
              </div>
            )}
          </>
        )}
      </div>
    </div>
  );
}

/* ============================================================
   EMAIL VERIFY BANNER
   ============================================================ */
function EmailVerifyBanner({ addToast }) {
  const { t } = useTranslation();
  const [sent, setSent] = useState(false);
  const [dismissed, setDismissed] = useState(false);
  if (dismissed) return null;
  const resend = async () => {
    try {
      await api.post("/auth/resend-verification");
      setSent(true);
    } catch (e) { addToast(e.message, "error"); }
  };
  return (
    <div style={{ background:"rgba(201,168,76,.1)", borderBottom:"1px solid rgba(201,168,76,.2)",
                  padding:"9px 20px", display:"flex", alignItems:"center", gap:12,
                  fontSize:13, position:"relative", zIndex:50 }}>
      <span style={{ color:"var(--gold)", flex:1 }}>
        ✉️ {sent ? t('verify.sent') : t('verify.banner')}
      </span>
      {!sent && (
        <button onClick={resend}
          style={{ background:"none", border:"1px solid rgba(201,168,76,.4)", color:"var(--gold)",
                   padding:"4px 10px", borderRadius:6, cursor:"pointer", fontSize:12, fontWeight:600 }}>
          {t('verify.resend')}
        </button>
      )}
      <button onClick={() => setDismissed(true)}
        style={{ background:"none", border:"none", color:"var(--muted)", cursor:"pointer",
                 fontSize:18, lineHeight:1, padding:"0 2px" }}>×</button>
    </div>
  );
}

/* ============================================================
   VERIFY EMAIL PAGE
   ============================================================ */
function VerifyEmailPage({ go, token, addToast, updateVerified }) {
  const { t } = useTranslation();
  const [status, setStatus] = useState("loading"); // loading | success | error

  useEffect(() => {
    if (!token) { setStatus("error"); return; }
    api.get(`/auth/verify-email?token=${encodeURIComponent(token)}`)
      .then(() => { setStatus("success"); updateVerified(); })
      .catch(() => setStatus("error"));
  }, [token]);

  return (
    <div className="authstage">
      <div className="authstage__aura" />
      <Glass className="authcard" style={{ textAlign:"center" }}>
        <div className="authcard__brand" style={{ justifyContent:"center", marginBottom:24 }}>
          <EagleMark size={48} />
          <span className="wordmark" style={{ fontSize:20 }}>WATCH<span className="wordmark__gold">EAGLE</span></span>
        </div>
        {status === "loading" && (
          <>
            <div className="spinner" style={{ margin:"0 auto 16px" }} />
            <p style={{ color:"var(--muted)", fontSize:14 }}>{t('verify.loading')}</p>
          </>
        )}
        {status === "success" && (
          <>
            <div style={{ fontSize:48, marginBottom:16 }}>✅</div>
            <h1 style={{ fontSize:20, fontWeight:700, color:"var(--text)", margin:"0 0 10px" }}>{t('verify.title')}</h1>
            <p style={{ color:"var(--muted)", fontSize:14, margin:"0 0 24px" }}>{t('verify.sub')}</p>
            <button className="btn btn--gold" style={{ width:"100%" }} onClick={() => go("search")}>
              {t('verify.go_home')}
            </button>
          </>
        )}
        {status === "error" && (
          <>
            <div style={{ fontSize:48, marginBottom:16 }}>❌</div>
            <h1 style={{ fontSize:20, fontWeight:700, color:"var(--text)", margin:"0 0 10px" }}>{t('verify.error_title')}</h1>
            <p style={{ color:"var(--muted)", fontSize:14, margin:"0 0 24px" }}>{t('verify.error_sub')}</p>
            <button className="btn btn--glass" style={{ width:"100%", color:"var(--text)" }} onClick={() => go("login")}>
              {t('btn.back')}
            </button>
          </>
        )}
      </Glass>
    </div>
  );
}

/* ============================================================
   AUTH PAGE
   ============================================================ */
function AuthPage({ go, mode, onLogin }) {
  const { t } = useTranslation();
  useMeta({ title: mode === 'register' ? t('nav.register') : t('nav.login'), noindex: true });
  const isReg   = mode === "register";
  const [email, setEmail] = useState("");
  const [pw,    setPw]    = useState("");
  const [showPw,setShowPw]= useState(false);
  const [loading,setLoading]=useState(false);
  const [err,   setErr]   = useState("");
  const [forgotSent, setForgotSent] = useState(false);

  const submit = async () => {
    setErr(""); setLoading(true);
    try {
      if (isReg) {
        const regRes = await api.post("/auth/register", { email, password: pw, role: "buyer" });
        const u = { email, role: regRes.role || "buyer", is_verified: false };
        onLogin(u, regRes.access_token, regRes.refresh_token);
        go("search");
      } else {
        const res = await api.post("/auth/login", { email, password: pw });
        const u = { email, role: res.role || "buyer", is_verified: res.is_verified ?? true };
        onLogin(u, res.access_token, res.refresh_token);
        go(u.role === "dealer" ? "dashboard" : "search");
      }
    } catch (e) { setErr(e.message); }
    finally { setLoading(false); }
  };

  const forgot = async () => {
    if (!email) { setErr("Entrez votre email d'abord"); return; }
    try { await api.post("/auth/forgot-password", { email }); setForgotSent(true); }
    catch (e) { setErr(e.message); }
  };

  return (
    <div className="authstage">
      <div className="authstage__aura" />
      <Glass className="authcard">
        <div className="authcard__brand">
          <EagleMark size={57} />
          <span className="wordmark" style={{ fontSize:22 }}>
            WATCH<span className="wordmark__gold">EAGLE</span>
          </span>
        </div>
        <h1 className="authcard__title">{isReg ? t('auth.register.title') : t('auth.login.title')}</h1>
        <p className="authcard__sub">
          {isReg ? t('auth.register.sub') : t('auth.login.sub')}
        </p>


        {err && (
          <div className="toast toast--error" style={{ marginBottom:14, pointerEvents:"auto" }}>
            <Icon name="close" size={14} /><span>{err}</span>
          </div>
        )}
        {forgotSent && (
          <div className="toast toast--success" style={{ marginBottom:14, pointerEvents:"auto" }}>
            <Icon name="check" size={14} /><span>{t('auth.forgot.sent')}</span>
          </div>
        )}

        <div className="field">
          <label className="field__label">{t('auth.email')}</label>
          <input className="input" type="email" placeholder="vous@exemple.com"
            value={email} onChange={e => setEmail(e.target.value)} />
        </div>

        <div className="field">
          <label className="field__label">{t('auth.password')}</label>
          <div className="pwfield">
            <input className="input" type={showPw ? "text" : "password"} placeholder="••••••••"
              value={pw} onChange={e => setPw(e.target.value)}
              onKeyDown={e => e.key === "Enter" && submit()} />
            <button type="button" className="pwfield__eye" onClick={() => setShowPw(v => !v)}>
              <Icon name={showPw ? "eyeOff" : "eye"} size={18} />
            </button>
          </div>
        </div>

        <button className="btn btn--gold btn--lg authcard__submit" onClick={submit} disabled={loading}>
          {loading ? t(isReg ? 'auth.loading.register' : 'auth.loading.login') : t(isReg ? 'auth.register.btn' : 'auth.login.btn')}
        </button>

        {!isReg && (
          <button className="authcard__link" onClick={forgot}>{t('auth.forgot_pw')}</button>
        )}

        <div className="authcard__divider"><span>{t('auth.or')}</span></div>

        {isReg
          ? <button className="btn btn--glass btn--lg" onClick={() => go("login")}>{t('auth.login.link')}</button>
          : <button className="btn btn--glass btn--lg" onClick={() => go("register")}>{t('auth.register.link')}</button>}
      </Glass>
    </div>
  );
}

/* ============================================================
   DASHBOARD
   ============================================================ */
const DASH_STATUS_TAB_VALUES = [null, "available", "reserved", "sold", "archived"];

/* ── Profile Tab ────────────────────────────────────────────── */
function ProfileTab({ dealer, needsOnboard, addToast, onSaved, onDealerUpdate }) {
  const { t } = useTranslation();
  const blank = {
    name: "", description: "", phone: "", email: "",
    address: "", city: "", postal_code: "", country: "France",
    website_url: "", specialties: "", languages: "", years_experience: "",
    opening_hours: "", logo_url: "", address_masked: false, notif_digest: true,
  };
  const [form, setForm] = useState(() => dealer ? {
    name:             dealer.name             || "",
    description:      dealer.description      || "",
    phone:            dealer.phone            || "",
    email:            dealer.email            || "",
    address:          dealer.address          || "",
    city:             dealer.city             || "",
    postal_code:      dealer.postal_code      || "",
    country:          dealer.country          || "France",
    website_url:      dealer.website_url      || "",
    specialties:      dealer.specialties      || "",
    languages:        dealer.languages        || "",
    years_experience: dealer.years_experience != null ? String(dealer.years_experience) : "",
    opening_hours:    dealer.opening_hours    || "",
    logo_url:         dealer.logo_url         || "",
    address_masked:   !!dealer.address_masked,
    notif_digest:     dealer.notif_digest !== false,
  } : blank);
  const [saving,       setSaving]       = useState(false);
  const [logoUploading,setLogoUploading]= useState(false);
  const logoRef = useRef(null);

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

  const handleLogoUpload = async e => {
    const file = e.target.files?.[0];
    if (!file) return;
    const prevUrl = form.logo_url;
    setLogoUploading(true);
    set("logo_url", URL.createObjectURL(file)); // optimistic preview
    try {
      const fd = new FormData();
      fd.append("file", file);
      const r = await api.upload("/upload/image?folder=dealers", fd); // folder en query param
      const absUrl = toAbsoluteImgUrl(r.url); // stocker une URL absolue en DB
      set("logo_url", absUrl);
      // Auto-save immédiatement → met à jour la barre de progression
      const updated = await api.patch("/dealers/me/logo", { logo_url: absUrl });
      if (onDealerUpdate) onDealerUpdate(updated);
      addToast(t('profile.logo_updated'), "success");
    } catch (err) {
      set("logo_url", prevUrl); // revert
      addToast(err.message, "error");
    } finally {
      setLogoUploading(false);
    }
  };

  const save = async e => {
    e.preventDefault();
    if (!form.name.trim()) { addToast(t('profile.shop_name_required'), "error"); return; }
    setSaving(true);
    try {
      const endpoint = needsOnboard ? "/dealers/onboard" : "/dealers/me";
      const method   = needsOnboard ? "post"             : "patch";
      const payload = { ...form, years_experience: form.years_experience !== "" ? +form.years_experience : null };
      const data = await api[method](endpoint, payload);
      addToast(t(needsOnboard ? 'profile.created' : 'profile.saved'), "success");
      onSaved(data);
    } catch (err) {
      addToast(err.message, "error");
    } finally {
      setSaving(false);
    }
  };

  return (
    <form className="profilform" onSubmit={save}>
      <div className="dash__titlerow">
        <div>
          <h1 className="page__h1">{t(needsOnboard ? 'profile.create' : 'profile.title')}</h1>
          <p className="page__lead">{t(needsOnboard ? 'profile.create_sub' : 'profile.edit_sub')}</p>
        </div>
        <button type="submit" className="btn btn--gold" disabled={saving}>
          {saving ? t('profile.saving') : t(needsOnboard ? 'profile.create' : 'profile.save')}
        </button>
      </div>

      {/* Logo upload */}
      <Glass className="profilform__section" style={{ marginBottom:20, padding:20 }}>
        <h2 className="profilform__h2">{t('profile.logo_section')}</h2>
        <div style={{ display:"flex", alignItems:"center", gap:16 }}>
          <div style={{ width:72, height:72, borderRadius:"50%", overflow:"hidden", flexShrink:0,
            background:"rgba(255,255,255,0.06)", border:"1px solid rgba(255,255,255,0.1)",
            display:"flex", alignItems:"center", justifyContent:"center" }}>
            {form.logo_url
              ? <img src={resolveImgUrl(form.logo_url)} alt="Logo"
                  style={{ width:"100%", height:"100%", objectFit:"cover" }} />
              : <EagleMark size={57} />}
          </div>
          <div>
            <button type="button" className="btn btn--glass" style={{ fontSize:13 }}
              onClick={() => logoRef.current?.click()} disabled={logoUploading}>
              <Icon name="upload" size={14} />
              {logoUploading ? t('profile.logo_uploading') : t('profile.logo_upload')}
            </button>
            <div style={{ fontSize:12, color:"var(--muted)", marginTop:6 }}>{t('profile.logo_hint')}</div>
          </div>
          <input ref={logoRef} type="file" accept="image/*" style={{ display:"none" }}
            onChange={handleLogoUpload} />
        </div>
      </Glass>

      <div className="profilform__grid">
        {/* Left column */}
        <div className="profilform__col">
          <Glass className="profilform__section">
            <h2 className="profilform__h2">{t('profile.general')}</h2>
            <label className="profilform__label">
              {t('profile.shop_name')} <span style={{color:"var(--gold)"}}>*</span>
              <input className="input" value={form.name} onChange={e => set("name", e.target.value)}
                placeholder="Ex : Montres Prestige Paris" required />
            </label>
            <label className="profilform__label">
              {t('profile.description')}
              <textarea className="input textarea" rows={4} value={form.description}
                onChange={e => set("description", e.target.value)}
                placeholder="Présentez votre boutique, votre expertise, vos services…" />
            </label>
            <label className="profilform__label">
              {t('profile.specialties')}
              <input className="input" value={form.specialties}
                onChange={e => set("specialties", e.target.value)}
                placeholder="Ex : Rolex, Patek Philippe, montres vintage" />
            </label>
            <label className="profilform__label">
              {t('profile.languages')}
              <input className="input" value={form.languages}
                onChange={e => set("languages", e.target.value)}
                placeholder="Ex : Français, Anglais, Arabe" />
            </label>
            <label className="profilform__label">
              {t('profile.years_experience')}
              <input className="input" type="number" min="0" max="99"
                value={form.years_experience}
                onChange={e => set("years_experience", e.target.value)}
                placeholder={t('profile.years_experience_ph')} />
            </label>
          </Glass>

          <Glass className="profilform__section">
            <h2 className="profilform__h2">{t('profile.contact')}</h2>
            <label className="profilform__label">
              {t('profile.email')}
              <input className="input" type="email" value={form.email}
                onChange={e => set("email", e.target.value)}
                placeholder="contact@maboutique.fr" />
            </label>
            <label className="profilform__label">
              {t('profile.phone')}
              <input className="input" type="tel" value={form.phone}
                onChange={e => set("phone", e.target.value)}
                placeholder="+33 1 23 45 67 89" />
            </label>
            <label className="profilform__label">
              {t('profile.website')}
              <input className="input" type="url" value={form.website_url}
                onChange={e => set("website_url", e.target.value)}
                placeholder="https://www.maboutique.fr" />
            </label>
          </Glass>
        </div>

        {/* Right column */}
        <div className="profilform__col">
          <Glass className="profilform__section">
            <h2 className="profilform__h2">{t('profile.address_section')}</h2>
            <label className="profilform__label">
              {t('profile.address')}
              <input className="input" value={form.address}
                onChange={e => set("address", e.target.value)}
                placeholder="12 rue de la Paix" />
            </label>
            <div style={{ display:"grid", gridTemplateColumns:"1fr 1fr", gap:12 }}>
              <label className="profilform__label">
                {t('profile.city')}
                <input className="input" value={form.city}
                  onChange={e => set("city", e.target.value)}
                  placeholder="Paris" />
              </label>
              <label className="profilform__label">
                {t('profile.postal_code')}
                <input className="input" value={form.postal_code}
                  onChange={e => set("postal_code", e.target.value)}
                  placeholder="75001" />
              </label>
            </div>
            <label className="profilform__label">
              {t('profile.country')}
              <input className="input" value={form.country}
                onChange={e => set("country", e.target.value)}
                placeholder="France" />
            </label>
            <button type="button" className="ftoggle" style={{ marginTop:8 }}
              onClick={() => set("address_masked", !form.address_masked)}>
              <div style={{ textAlign:"left" }}>
                <span style={{ display:"block", fontWeight:500 }}>{t('profile.address_masked')}</span>
                <span style={{ display:"block", fontSize:11, color:"var(--muted)", marginTop:2 }}>
                  {t('profile.address_masked_hint')}
                </span>
              </div>
              <span className={"switch " + (form.address_masked ? "switch--on" : "")}>
                <span className="switch__dot" />
              </span>
            </button>
            <button type="button" className="ftoggle" style={{ marginTop:8 }}
              onClick={() => set("notif_digest", !form.notif_digest)}>
              <div style={{ textAlign:"left" }}>
                <span style={{ display:"block", fontWeight:500 }}>Digest hebdomadaire</span>
                <span style={{ display:"block", fontSize:11, color:"var(--muted)", marginTop:2 }}>
                  Recevoir un récapitulatif de vos stats chaque lundi matin
                </span>
              </div>
              <span className={"switch " + (form.notif_digest ? "switch--on" : "")}>
                <span className="switch__dot" />
              </span>
            </button>
          </Glass>

          <Glass className="profilform__section">
            <h2 className="profilform__h2">{t('profile.hours_section')}</h2>
            <label className="profilform__label">
              {t('profile.hours_hint')}
              <textarea className="input textarea" rows={4} value={form.opening_hours}
                onChange={e => set("opening_hours", e.target.value)}
                placeholder={"Lun-Ven : 10h-19h\nSamedi : 10h-17h\nDimanche : Fermé"} />
            </label>
          </Glass>

          {!needsOnboard && dealer && (
            <Glass className="profilform__section">
              <h2 className="profilform__h2">{t('profile.account_info')}</h2>
              <div className="profilform__info-row">
                <span className="profilform__info-label">{t('profile.plan')}</span>
                <span className="badge badge--gold">{dealer.plan || "free"}</span>
              </div>
              <div className="profilform__info-row">
                <span className="profilform__info-label">{t('profile.max_listings')}</span>
                <span>{dealer.max_listings}</span>
              </div>
              <div className="profilform__info-row">
                <span className="profilform__info-label">{t('profile.active_listings')}</span>
                <span>{dealer.active_listings_count}</span>
              </div>
              <div className="profilform__info-row">
                <span className="profilform__info-label">{t('profile.verified')}</span>
                <span>{dealer.is_verified ? t('profile.verified_yes') : t('profile.verified_no')}</span>
              </div>
            </Glass>
          )}
        </div>
      </div>

      <div className="profilform__submit-mobile">
        <button type="submit" className="btn btn--gold btn--full" disabled={saving}>
          {saving ? t('profile.saving') : t(needsOnboard ? 'profile.create' : 'profile.save_mobile')}
        </button>
      </div>
    </form>
  );
}

/* ============================================================
   CLAIM DEALER FLOW
   ============================================================ */
function ClaimDealerFlow({ go, user, addToast, onDone, onCancel, initialDealer }) {
  const { t } = useTranslation();
  const [step, setStep]       = useState("search"); // search | code | pending
  const [q, setQ]             = useState("");
  const [results, setResults] = useState([]);
  const [selected, setSelected] = useState(null);
  const [code, setCode]       = useState("");
  const [hasEmail, setHasEmail] = useState(false);
  const [emailHint, setEmailHint] = useState("");
  const [loading, setLoading] = useState(false);
  const [err, setErr]         = useState("");

  const search = async () => {
    setLoading(true); setErr("");
    try {
      const res = await api.get(`/dealers/search?q=${encodeURIComponent(q.trim())}`);
      setResults(res || []);
    } catch (e) { setErr(e.message); }
    finally { setLoading(false); }
  };

  // Si dealer pré-sélectionné (depuis fiche), déclencher directement la revendication
  useEffect(() => {
    if (initialDealer) {
      requestClaim(initialDealer);
      return;
    }
    setLoading(true);
    api.get("/dealers/search?q=")
      .then(r => setResults(r || []))
      .catch(e => setErr(e.message))
      .finally(() => setLoading(false));
  }, []);

  const requestClaim = async (dealer) => {
    setSelected(dealer); setLoading(true); setErr("");
    try {
      const res = await api.post(`/dealers/me/claim/${dealer.slug}/request`);
      if (res.status === "pending_no_email") {
        setStep("pending");
      } else {
        setHasEmail(true);
        setEmailHint(res.email_hint || "");
        setStep("code");
      }
    } catch (e) { setErr(e.message); setSelected(null); }
    finally { setLoading(false); }
  };

  const verifyClaim = async () => {
    setLoading(true); setErr("");
    try {
      await api.post(`/dealers/me/claim/${selected.slug}/verify`, { code });
      setStep("pending");
    } catch (e) { setErr(e.message); }
    finally { setLoading(false); }
  };

  const cancelClaim = async () => {
    if (!selected) return;
    try { await api.delete(`/dealers/me/claim/${selected.slug}`); }
    catch (_) {}
    setStep("search"); setSelected(null); setCode(""); setErr("");
  };

  if (step === "pending") return (
    <div style={{ textAlign:"center", padding:"40px 0" }}>
      <div style={{ fontSize:40, marginBottom:16 }}>⏳</div>
      <div style={{ fontSize:18, fontWeight:700, color:"var(--text)", marginBottom:10 }}>
        {t('claim.pending.title')}
      </div>
      <p style={{ color:"var(--muted)", fontSize:14, maxWidth:400, margin:"0 auto 24px" }}>
        {t('claim.pending.sub')}
      </p>
      {selected?.name && (
        <div style={{ background:"rgba(255,255,255,.04)", border:"1px solid var(--line)", borderRadius:12, padding:"16px 20px", display:"inline-block", marginBottom:24 }}>
          <div style={{ fontWeight:600, color:"var(--text)" }}>{selected.name}</div>
          {selected.city && <div style={{ fontSize:13, color:"var(--muted)", marginTop:4 }}>📍 {selected.city}</div>}
        </div>
      )}
      <div>
        <button className="btn btn--glass" style={{ fontSize:13, color:"var(--text)" }} onClick={() => onDone(selected)}>
          {t('btn.back')}
        </button>
      </div>
    </div>
  );

  if (step === "code") return (
    <div style={{ maxWidth:400, margin:"0 auto", padding:"24px 0" }}>
      <div style={{ marginBottom:20 }}>
        <div style={{ fontWeight:700, fontSize:17, color:"var(--text)", marginBottom:6 }}>
          {t('claim.code.title')}
        </div>
        <p style={{ color:"var(--muted)", fontSize:14, margin:0 }}>
          {t('claim.code.sub').replace('{email}', emailHint)}
        </p>
      </div>
      {err && <div className="toast toast--error" style={{ marginBottom:12, pointerEvents:"auto" }}><span>{err}</span></div>}
      <div className="field">
        <label className="field__label">{t('claim.code.label')}</label>
        <input className="input" placeholder="123456" maxLength={6}
          value={code} onChange={e => setCode(e.target.value.replace(/\D/g,""))}
          onKeyDown={e => e.key === "Enter" && verifyClaim()}
          style={{ letterSpacing:8, fontSize:20, textAlign:"center" }}
        />
      </div>
      <div style={{ display:"flex", gap:8 }}>
        <button className="btn btn--gold" style={{ flex:1 }} onClick={verifyClaim} disabled={loading || code.length < 6}>
          {loading ? "…" : t('claim.code.verify')}
        </button>
        <button className="btn btn--glass" onClick={cancelClaim}>{t('btn.cancel')}</button>
      </div>
    </div>
  );

  // step === "search"
  return (
    <div style={{ maxWidth:560, margin:"0 auto", padding:"24px 0" }}>
      <div style={{ marginBottom:20 }}>
        <div style={{ fontWeight:700, fontSize:17, color:"var(--text)", marginBottom:6 }}>
          {t('claim.search.title')}
        </div>
        <p style={{ color:"var(--muted)", fontSize:14, margin:0 }}>
          {t('claim.search.sub')}
        </p>
      </div>
      {err && <div className="toast toast--error" style={{ marginBottom:12, pointerEvents:"auto" }}><span>{err}</span></div>}
      <div style={{ display:"flex", gap:8, marginBottom:16 }}>
        <input className="input" style={{ flex:1 }}
          placeholder={t('claim.search.placeholder')}
          value={q} onChange={e => setQ(e.target.value)}
          onKeyDown={e => e.key === "Enter" && search()}
        />
        <button className="btn btn--gold" onClick={search} disabled={loading}>
          {loading ? "…" : t('claim.search.btn')}
        </button>
      </div>
      {results.length > 0 && (
        <div style={{ display:"flex", flexDirection:"column", gap:8 }}>
          {results.map(d => (
            <div key={d.id} style={{ display:"flex", alignItems:"center", justifyContent:"space-between",
              padding:"14px 16px", borderRadius:12, border:"1px solid var(--line)",
              background:"rgba(255,255,255,.03)" }}>
              <div>
                <div style={{ fontWeight:600, color:"var(--text)", marginBottom:3 }}>{d.name}</div>
                <div style={{ fontSize:12, color:"var(--muted)" }}>📍 {d.city}{d.address ? ` — ${d.address}` : ""}</div>
                {!d.has_email && <div style={{ fontSize:11, color:"var(--faint)", marginTop:4 }}>{t('claim.no_email_hint')}</div>}
              </div>
              <button className="btn btn--gold" style={{ fontSize:12, padding:"6px 14px", whiteSpace:"nowrap", marginLeft:12 }}
                onClick={() => requestClaim(d)} disabled={loading}>
                {t('claim.search.claim_btn')}
              </button>
            </div>
          ))}
        </div>
      )}
      {results.length === 0 && !loading && !err && (
        <p style={{ color:"var(--muted)", fontSize:13 }}>
          {q ? t('claim.search.no_results') : t('claim.search.empty')}
        </p>
      )}
      <div style={{ marginTop:24, paddingTop:20, borderTop:"1px solid var(--line)" }}>
        <p style={{ fontSize:13, color:"var(--muted)", margin:"0 0 12px" }}>{t('claim.create_instead')}</p>
        <button className="btn btn--glass" style={{ fontSize:13 }} onClick={() => onCancel ? onCancel() : go("dashboard")}>
          {t('claim.create_btn')}
        </button>
      </div>
    </div>
  );
}


function DashboardPage({ go, user, addToast, onLogout }) {
  const { t } = useTranslation();
  useMeta({ title: t('nav.dashboard') || "Tableau de bord", noindex: true });
  const [dealer,   setDealer]   = useState(null);
  const [listings, setListings] = useState([]);
  const [loading,  setLoading]  = useState(true);
  const [tab,      setTab]      = useState(null); // null = Tous
  const [nav,      setNav]      = useState("stock");
  const [needsOnboard, setNeedsOnboard] = useState(false);
  const { askConfirm, confirmModal } = useConfirm();

  const [claimStatus, setClaimStatus] = useState(null); // {has_pending, dealer_name, dealer_slug}
  const [showClaimFlow, setShowClaimFlow] = useState(false);

  useEffect(() => {
    if (!user) { setLoading(false); return; }
    if (user.role !== "dealer") {
      // Vérifier si une demande est en cours
      api.get("/dealers/me/claim/status").then(s => setClaimStatus(s)).catch(() => {});
      setLoading(false);
      return;
    }
    Promise.all([
      api.get("/dealers/me").catch(e => {
        if (e.message.includes("404") || e.message.includes("introuvable")) return null;
        throw e;
      }),
      api.get("/dealers/me/listings").catch(() => []),
    ])
      .then(([d, l]) => {
        if (!d) { setNeedsOnboard(true); setNav("profil"); }
        else { setDealer(d); }
        setListings(l.items || l || []);
      })
      .catch(e => addToast(e.message, "error"))
      .finally(() => setLoading(false));
  }, [user]);

  const stock = useMemo(() =>
    tab ? listings.filter(l => l.status === tab) : listings,
    [listings, tab]);

  const stats = useMemo(() => ({
    active:  listings.filter(l => l.status === "available").length,
    views:   listings.reduce((s, l) => s + (l.views_count    || 0), 0),
    clicks:  listings.reduce((s, l) => s + (l.contact_clicks || 0), 0),
    sold:    listings.filter(l => l.status === "sold").length,
  }), [listings]);

  const changeStatus = async (id, status) => {
    try {
      await api.patch("/dealers/me/listings/" + id + "/status", { status });
      setListings(p => p.map(l => l.id === id ? { ...l, status } : l));
      addToast(t('dash.status_updated'), "success");
    } catch (e) { addToast(e.message, "error"); }
  };

  const deleteListing = async id => {
    const w = listings.find(l => l.id === id);
    const name = w ? `${w.brand} ${w.model}` : null;
    const msg = name
      ? t('dash.listing.confirm_delete_named').replace('{name}', name)
      : t('dash.listing.confirm_delete');
    const ok = await askConfirm(msg);
    if (!ok) return;
    try {
      await api.delete("/dealers/me/listings/" + id);
      setListings(p => p.filter(l => l.id !== id));
      addToast(t('toast.listing_deleted'), "info");
    } catch (e) { addToast(e.message, "error"); }
  };

  if (!user) return (
    <div className="app app--desktop">
      <PageHeader backTo="search" go={go} user={user} />
      <div className="page">
        <div className="empty">
          <Icon name="shield" size={38} />
          <div className="empty__title">{t('dash.dealer_only')}</div>
          <button className="btn btn--gold" onClick={() => go("login")}>{t('nav.login')}</button>
        </div>
      </div>
    </div>
  );

  // Acheteur qui crée une nouvelle fiche — formulaire d'onboarding accessible
  if (user.role !== "dealer" && needsOnboard) return (
    <div className="app app--desktop">
      <PageHeader backTo="search" go={go} user={user} />
      <div className="page">
        <ProfileTab
          dealer={null}
          needsOnboard={true}
          addToast={addToast}
          onSaved={data => {
            setNeedsOnboard(false);
            setClaimStatus({ has_pending: true, type: "new_profile",
              dealer_name: data.name, dealer_city: data.city });
          }}
          onDealerUpdate={() => {}}
        />
      </div>
    </div>
  );

  if (user.role !== "dealer") return (
    <div className="app app--desktop">
      <PageHeader backTo="search" go={go} user={user} />
      <div className="page">
        {showClaimFlow ? (
          <ClaimDealerFlow go={go} user={user} addToast={addToast}
            onCancel={() => setShowClaimFlow(false)}
            onDone={(dealer) => { setShowClaimFlow(false); setClaimStatus({ has_pending: true, type: "claim", dealer_name: dealer?.name, dealer_city: dealer?.city }); }} />
        ) : claimStatus?.has_pending ? (
          <div style={{ maxWidth:480, margin:"60px auto", textAlign:"center" }}>
            <div style={{ fontSize:40, marginBottom:16 }}>⏳</div>
            <div style={{ fontSize:18, fontWeight:700, color:"var(--text)", marginBottom:10 }}>
              {t('claim.pending.title')}
            </div>
            <p style={{ color:"var(--muted)", fontSize:14, marginBottom:20 }}>{t('claim.pending.sub')}</p>
            {claimStatus.dealer_name && (
              <div style={{ background:"rgba(255,255,255,.04)", border:"1px solid var(--line)", borderRadius:12, padding:"16px 20px", display:"inline-block", marginBottom:24 }}>
                <div style={{ fontWeight:600, color:"var(--text)" }}>{claimStatus.dealer_name}</div>
                {claimStatus.dealer_city && <div style={{ fontSize:13, color:"var(--muted)", marginTop:4 }}>📍 {claimStatus.dealer_city}</div>}
              </div>
            )}
            <div>
              <button className="btn btn--glass" style={{ fontSize:13 }} onClick={() => go("search")}>
                {t('btn.back')}
              </button>
            </div>
          </div>
        ) : (
          <div style={{ maxWidth:480, margin:"60px auto", textAlign:"center" }}>
            <div style={{ fontSize:40, marginBottom:16 }}>🏪</div>
            <div style={{ fontSize:20, fontWeight:700, color:"var(--text)", marginBottom:10 }}>
              {t('claim.dealer_cta.title')}
            </div>
            <p style={{ color:"var(--muted)", fontSize:14, marginBottom:28 }}>
              {t('claim.dealer_cta.sub')}
            </p>
            <div style={{ display:"flex", gap:12, justifyContent:"center", flexWrap:"wrap" }}>
              <button className="btn btn--gold" onClick={() => setShowClaimFlow(true)}>
                {t('claim.dealer_cta.claim_btn')}
              </button>
              <button className="btn btn--glass" onClick={() => setNeedsOnboard(true)}>
                {t('claim.dealer_cta.create_btn')}
              </button>
            </div>
          </div>
        )}
      </div>
    </div>
  );

  const d = dealer || {};
  const initials = d.name ? d.name.split(" ").map(w => w[0]).join("").slice(0,2).toUpperCase() : "DE";

  return (
    <div className="app app--desktop">
      <header className="dash__header">
        <button className="topbar__brand" onClick={() => go("search")} style={{ background:"none" }}>
          <EagleMark size={57} />
          <span className="wordmark">WATCH<span className="wordmark__gold">EAGLE</span></span>
        </button>
        <span className="dash__crumb">{t('nav.dashboard')}</span>
        <div className="dash__profile">
          <span className="dash__shop">{d.name}</span>
          <span className="dash__avatar">{initials}</span>
        </div>
      </header>

      <div className="dash__body">
        <aside className="dash__nav">
          {[
            { id:"stock",  labelKey:"dash.stock",   icon:"tag" },
            { id:"profil", labelKey:"dash.profile", icon:"settings" },
            { id:"abo",    labelKey:"dash.subscription", icon:"card" },
          ].map(n => (
            <button key={n.id} className={"dash__navitem " + (nav === n.id ? "on" : "")}
              onClick={() => setNav(n.id)}>
              <Icon name={n.icon} size={18} /><span>{t(n.labelKey)}</span>
            </button>
          ))}
          <button className="dash__navitem dash__navitem--out" onClick={onLogout}>
            <Icon name="logout" size={18} /><span>{t('dash.logout')}</span>
          </button>
        </aside>

        <main className="dash__main">
          {loading ? <div className="empty"><div className="spinner" /></div> : (
            <>
              {/* ── PROFIL TAB ── */}
              {nav === "profil" && (
                <ProfileTab
                  dealer={dealer}
                  needsOnboard={needsOnboard}
                  addToast={addToast}
                  onSaved={data => { setDealer(data); setNeedsOnboard(false); setNav("stock"); }}
                  onDealerUpdate={data => setDealer(data)}
                />
              )}

              {/* ── STOCK TAB ── */}
              {nav === "stock" && (<>

                {/* Checklist onboarding profil */}
                {!needsOnboard && dealer && (() => {
                  const checks = [
                    { labelKey:"dash.onboard.logo",    done:!!dealer.logo_url,                       tab:"profil" },
                    { labelKey:"dash.onboard.description", done:!!(dealer.description?.trim()),       tab:"profil" },
                    { labelKey:"dash.onboard.address", done:!!(dealer.address && dealer.city),        tab:"profil" },
                    { labelKey:"dash.onboard.hours",   done:!!(dealer.opening_hours?.trim()),         tab:"profil" },
                    { labelKey:"dash.onboard.first",   done:listings.length > 0,                     tab:"new" },
                  ];
                  const doneCount = checks.filter(c => c.done).length;
                  if (doneCount === checks.length) return null;
                  const pct = Math.round(doneCount / checks.length * 100);
                  return (
                    <Glass style={{ padding:18, marginBottom:22,
                      borderLeft:"3px solid var(--gold)", background:"rgba(201,168,76,0.04)" }}>
                      <div style={{ display:"flex", alignItems:"center", justifyContent:"space-between", marginBottom:10 }}>
                        <div>
                          <div style={{ fontWeight:600, fontSize:14, marginBottom:2 }}>{t('dash.onboard.title')}</div>
                          <div style={{ fontSize:12, color:"var(--muted)" }}>{t('dash.onboard.sub', { done: doneCount, total: checks.length })}</div>
                        </div>
                        <div style={{ fontSize:22, fontWeight:800, color:"var(--gold)" }}>{pct}%</div>
                      </div>
                      {/* Barre de progression */}
                      <div style={{ height:4, background:"rgba(255,255,255,0.08)", borderRadius:2, marginBottom:12 }}>
                        <div style={{ height:"100%", width:pct+"%", background:"var(--gold)",
                          borderRadius:2, transition:"width .4s" }} />
                      </div>
                      <div style={{ display:"flex", flexWrap:"wrap", gap:8 }}>
                        {checks.map(c => (
                          <button key={c.labelKey} type="button"
                            className={"chip " + (c.done ? "chip--on" : "")}
                            style={{ cursor: !c.done && c.tab ? "pointer" : "default",
                                     opacity: c.done ? 0.55 : 1 }}
                            onClick={() => { if (c.done || !c.tab) return; c.tab === "new" ? go("new") : setNav(c.tab); }}>
                            {c.done
                              ? <Icon name="check" size={11} style={{ marginRight:4 }} />
                              : <span style={{ marginRight:4, opacity:.5 }}>○</span>}
                            {t(c.labelKey)}
                          </button>
                        ))}
                      </div>
                    </Glass>
                  );
                })()}

                <div className="dash__titlerow">
                  <div>
                    <h1 className="page__h1">{t('dash.my_stock')}</h1>
                    <p className="page__lead">{t(listings.length === 1 ? 'dash.pieces_one' : 'dash.pieces', { n: listings.length })} · {d.name}</p>
                  </div>
                  <button className="btn btn--gold" onClick={() => needsOnboard ? (setNav("profil"), addToast(t('toast.create_shop_first'), "info")) : go("new")}>
                    <Icon name="plus" size={16} /> {t('dash.add_listing')}
                  </button>
                </div>

                {/* Stats */}
                <div className="statgrid">
                  {[
                    { labelKey:"dash.stats.active", value: dealer?.max_listings ? `${stats.active}/${dealer.max_listings}` : stats.active, icon:"tag", gold:true },
                    { labelKey:"dash.stats.views",  value:stats.views,  icon:"eye",   gold:false },
                    { labelKey:"dash.stats.clicks", value:stats.clicks, icon:"phone", gold:false },
                    { labelKey:"dash.stats.sold",   value:stats.sold,   icon:"check", gold:false },
                  ].map(s => (
                    <Glass key={s.labelKey} className="statcard">
                      <div className="statcard__top">
                        <span className="statcard__label">{t(s.labelKey)}</span>
                        <span className={"statcard__icon " + (s.gold ? "statcard__icon--gold" : "")}>
                          <Icon name={s.icon} size={16} />
                        </span>
                      </div>
                      <div className="statcard__value">{s.value}</div>
                    </Glass>
                  ))}
                </div>

                {/* Tabs */}
                <div className="dash__tabs">
                  {DASH_STATUS_TAB_VALUES.map(v => {
                    const labelKey = v === null ? 'dash.filter.all'
                      : v === 'available' ? 'dash.filter.available'
                      : v === 'reserved'  ? 'dash.filter.reserved'
                      : v === 'sold'      ? 'dash.filter.sold'
                      : 'dash.filter.archived';
                    return (
                      <button key={v ?? 'all'} className={"dash__tab " + (tab === v ? "on" : "")}
                        onClick={() => setTab(v)}>
                        {t(labelKey)}
                      </button>
                    );
                  })}
                </div>

                {/* Desktop table */}
                <div className="stocktable">
                  <div className="stocktable__head">
                    <span>{t('dash.col.watch')}</span><span>{t('dash.col.price')}</span><span>{t('dash.col.cond')}</span>
                    <span>{t('dash.col.status')}</span><span className="ta-c">{t('dash.col.views')}</span><span className="ta-r">{t('dash.col.actions')}</span>
                  </div>
                  {stock.map(w => (
                    <div key={w.id} className="stockrow">
                      <div className="stockrow__watch">
                        <div className="stockrow__thumb">
                          {firstPhoto(w)
                            ? <img src={firstPhoto(w)} alt="" style={{ position:"absolute",inset:0,width:"100%",height:"100%",objectFit:"cover" }} />
                            : <DialPlaceholder hue={brandHue(w.brand)} brand={w.brand} />}
                        </div>
                        <div>
                          <div className="stockrow__name">{w.brand} {w.model}</div>
                          <div className="stockrow__ref">{t('card.ref')} {w.reference}</div>
                        </div>
                      </div>
                      <div className="stockrow__price">{eur(w.price)}</div>
                      <div className="stockrow__cond">{t('cond.' + condApiKey(w.condition))}</div>
                      <div>
                        <span className="statusdot" style={{ "--c": statusColor(w.status) }}>
                          {t('status.' + w.status)}
                        </span>
                      </div>
                      <div className="ta-c stockrow__views">{w.views_count || 0}</div>
                      <div className="stockrow__actions">
                        <button className="iconbtn iconbtn--sm" title={t('dash.listing.preview')} onClick={() => go("watch", w.id)}>
                          <Icon name="eye" size={15} />
                        </button>
                        <button className="iconbtn iconbtn--sm" title="Modifier" onClick={() => go("new", w.id)}>
                          <Icon name="edit" size={15} />
                        </button>
                        <div className="selectwrap">
                          <select className="select" style={{ height:34, fontSize:12, padding:"0 26px 0 8px" }}
                            value={w.status} onChange={e => changeStatus(w.id, e.target.value)}>
                            <option value="available">{t('status.available')}</option>
                            <option value="reserved">{t('status.reserved')}</option>
                            <option value="sold">{t('status.sold')}</option>
                            <option value="archived">{t('status.archived')}</option>
                          </select>
                          <Icon name="chevron" size={11} className="selectwrap__chev" />
                        </div>
                        <button className="iconbtn iconbtn--sm iconbtn--danger" title="Supprimer"
                          onClick={() => deleteListing(w.id)}>
                          <Icon name="trash" size={15} />
                        </button>
                      </div>
                    </div>
                  ))}
                  {stock.length === 0 && (
                    <div style={{ padding:40, textAlign:"center", color:"var(--muted)" }}>
                      {needsOnboard
                        ? <span>{t('dash.create_shop_first')}<button className="link" onClick={() => setNav("profil")}>{t('dash.shop_profile')}</button>.</span>
                        : t('dash.no_listings_cat')}
                    </div>
                  )}
                </div>

                {/* Mobile cards */}
                <div className="stockcards">
                  {stock.map(w => (
                    <Glass key={w.id} className="stockcard">
                      <div className="stockcard__top">
                        <div className="stockcard__thumb">
                          {firstPhoto(w)
                            ? <img src={firstPhoto(w)} alt="" style={{ position:"absolute",inset:0,width:"100%",height:"100%",objectFit:"cover" }} />
                            : <DialPlaceholder hue={brandHue(w.brand)} brand={w.brand} />}
                        </div>
                        <div className="stockcard__body">
                          <div className="stockrow__name">{w.brand} {w.model}</div>
                          <div className="stockrow__ref">{w.reference && <>{t('card.ref')} {w.reference} · </>}{t((w.views_count||0)===1 ? 'card.views_one' : 'card.views', {n: w.views_count||0})}</div>
                          <div className="stockcard__foot">
                            <span className="stockrow__price">{eur(w.price)}</span>
                            <span className="statusdot" style={{ "--c": statusColor(w.status) }}>
                              {t('status.' + w.status)}
                            </span>
                          </div>
                        </div>
                      </div>
                      <div className="stockcard__actions">
                        <div className="selectwrap" style={{ flex:1 }}>
                          <select className="select" style={{ height:34, fontSize:12, padding:"0 26px 0 8px", width:"100%" }}
                            value={w.status} onChange={e => changeStatus(w.id, e.target.value)}>
                            <option value="available">{t('status.available')}</option>
                            <option value="reserved">{t('status.reserved')}</option>
                            <option value="sold">{t('status.sold')}</option>
                            <option value="archived">{t('status.archived')}</option>
                          </select>
                          <Icon name="chevron" size={11} className="selectwrap__chev" />
                        </div>
                        <button className="iconbtn iconbtn--sm" title={t('dash.listing.preview')} onClick={() => go("watch", w.id)}>
                          <Icon name="eye" size={15} />
                        </button>
                        <button className="iconbtn iconbtn--sm" onClick={() => go("new", w.id)}>
                          <Icon name="edit" size={15} />
                        </button>
                        <button className="iconbtn iconbtn--sm iconbtn--danger" title={t('dash.listing.delete')}
                          onClick={() => deleteListing(w.id)}>
                          <Icon name="trash" size={15} />
                        </button>
                      </div>
                    </Glass>
                  ))}
                </div>
              </>)}

              {/* ── ABO TAB ── */}
              {nav === "abo" && (
                <div className="empty" style={{ minHeight:320 }}>
                  <Icon name="card" size={44} style={{ color:"var(--gold)", opacity:.7 }} />
                  <div className="empty__title">{t('sub.coming_soon')}</div>
                  <div className="empty__sub" style={{ maxWidth:360 }}>{t('sub.coming_soon_sub')}</div>
                  <a href="mailto:info@watch-eagle.com" className="btn btn--glass" style={{ marginTop:8 }}>
                    {t('sub.notify')}
                  </a>
                </div>
              )}
            </>
          )}
        </main>
      </div>
      {confirmModal}
    </div>
  );
}

/* ============================================================
   NEW / EDIT LISTING
   ============================================================ */
function NewListingPage({ go, user, editId, addToast }) {
  const { t } = useTranslation();
  const blank = {
    brand:"", model:"", reference:"", year:"",
    condition:"very_good", has_box:true, has_papers:true,
    recently_serviced:false, price:"", currency:"EUR",
    price_negotiable:false, appointment_only:false, watermark_photos:true, description:"",
  };
  const [form,     setForm]     = useState(blank);
  const [photos,   setPhotos]   = useState([]);
  const [uploading,setUploading]= useState(false);
  const [saving,   setSaving]   = useState(false);
  const [loading,  setLoading]  = useState(!!editId);
  const [dragOver, setDragOver] = useState(false);
  const fileRef = useRef(null);
  const set = (k, v) => setForm(p => ({ ...p, [k]: v }));

  // Load existing listing when editing
  useEffect(() => {
    if (!editId) return;
    api.get("/search/watches/" + editId)
      .then(d => {
        setForm({
          brand: d.brand||"", model: d.model||"", reference: d.reference||"",
          year: d.year || "", condition: condApiKey(d.condition||"very_good"),
          has_box: !!d.has_box, has_papers: !!d.has_papers,
          recently_serviced: !!d.recently_serviced, price: d.price||"",
          currency: d.currency||"EUR", price_negotiable: !!d.price_negotiable,
          appointment_only: !!d.appointment_only,
          watermark_photos: d.watermark_photos !== false,
          description: d.description||"",
        });
        setPhotos(allPhotos(d));
      })
      .catch(e => addToast(e.message, "error"))
      .finally(() => setLoading(false));
  }, [editId]);

  const handleFiles = async files => {
    if (!files || !files.length) return;
    setUploading(true);
    for (const file of Array.from(files)) {
      if (photos.length >= 8) break;
      // Préview locale immédiate (blob URL) — indépendante du serveur
      const blobUrl = URL.createObjectURL(file);
      setPhotos(p => [...p, blobUrl]);
      try {
        const fd = new FormData();
        fd.append("file", file);
        const wm = form.watermark_photos !== false ? "true" : "false";
        const r = await api.upload(`/upload/image?folder=listings&watermark=${wm}`, fd);
        const absUrl = toAbsoluteImgUrl(r.url); // URL absolue stable
        // Remplacer le blob URL par l'URL serveur une fois l'upload terminé
        setPhotos(p => p.map(u => u === blobUrl ? absUrl : u));
      } catch (e) {
        setPhotos(p => p.filter(u => u !== blobUrl)); // retirer si upload échoue
        addToast(e.message, "error");
      }
    }
    setUploading(false);
  };

  const publish = async () => {
    if (!form.brand || !form.model || !form.price) {
      addToast(t('listing.required'), "error"); return;
    }
    // Vérifier qu'aucun upload n'est encore en cours
    if (photos.some(u => u.startsWith("blob:"))) {
      addToast(t('listing.upload_pending'), "info"); return;
    }
    setSaving(true);
    try {
      const body = { ...form, year: +form.year, price: +form.price, photos_json: JSON.stringify(photos) };
      if (editId) {
        await api.patch("/dealers/me/listings/" + editId, body);
        addToast(t('listing.updated'), "success");
      } else {
        await api.post("/dealers/me/listings", body);
        addToast(t('listing.saved'), "success");
      }
      go("dashboard");
    } catch (e) { addToast(e.message, "error"); }
    finally { setSaving(false); }
  };

  if (loading) return (
    <div className="app app--desktop">
      <header className="topbar topbar--inner">
        <button className="backbtn" onClick={() => go("dashboard")}>
          <Icon name="arrowL" size={18} /><span>{t('nav.dashboard')}</span>
        </button>
      </header>
      <div className="page"><div className="empty"><div className="spinner" /></div></div>
    </div>
  );

  return (
    <div className="app app--desktop">
      <header className="topbar topbar--inner">
        <button className="backbtn" onClick={() => go("dashboard")}>
          <Icon name="arrowL" size={18} /><span>{t('nav.dashboard')}</span>
        </button>
        <span className="topbar__pagetitle topbar__pagetitle--center">
          {editId ? t('listing.edit_title') : t('listing.new')}
        </span>
        <div className="topbar__actions" />
      </header>

      <div className="page page--form">
        <div className="formgrid">
          {/* Left col – photos */}
          <div className="formcol">
            <div className="field__label">
              {t('listing.photos')} <span className="field__hint">{t('listing.photos_hint')}</span>
            </div>
            <div
              className={"dropzone" + (dragOver ? " dropzone--over" : "")}
              onClick={() => fileRef.current?.click()}
              onDragEnter={e => { e.preventDefault(); setDragOver(true); }}
              onDragOver={e => { e.preventDefault(); setDragOver(true); }}
              onDragLeave={e => { e.preventDefault(); setDragOver(false); }}
              onDrop={e => {
                e.preventDefault();
                setDragOver(false);
                if (e.dataTransfer.files?.length) handleFiles(e.dataTransfer.files);
              }}
            >
              <Icon name="upload" size={57} />
              <div className="dropzone__t">{t('listing.upload_hint')}</div>
              <div className="dropzone__s">{t('listing.upload_sub')}</div>
              <input ref={fileRef} type="file" accept="image/*" multiple
                style={{ display:"none" }} onChange={e => handleFiles(e.target.files)} />
            </div>
            {uploading && <div style={{ color:"var(--gold)", fontSize:13 }}>{t('listing.uploading')}</div>}
            <div className="slotgrid">
              {Array.from({ length: 8 }).map((_, i) => (
                <div key={i} className={"slot " + (i === 0 ? "slot--main" : "")}>
                  {photos[i] ? (
                    <>
                      <img src={resolveImgUrl(photos[i])} alt=""
                        style={{ position:"absolute",inset:0,width:"100%",height:"100%",objectFit:"cover",borderRadius:12 }} />
                      <button style={{ position:"absolute",top:4,right:4,width:20,height:20,borderRadius:"50%",
                          background:"rgba(0,0,0,.65)",display:"grid",placeItems:"center",color:"#fff",zIndex:2 }}
                        onClick={e => { e.stopPropagation(); setPhotos(p => p.filter((_,j) => j !== i)); }}>
                        <Icon name="close" size={11} sw={2.5} />
                      </button>
                    </>
                  ) : (
                    <>
                      <Icon name="image" size={18} />
                      {i === 0 && <span className="slot__tag">{t('listing.photo_main')}</span>}
                    </>
                  )}
                </div>
              ))}
            </div>
          </div>

          {/* Right col – form fields */}
          <div className="formcol">
            <div className="field">
              <label className="field__label">{t('listing.brand')}</label>
              <div className="selectwrap selectwrap--full">
                <select className="select select--full" value={form.brand}
                  onChange={e => set("brand", e.target.value)}>
                  <option value="">{t('listing.select_brand')}</option>
                  {BRANDS_LIST.map(b => <option key={b}>{b}</option>)}
                </select>
                <Icon name="chevron" size={15} className="selectwrap__chev" />
              </div>
            </div>

            <div className="field__row">
              <div className="field">
                <label className="field__label">{t('listing.model')}</label>
                <input className="input" placeholder="Daytona"
                  value={form.model} onChange={e => set("model", e.target.value)} />
              </div>
              <div className="field">
                <label className="field__label">{t('listing.reference')}</label>
                <input className="input" placeholder="116500LN"
                  value={form.reference} onChange={e => set("reference", e.target.value)} />
              </div>
            </div>

            <div className="field__row">
              <div className="field">
                <label className="field__label">{t('listing.year')}</label>
                <input className="input" type="number" min="1900" max={new Date().getFullYear()}
                  placeholder="2023" value={form.year} onChange={e => set("year", e.target.value)} />
              </div>
              <div className="field">
                <label className="field__label">{t('listing.price')}</label>
                <div className="priceinput">
                  <input className="input" type="number" placeholder="0"
                    value={form.price} onChange={e => set("price", e.target.value)} />
                  <div className="selectwrap">
                    <select className="select" value={form.currency}
                      onChange={e => set("currency", e.target.value)}>
                      <option>EUR</option><option>CHF</option><option>GBP</option>
                    </select>
                    <Icon name="chevron" size={13} className="selectwrap__chev" />
                  </div>
                </div>
              </div>
            </div>

            <div className="field">
              <label className="field__label">{t('listing.condition')}</label>
              <div className="chipwrap">
                {CONDITIONS.map(c => (
                  <button key={c} type="button"
                    className={"chip " + (form.condition === c ? "chip--on" : "")}
                    onClick={() => set("condition", c)}>{t('cond.' + c)}</button>
                ))}
              </div>
            </div>

            <div className="togglegrid">
              {[
                { k:"has_box",          labelKey:"listing.has_box" },
                { k:"has_papers",       labelKey:"listing.has_papers" },
                { k:"recently_serviced",labelKey:"listing.recently_serviced" },
                { k:"price_negotiable", labelKey:"listing.price_negotiable" },
                { k:"appointment_only",  labelKey:"listing.appointment_only" },
                { k:"watermark_photos", labelKey:"listing.watermark_photos", hint: editId ? t('listing.watermark_edit_hint') : null },
              ].map(({ k, labelKey, hint }) => (
                <button key={k} type="button" className="ftoggle"
                  onClick={() => set(k, !form[k])}>
                  <span style={{ flex:1 }}>
                    {t(labelKey)}
                    {hint && <span style={{ display:"block", fontSize:11, color:"var(--gold)", marginTop:2, fontWeight:400 }}>⚠ {hint}</span>}
                  </span>
                  <span className={"switch " + (form[k] ? "switch--on" : "")}>
                    <span className="switch__dot" />
                  </span>
                </button>
              ))}
            </div>

            <div className="field">
              <label className="field__label">{t('listing.description')}</label>
              <textarea className="input textarea" rows={4}
                placeholder="Spécificités, historique, état détaillé…"
                value={form.description} onChange={e => set("description", e.target.value)} />
            </div>

            <button className="btn btn--gold btn--lg" onClick={publish} disabled={saving}>
              {saving ? t('listing.saving') : (editId ? t('listing.update') : t('listing.save'))}
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

/* ============================================================
   BOTTOM NAV (mobile)
   ============================================================ */
function BottomNav({ page, go, favCount, user }) {
  const { t } = useTranslation();
  const accountTab = user ? "account" : "login";
  const TABS = MARKETPLACE_ENABLED
    ? [
        { id:"search",    labelKey:"nav.tab.search",    icon:"search" },
        { id:"alerts",    labelKey:"nav.tab.alerts",    icon:"bell"   },
        { id:"favorites", labelKey:"nav.tab.favorites", icon:"heart"  },
        { id:accountTab,  labelKey:"nav.tab.account",   icon:"user"   },
      ]
    : [
        { id:"dealers",   labelKey:"dealers.nav",       icon:"pin"    },
        { id:"alerts",    labelKey:"nav.tab.alerts",    icon:"bell"   },
        { id:accountTab,  labelKey:"nav.tab.account",   icon:"user"   },
      ];
  const active = id => page === id
    || (id === "search"  && page === "watch")
    || (id === "dealers" && ["dealers","search"].includes(page))
    || (id === accountTab && (page === "account" || page === "login" || page === "register"));
  return (
    <nav className="bottomnav">
      {TABS.map(tab => (
        <button key={tab.id} className={"bottomnav__tab " + (active(tab.id) ? "on" : "")}
          onClick={() => go(tab.id)}>
          <span className="bottomnav__icon">
            <Icon name={tab.icon} size={21}
              fill={active(tab.id) && tab.id === "favorites" ? "var(--gold)" : "none"} />
            {tab.id === "favorites" && favCount > 0 &&
              <span className="bottomnav__badge">{favCount}</span>}
          </span>
          <span className="bottomnav__lbl">{t(tab.labelKey)}</span>
        </button>
      ))}
    </nav>
  );
}

/* ============================================================
   RESET PASSWORD PAGE
   ============================================================ */
function ResetPasswordPage({ go, addToast }) {
  const { t } = useTranslation();
  const token = new URLSearchParams(window.location.search).get("token");
  const [pw,      setPw]      = useState("");
  const [showPw,  setShowPw]  = useState(false);
  const [loading, setLoading] = useState(false);
  const [done,    setDone]    = useState(false);

  const submit = async () => {
    if (!token) { addToast(t('reset.invalid'), "error"); return; }
    setLoading(true);
    try {
      await api.post("/auth/reset-password", { token, new_password: pw });
      setDone(true);
      addToast(t('reset.success'), "success");
      setTimeout(() => go("login"), 2000);
    } catch (e) { addToast(e.message, "error"); }
    finally { setLoading(false); }
  };

  return (
    <div className="authstage">
      <div className="authstage__aura" />
      <Glass className="authcard">
        <div className="authcard__brand"><EagleMark size={57} /></div>
        <h1 className="authcard__title">{t('reset.title')}</h1>
        <p className="authcard__sub">{t('reset.sub')}</p>
        {done ? (
          <div className="toast toast--success" style={{ pointerEvents:"auto" }}>
            <Icon name="check" size={14} /><span>{t('reset.redirect')}</span>
          </div>
        ) : (
          <>
            <div className="field authcard .field">
              <label className="field__label">{t('account.new_pw')}</label>
              <div className="pwfield">
                <input className="input" type={showPw ? "text" : "password"} placeholder="••••••••"
                  value={pw} onChange={e => setPw(e.target.value)} />
                <button type="button" className="pwfield__eye" onClick={() => setShowPw(v => !v)}>
                  <Icon name={showPw ? "eyeOff" : "eye"} size={18} />
                </button>
              </div>
            </div>
            <button className="btn btn--gold btn--lg" onClick={submit} disabled={loading}>
              {loading ? t('reset.loading') : t('reset.btn')}
            </button>
          </>
        )}
      </Glass>
    </div>
  );
}

/* ============================================================
   DEALER PUBLIC PAGE
   ============================================================ */
function DealerPage({ go, user, favIds, onFav, addToast, slug }) {
  const { t } = useTranslation();
  const [dealer,    setDealer]   = useState(null);
  const [listings,  setListings] = useState([]);
  const [loading,   setLoading]  = useState(true);
  const [showClaim, setShowClaim] = useState(false);
  useMeta({
    title: dealer ? dealer.name : null,
    description: dealer
      ? (dealer.description || `${dealer.name}${dealer.city ? ' — ' + dealer.city : ''} · ${t('dealer.listings', {n: listings.length})}`)
      : null,
    image: dealer?.logo_url ? resolveImgUrl(dealer.logo_url) : undefined,
  });

  useEffect(() => {
    if (!dealer) return;
    const jsonld = {
      "@context": "https://schema.org",
      "@type": "LocalBusiness",
      "name": dealer.name,
      "description": dealer.description || `${dealer.name} – Revendeur de montres de seconde main certifié`,
      "url": window.location.href,
      ...(dealer.phone    && { "telephone": dealer.phone }),
      ...(dealer.email    && { "email": dealer.email }),
      ...(dealer.logo_url && { "image": toAbsoluteImgUrl(dealer.logo_url) }),
      ...(dealer.city && {
        "address": {
          "@type": "PostalAddress",
          "addressLocality": dealer.city,
          ...(dealer.postal_code && { "postalCode": dealer.postal_code }),
          "addressCountry": dealer.country || "FR",
        },
      }),
      ...(dealer.lat && dealer.lng && {
        "geo": { "@type": "GeoCoordinates", "latitude": dealer.lat, "longitude": dealer.lng },
      }),
      ...(dealer.gmaps_rating && {
        "aggregateRating": {
          "@type": "AggregateRating",
          "ratingValue": dealer.gmaps_rating,
          "reviewCount": dealer.gmaps_reviews || 1,
        },
      }),
    };
    let el = document.getElementById("we-jsonld");
    if (!el) { el = document.createElement("script"); el.type = "application/ld+json"; el.id = "we-jsonld"; document.head.appendChild(el); }
    el.textContent = JSON.stringify(jsonld);
    return () => { const s = document.getElementById("we-jsonld"); if (s) s.remove(); };
  }, [dealer]);

  useEffect(() => {
    if (!slug) { go("search"); return; }
    Promise.all([
      api.get("/dealers/" + slug),
      api.get("/dealers/" + slug + "/listings"),
    ])
      .then(([d, l]) => { setDealer(d); setListings(Array.isArray(l) ? l : []); })
      .catch(e => { addToast(e.message, "error"); go("search"); })
      .finally(() => setLoading(false));
  }, [slug]);

  if (loading) return (
    <div className="app app--desktop">
      <PageHeader backTo="search" go={go} user={user} />
      <div className="page"><div className="empty"><div className="spinner" /></div></div>
    </div>
  );

  if (!dealer) return null;

  const d = dealer;
  const initials = d.name ? d.name.split(" ").map(w => w[0]).join("").slice(0,2).toUpperCase() : "??";

  return (
    <div className="app app--desktop">
      <PageHeader backTo="search" go={go} user={user} />
      <div className="page">
        {/* Hero boutique */}
        <Glass className="dealerhero">
          <div className="dealerhero__logo">
            {d.logo_url
              ? <img src={resolveImgUrl(d.logo_url)} alt={d.name}
                  style={{ width:"100%", height:"100%", objectFit:"cover", borderRadius:"50%" }} />
              : <span className="dealerhero__initials">{initials}</span>}
          </div>
          <div className="dealerhero__info">
            <div className="dealerhero__name">
              {d.name}
              {d.is_verified && <span className="vbadge" title={t('dealer.verified_tooltip')}><Icon name="shield" size={13} sw={1.8} /></span>}
              {d.is_claimed === false && (
                <span style={{ fontSize:11, padding:"2px 7px", borderRadius:4, background:"rgba(156,163,175,.12)", color:"#9ca3af", fontWeight:600, marginLeft:4 }}>
                  Non revendiqué
                </span>
              )}
            </div>
            {d.description && <p className="dealerhero__desc">{d.description}</p>}
            {d.categories && d.categories.length > 0 && (
              <div style={{ display:"flex", flexWrap:"wrap", gap:5, margin:"6px 0 2px" }}>
                {d.categories.map(c => (
                  <span key={c.id} style={{ fontSize:11, padding:"2px 9px", borderRadius:20,
                    background:"rgba(201,168,76,.1)", color:"var(--gold)",
                    border:"1px solid rgba(201,168,76,.25)", fontWeight:500 }}>
                    {c.label_fr}
                  </span>
                ))}
              </div>
            )}
            <div className="dealerhero__meta">
              {(d.address || d.city) && (
                <span>
                  <Icon name="pin" size={13} /> {[d.address, d.city, d.country].filter(Boolean).join(", ")}
                  {d.address_masked && (
                    <span className="pill pill--rdv" style={{ marginLeft:6, fontSize:10, verticalAlign:"middle" }}>
                      {t('detail.addr_on_rdv')}
                    </span>
                  )}
                </span>
              )}
              {d.specialties && <span><Icon name="tag" size={13} /> {d.specialties}</span>}
              {d.gmaps_rating && <span><Stars value={d.gmaps_rating} /> {d.gmaps_rating} ({d.gmaps_reviews} {t('dealer.gmaps_reviews')})</span>}
              {d.phone && <span><Icon name="phone" size={13} /> <a href={"tel:" + d.phone} style={{ color:"inherit" }}>{d.phone}</a></span>}
              {d.website_url && (
                <span>
                  <Icon name="link" size={13} />
                  <a href={d.website_url} target="_blank" rel="noopener noreferrer" style={{ color:"var(--gold)" }}>
                    Site web
                  </a>
                </span>
              )}
            </div>
            <div className="dealerhero__cta">
              {d.phone && (
                <a href={"tel:" + d.phone} className="btn btn--gold">
                  <Icon name="phone" size={15} /> {t('dealer.contact')}
                </a>
              )}
              {d.website_url && (
                <a href={d.website_url} target="_blank" rel="noopener noreferrer" className="btn btn--glass">
                  <Icon name="link" size={15} /> Site web
                </a>
              )}
              {d.email && (
                <a href={"mailto:" + d.email} className="btn btn--glass">
                  <Icon name="mail" size={15} /> {t('dealer.email')}
                </a>
              )}
              {(d.lat && d.lng ? true : (d.address || d.city)) && !d.address_masked && (
                <a href={
                  d.lat && d.lng
                    ? `https://www.google.com/maps/dir/?api=1&destination=${d.lat},${d.lng}`
                    : `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent([d.address, d.city, d.country].filter(Boolean).join(", "))}`
                } target="_blank" rel="noopener noreferrer" className="btn btn--glass">
                  <Icon name="pin" size={15} /> {t('dealer.directions')}
                </a>
              )}
            </div>
          </div>
        </Glass>

        {/* CTA revendication */}
        {!d.is_claimed && !showClaim && (
          <div style={{ margin:"16px 0", padding:"14px 20px", borderRadius:12,
                        background:"rgba(255,255,255,.03)", border:"1px solid var(--line)",
                        display:"flex", alignItems:"center", justifyContent:"space-between",
                        gap:12, flexWrap:"wrap" }}>
            <div style={{ fontSize:13, color:"var(--muted)" }}>
              <span style={{ marginRight:6 }}>🏪</span>
              C'est votre boutique ? Revendiquez-la gratuitement et gérez votre profil.
            </div>
            <button className="btn btn--gold" style={{ fontSize:13, whiteSpace:"nowrap" }}
              onClick={() => user ? setShowClaim(true) : go("login")}>
              Revendiquer cette fiche
            </button>
          </div>
        )}

        {/* Flow de revendication inline */}
        {showClaim && (
          <Glass style={{ padding:"24px", margin:"16px 0" }}>
            <div style={{ display:"flex", justifyContent:"space-between", alignItems:"center", marginBottom:16 }}>
              <div style={{ fontWeight:700, fontSize:16, color:"var(--gold)" }}>
                Revendiquer {d.name}
              </div>
              <button onClick={() => setShowClaim(false)}
                style={{ background:"none", border:"none", color:"var(--muted)", cursor:"pointer", fontSize:18 }}>✕</button>
            </div>
            <ClaimDealerFlow
              go={go} user={user} addToast={addToast}
              initialDealer={{ id: d.id, slug: d.slug, name: d.name, city: d.city, has_email: d.email != null }}
              onDone={() => { setShowClaim(false); setDealer(prev => ({ ...prev, is_claimed: true })); }}
              onCancel={() => setShowClaim(false)}
            />
          </Glass>
        )}

        {/* Annonces */}
        <h2 className="page__h2" style={{ margin:"28px 0 16px" }}>
          {t(listings.length === 1 ? 'dealer.listings_one' : 'dealer.listings', { n: listings.length })}
        </h2>
        {listings.length === 0 ? (
          <div className="empty" style={{ minHeight:200 }}>
            <Icon name="tag" size={32} />
            <div className="empty__title">{t('dealer.empty')}</div>
          </div>
        ) : (
          <div className="grid">
            {listings.map(w => (
              <WatchCard key={w.id}
                w={{ ...w, dealer: { name: d.name, slug: d.slug, city: d.city, is_verified: d.is_verified } }}
                fav={favIds.includes(w.id)} onFav={onFav}
                onOpen={w => go("watch", w.id)} />
            ))}
          </div>
        )}
      </div>
    </div>
  );
}

/* ============================================================
   ACCOUNT PAGE
   ============================================================ */
function AccountPage({ go, user, addToast, onLogout }) {
  const { t } = useTranslation();
  useMeta({ title: t('nav.account') || "Mon compte", noindex: true });
  const [tab,            setTab]           = useState("infos");
  const [profile,        setProfile]        = useState(null);
  const [pwForm,         setPwForm]         = useState({ current:"", next:"", show:false });
  const [notifEmail,     setNotifEmail]     = useState(true);
  const [saving,         setSaving]         = useState(false);
  const [tgLinking,      setTgLinking]      = useState(false);
  const [tgLink,         setTgLink]         = useState(null); // { link, expires_in_seconds }
  const { askConfirm, confirmModal } = useConfirm();

  useEffect(() => {
    api.get("/users/me")
      .then(d => { setProfile(d); setNotifEmail(d.notif_email ?? true); })
      .catch(e => addToast(e.message, "error"));
  }, []);

  const connectTelegram = async () => {
    setTgLinking(true);
    try {
      const data = await api.post("/users/me/telegram-link", {});
      setTgLink(data);
      window.open(data.link, "_blank");
    } catch(e) { addToast(e.message, "error"); }
    finally { setTgLinking(false); }
  };

  const disconnectTelegram = async () => {
    try {
      await api.delete("/users/me/telegram-disconnect");
      setProfile(p => ({ ...p, telegram_chat_id: null, notification_channel: "email" }));
      setTgLink(null);
      addToast(t('account.tg_disconnected'), "info");
    } catch(e) { addToast(e.message, "error"); }
  };

  const checkTgLinked = async () => {
    const d = await api.get("/users/me");
    setProfile(d);
    if (d.telegram_chat_id) addToast(t('account.tg_linked_ok'), "success");
  };

  const savePassword = async () => {
    if (pwForm.next.length < 8) { addToast(t('account.pw_min'), "error"); return; }
    setSaving(true);
    try {
      await api.post("/users/me/change-password", { current_password: pwForm.current, new_password: pwForm.next });
      addToast(t('account.pw_saved'), "success");
      setPwForm({ current:"", next:"", show:false });
    } catch(e) { addToast(e.message, "error"); }
    finally { setSaving(false); }
  };

  const saveNotifs = async () => {
    setSaving(true);
    try {
      await api.patch("/users/me", { notif_email: notifEmail });
      addToast(t('account.prefs_saved'), "success");
    } catch(e) { addToast(e.message, "error"); }
    finally { setSaving(false); }
  };

  const deleteAccount = async () => {
    const ok = await askConfirm(t('account.delete_confirm'));
    if (!ok) return;
    try {
      await api.delete("/users/me");
      onLogout();
      addToast(t('account.deleted'), "info");
    } catch(e) { addToast(e.message, "error"); }
  };

  return (
    <div className="app app--desktop">
      <PageHeader backTo="search" go={go} user={user} />
      <div className="page">
        <h1 className="page__h1">{t('account.title')}</h1>

        {/* Tabs */}
        <div style={{ display:"flex", gap:8, marginBottom:28 }}>
          {[["infos",'account.tab_infos'], ["password",'account.tab_password'], ["notifs",'account.tab_notifs']].map(([id,key]) => (
            <button key={id}
              className={tab === id ? "btn btn--gold" : "btn btn--glass"}
              style={{ fontSize:13, padding:"8px 16px", whiteSpace:"nowrap" }}
              onClick={() => setTab(id)}>{t(key)}</button>
          ))}
        </div>

        {/* ── INFOS ── */}
        {tab === "infos" && (
          <Glass style={{ maxWidth:480, padding:28 }}>
            <div style={{ marginBottom:20 }}>
              <div style={{ fontSize:12, color:"var(--muted)", marginBottom:4 }}>{t('account.email_label')}</div>
              <div style={{ fontWeight:600, fontSize:16 }}>{user?.email || profile?.email}</div>
            </div>
            <div style={{ marginBottom:20 }}>
              <div style={{ fontSize:12, color:"var(--muted)", marginBottom:4 }}>{t('account.role_label')}</div>
              <div style={{ fontWeight:500, color:"var(--gold)", textTransform:"capitalize" }}>
                {t(user?.role === "dealer" ? 'account.role_dealer' : 'account.role_buyer')}
              </div>
            </div>
            {user?.role === "dealer" && (
              <button className="btn btn--glass" style={{ marginBottom:16, width:"100%" }}
                onClick={() => go("dashboard")}>
                {t('account.dealer_space')}
              </button>
            )}
            <button className="btn btn--glass" style={{ width:"100%" }}
              onClick={() => { onLogout(); go("search"); }}>
              {t('account.disconnect')}
            </button>
            <hr style={{ border:"none", borderTop:"1px solid rgba(255,255,255,0.06)", margin:"20px 0" }} />
            <button
              onClick={deleteAccount}
              style={{ background:"none", border:"none", color:"#ef4444", fontSize:13,
                       cursor:"pointer", padding:0, opacity:0.7 }}>
              {t('account.delete_account')}
            </button>
          </Glass>
        )}

        {/* ── MOT DE PASSE ── */}
        {tab === "password" && (
          <Glass style={{ maxWidth:480, padding:28 }}>
            <h2 style={{ margin:"0 0 20px", fontSize:17 }}>{t('account.pw_title')}</h2>
            <div style={{ marginBottom:14 }}>
              <label className="form__label">{t('account.current_pw')}</label>
              <div style={{ position:"relative" }}>
                <input className="input" type={pwForm.show ? "text" : "password"}
                  value={pwForm.current}
                  onChange={e => setPwForm(p => ({...p, current:e.target.value}))}
                  placeholder="••••••••" />
              </div>
            </div>
            <div style={{ marginBottom:20 }}>
              <label className="form__label">{t('account.new_pw')}</label>
              <input className="input" type={pwForm.show ? "text" : "password"}
                value={pwForm.next}
                onChange={e => setPwForm(p => ({...p, next:e.target.value}))}
                placeholder="8 caractères min." />
              <label style={{ display:"flex", alignItems:"center", gap:8, marginTop:8,
                              fontSize:13, color:"var(--muted)", cursor:"pointer" }}>
                <input type="checkbox" checked={pwForm.show}
                  onChange={e => setPwForm(p => ({...p, show:e.target.checked}))} />
                {t('account.pw_show')}
              </label>
            </div>
            <button className="btn btn--gold" style={{ width:"100%" }}
              disabled={saving || !pwForm.current || !pwForm.next}
              onClick={savePassword}>
              {saving ? t('account.pw_saving') : t('account.pw_update')}
            </button>
          </Glass>
        )}

        {/* ── NOTIFICATIONS ── */}
        {tab === "notifs" && (
          <Glass style={{ maxWidth:480, padding:28 }}>
            <h2 style={{ margin:"0 0 20px", fontSize:17 }}>{t('account.notifs_title')}</h2>

            {/* Email toggle */}
            <label style={{ display:"flex", alignItems:"center", justifyContent:"space-between",
                            padding:"14px 0", borderBottom:"1px solid rgba(255,255,255,0.06)",
                            cursor:"pointer" }}>
              <div>
                <div style={{ fontWeight:500, marginBottom:3 }}>{t('account.notif_email')}</div>
                <div style={{ fontSize:13, color:"var(--muted)" }}>{t('account.notifs_email_sub')}</div>
              </div>
              <div onClick={() => setNotifEmail(p => !p)}
                style={{
                  width:44, height:24, borderRadius:12, flexShrink:0, marginLeft:16,
                  background: notifEmail ? "var(--gold)" : "rgba(255,255,255,0.1)",
                  position:"relative", cursor:"pointer", transition:"background .2s"
                }}>
                <div style={{
                  position:"absolute", top:3, left: notifEmail ? 23 : 3,
                  width:18, height:18, borderRadius:9, background:"#fff",
                  transition:"left .2s", boxShadow:"0 1px 4px rgba(0,0,0,.3)"
                }} />
              </div>
            </label>

            {/* Telegram */}
            <div style={{ padding:"18px 0", borderBottom:"1px solid rgba(255,255,255,0.06)" }}>
              <div style={{ fontWeight:500, marginBottom:4 }}>{t('account.tg_title')}</div>
              <div style={{ fontSize:13, color:"var(--muted)", marginBottom:14 }}>{t('account.tg_sub')}</div>

              {profile?.telegram_chat_id ? (
                <div style={{ display:"flex", alignItems:"center", gap:10, flexWrap:"wrap" }}>
                  <span style={{ fontSize:13, color:"var(--gold)", fontWeight:600 }}>
                    ✓ {t('account.tg_connected')}
                  </span>
                  <button className="btn btn--glass" style={{ fontSize:12, padding:"5px 12px" }}
                    onClick={disconnectTelegram}>{t('account.tg_disconnect')}</button>
                </div>
              ) : (
                <div style={{ display:"flex", flexDirection:"column", gap:10 }}>
                  <button className="btn btn--glass" style={{ width:"100%", gap:8 }}
                    disabled={tgLinking} onClick={connectTelegram}>
                    <svg width="18" height="18" viewBox="0 0 24 24" fill="none" style={{ flexShrink:0 }}>
                      <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm4.64 6.8l-1.7 8.02c-.13.57-.47.71-.94.44l-2.6-1.92-1.25 1.21c-.14.14-.26.26-.53.26l.19-2.68 4.87-4.4c.21-.19-.05-.29-.33-.1L7.9 14.47 5.33 13.7c-.56-.17-.57-.56.12-.83l9.03-3.48c.47-.17.88.11.16.91z" fill="#29B6F6"/>
                    </svg>
                    {tgLinking ? t('account.tg_generating') : t('account.tg_connect')}
                  </button>
                  {tgLink && (
                    <div style={{ fontSize:12, color:"var(--muted)", lineHeight:1.5 }}>
                      {t('account.tg_instructions')}
                      <button className="btn btn--glass" style={{ fontSize:12, padding:"5px 12px", marginTop:8, width:"100%" }}
                        onClick={checkTgLinked}>{t('account.tg_check')}</button>
                    </div>
                  )}
                </div>
              )}
            </div>

            <button className="btn btn--gold" style={{ width:"100%", marginTop:24 }}
              disabled={saving} onClick={saveNotifs}>
              {saving ? t('account.pw_saving') : t('account.save_prefs')}
            </button>
          </Glass>
        )}
      </div>
      {confirmModal}
    </div>
  );
}

/* ── Admin : gestion des catégories ──────────────────────────────────────── */
function CategoriesTab({ categories, aFetch, reload, addToast }) {
  const [form, setForm] = React.useState({ key: "", label_fr: "", label_en: "" });
  const [saving, setSaving] = React.useState(false);

  const toKey = (s) => s.toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g,"").replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"");

  const createCat = async () => {
    if (!form.key || !form.label_fr || !form.label_en) return;
    setSaving(true);
    try {
      await aFetch("POST", "/admin/categories", form);
      setForm({ key: "", label_fr: "", label_en: "" });
      reload();
      addToast("Catégorie créée", "success");
    } catch (e) { addToast(e.message, "error"); }
    finally { setSaving(false); }
  };

  const deleteCat = async (id, label) => {
    if (!window.confirm(`Supprimer la catégorie "${label}" ? Elle sera retirée de tous les dealers.`)) return;
    try {
      await aFetch("DELETE", `/admin/categories/${id}`);
      reload();
      addToast("Catégorie supprimée", "info");
    } catch (e) { addToast(e.message, "error"); }
  };

  return (
    <div>
      <p style={{ fontSize:13, color:"var(--muted)", marginBottom:20 }}>
        Les catégories permettent de caractériser les dealers (vintage, luxe, etc.). Elles s'assignent depuis la fiche de chaque dealer.
      </p>

      {/* Formulaire ajout */}
      <Glass style={{ padding:16, marginBottom:24, maxWidth:560 }}>
        <div style={{ fontWeight:600, fontSize:14, marginBottom:12, color:"var(--gold)" }}>Nouvelle catégorie</div>
        <div style={{ display:"grid", gridTemplateColumns:"1fr 1fr 1fr", gap:10, marginBottom:12 }}>
          {[["Clé (slug)", "key"], ["Libellé FR", "label_fr"], ["Libellé EN", "label_en"]].map(([lbl, field]) => (
            <div key={field}>
              <label style={{ fontSize:11, color:"var(--muted)", display:"block", marginBottom:3 }}>{lbl}</label>
              <input className="input" style={{ fontSize:13, padding:"6px 9px" }}
                value={form[field]}
                onChange={e => setForm(p => ({
                  ...p,
                  [field]: e.target.value,
                  ...(field === "label_fr" && !p.key ? { key: toKey(e.target.value) } : {}),
                }))}
                placeholder={field === "key" ? "ex: vintage" : ""}
              />
            </div>
          ))}
        </div>
        <button className="btn btn--gold" style={{ fontSize:13 }} onClick={createCat} disabled={saving || !form.key || !form.label_fr || !form.label_en}>
          + Ajouter
        </button>
      </Glass>

      {/* Liste */}
      <div style={{ display:"flex", flexDirection:"column", gap:8 }}>
        {categories.length === 0 ? (
          <div className="empty" style={{ minHeight:100 }}>Aucune catégorie</div>
        ) : categories.map(cat => (
          <div key={cat.id} style={{ display:"flex", alignItems:"center", gap:12, padding:"10px 14px",
            background:"rgba(255,255,255,.03)", border:"1px solid var(--line)", borderRadius:8 }}>
            <span style={{ fontSize:11, color:"var(--muted)", minWidth:28 }}>#{cat.id}</span>
            <span style={{ fontSize:12, fontFamily:"monospace", color:"var(--gold)", minWidth:120 }}>{cat.key}</span>
            <span style={{ flex:1, fontSize:13 }}>{cat.label_fr}</span>
            <span style={{ fontSize:13, color:"var(--muted)", minWidth:140 }}>{cat.label_en}</span>
            <button className="btn btn--glass" style={{ fontSize:11, padding:"3px 8px", color:"#ef4444" }}
              onClick={() => deleteCat(cat.id, cat.label_fr)}>🗑</button>
          </div>
        ))}
      </div>
    </div>
  );
}

/* ============================================================
   ADMIN PAGE
   ============================================================ */
function AdminPage({ go, addToast }) {
  const [key,     setKey]     = useState(() => sessionStorage.getItem("we_admin_key") || "");
  const [keyInput,setKeyInput]= useState("");
  const [tab,          setTab]         = useState("stats");   // stats | listings | dealers | rgpd
  const [stats,        setStats]       = useState(null);
  const [listings,     setListings]    = useState([]);
  const [dealers,      setDealers]     = useState([]);
  const [users,        setUsers]       = useState([]);
  const [claims,       setClaims]      = useState([]);
  const [lFilter,      setLFilter]     = useState("all");     // listing status filter
  const [loading,      setLoading]     = useState(false);
  const [categories,   setCategories]  = useState([]);
  const [showSeedForm, setShowSeedForm]= useState(false);
  const [seedForm,     setSeedForm]    = useState({
    name: "", slug: "", city: "", country: "BE",
    address: "", phone: "", website_url: "", priority_weight: 0,
  });
  const { askConfirm, confirmModal } = useConfirm();

  // admin fetch helper — sends X-Admin-Key header
  const aFetch = useCallback(async (method, path, body) => {
    const origin = API_BASE.replace(/\/api\/v1\/?$/, "");
    const url = origin + "/api/v1" + path;
    const opts = {
      method,
      headers: { "X-Admin-Key": key, "Content-Type": "application/json" },
    };
    if (body) opts.body = JSON.stringify(body);
    const r = await fetch(url, opts);
    if (r.status === 204) return null;
    const d = await r.json().catch(() => ({}));
    if (!r.ok) throw new Error(d.detail || "Erreur " + r.status);
    return d;
  }, [key]);

  const loadStats = useCallback(async () => {
    try { setStats(await aFetch("GET", "/admin/stats")); }
    catch (e) { addToast(e.message, "error"); }
  }, [aFetch]);

  const loadListings = useCallback(async () => {
    setLoading(true);
    try { setListings(await aFetch("GET", "/admin/listings?limit=200")); }
    catch (e) { addToast(e.message, "error"); }
    finally { setLoading(false); }
  }, [aFetch]);

  const loadDealers = useCallback(async () => {
    setLoading(true);
    try { setDealers(await aFetch("GET", "/admin/dealers?limit=200")); }
    catch (e) { addToast(e.message, "error"); }
    finally { setLoading(false); }
  }, [aFetch]);

  const loadUsers = useCallback(async () => {
    setLoading(true);
    try { setUsers(await aFetch("GET", "/admin/users?limit=200")); }
    catch (e) { addToast(e.message, "error"); }
    finally { setLoading(false); }
  }, [aFetch]);

  const loadClaims = useCallback(async () => {
    setLoading(true);
    try { setClaims(await aFetch("GET", "/admin/dealers/claims")); }
    catch (e) { addToast(e.message, "error"); }
    finally { setLoading(false); }
  // eslint-disable-next-line
  }, [aFetch]);

  const loadCategories = useCallback(async () => {
    try { setCategories(await aFetch("GET", "/admin/categories")); }
    catch (e) { addToast(e.message, "error"); }
  }, [aFetch]);

  const approveClaim = async (id, name) => {
    if (!await askConfirm(`Approuver la revendication de "${name}" ?`)) return;
    try {
      await aFetch("POST", `/admin/dealers/${id}/claim/approve`);
      addToast(`${name} — revendication approuvée`, "success");
      loadClaims();
    } catch (e) { addToast(e.message, "error"); }
  };

  const rejectClaim = async (id, name) => {
    if (!await askConfirm(`Rejeter la revendication de "${name}" ?`)) return;
    try {
      await aFetch("POST", `/admin/dealers/${id}/claim/reject`);
      addToast(`${name} — revendication rejetée`, "info");
      loadClaims();
    } catch (e) { addToast(e.message, "error"); }
  };

  const activateDealer = async (id, name) => {
    if (!await askConfirm(`Activer la fiche "${name}" ?`)) return;
    try {
      await aFetch("POST", `/admin/dealers/${id}/activate`);
      addToast(`${name} — fiche activée`, "success");
      loadClaims();
    } catch (e) { addToast(e.message, "error"); }
  };

  const rejectNewDealer = async (id, name) => {
    if (!await askConfirm(`Rejeter et supprimer la fiche "${name}" ?`)) return;
    try {
      await aFetch("POST", `/admin/dealers/${id}/reject-new`);
      addToast(`${name} — fiche rejetée`, "info");
      loadClaims();
    } catch (e) { addToast(e.message, "error"); }
  };

  // Load data when key is set and tab changes
  useEffect(() => {
    if (!key) return;
    if (tab === "stats")          loadStats();
    if (tab === "listings")       loadListings();
    if (tab === "dealers")        { loadDealers(); loadCategories(); }
    if (tab === "rgpd")           loadUsers();
    if (tab === "revendications") loadClaims();
    if (tab === "categories")     loadCategories();
  }, [key, tab]);

  const handleLogin = () => {
    if (!keyInput.trim()) return;
    sessionStorage.setItem("we_admin_key", keyInput.trim());
    setKey(keyInput.trim());
  };

  const setListingStatus = async (id, status) => {
    try {
      await aFetch("PATCH", `/admin/listings/${id}/status?status=${status}`);
      setListings(p => p.map(l => l.id === id ? { ...l, status } : l));
      addToast("Statut mis à jour", "success");
    } catch (e) { addToast(e.message, "error"); }
  };

  const deleteListing = async (id) => {
    const ok = await askConfirm("Supprimer cette annonce définitivement ?");
    if (!ok) return;
    try {
      await aFetch("DELETE", `/admin/listings/${id}`);
      setListings(p => p.filter(l => l.id !== id));
      addToast("Annonce supprimée", "success");
    } catch (e) { addToast(e.message, "error"); }
  };

  const verifyDealer = async (id) => {
    try {
      await aFetch("PATCH", `/admin/dealers/${id}/verify`);
      setDealers(p => p.map(d => d.id === id ? { ...d, is_verified: true } : d));
      addToast("Dealer vérifié ✓", "success");
    } catch (e) { addToast(e.message, "error"); }
  };

  const toggleDealer = async (id, current) => {
    try {
      const r = await aFetch("PATCH", `/admin/dealers/${id}/toggle`);
      setDealers(p => p.map(d => d.id === id ? { ...d, is_active: r.is_active } : d));
      addToast(r.is_active ? "Dealer activé" : "Dealer suspendu", "info");
    } catch (e) { addToast(e.message, "error"); }
  };

  const setDealerPlan = async (id, plan) => {
    const maxMap = { free: 5, starter: 15, pro: 50, premium: 999 };
    try {
      await aFetch("PATCH", `/admin/dealers/${id}/plan?plan=${plan}&max_listings=${maxMap[plan]}`);
      setDealers(p => p.map(d => d.id === id ? { ...d, plan } : d));
      addToast(`Plan ${plan} appliqué`, "success");
    } catch (e) { addToast(e.message, "error"); }
  };

  const seedDealer = async () => {
    if (!seedForm.name.trim() || !seedForm.slug.trim() || !seedForm.city.trim()) {
      addToast("Nom, slug et ville sont obligatoires", "error"); return;
    }
    try {
      const r = await aFetch("POST", "/admin/dealers/seed", seedForm);
      addToast(`Dealer "${r.name}" créé (#${r.id})`, "success");
      setSeedForm({ name:"", slug:"", city:"", country:"BE", address:"", phone:"", website_url:"", priority_weight:0 });
      setShowSeedForm(false);
      loadDealers();
    } catch (e) { addToast(e.message, "error"); }
  };

  const [editDealer, setEditDealer] = useState(null); // dealer en cours d'édition
  const [editForm, setEditForm]     = useState({});

  const openEdit = (d) => {
    setEditDealer(d.id);
    setEditForm({
      name: d.name || "", slug: d.slug || "", city: d.city || "",
      country: d.country || "", address: d.address || "", phone: d.phone || "",
      email: d.email || "", website_url: d.website_url || "",
      description: d.description || "", lat: d.lat ?? "", lng: d.lng ?? "",
      category_ids: d.category_ids || [],
    });
  };

  const saveDealer = async (id) => {
    try {
      const r = await aFetch("PATCH", `/admin/dealers/${id}/edit`, editForm);
      setDealers(p => p.map(d => d.id === id ? { ...d, ...r } : d));
      setEditDealer(null);
      addToast("Fiche mise à jour", "success");
    } catch (e) { addToast(e.message, "error"); }
  };

  const deleteDealer = async (id, name) => {
    const ok = await askConfirm(`Supprimer le dealer "${name}" ?`);
    if (!ok) return;
    try {
      await aFetch("DELETE", `/admin/dealers/${id}`);
      setDealers(p => p.filter(d => d.id !== id));
      addToast("Dealer supprimé", "success");
    } catch (e) { addToast(e.message, "error"); }
  };

  const setPriority = async (id, weight) => {
    try {
      await aFetch("PATCH", `/admin/dealers/${id}/priority?weight=${weight}`);
      setDealers(p => p.map(d => d.id === id ? { ...d, priority_weight: weight } : d));
    } catch (e) { addToast(e.message, "error"); }
  };

  const deleteUser = async (id, email) => {
    const ok = await askConfirm(`Anonymiser le compte "${email}" (RGPD) ? Cette action est irréversible.`);
    if (!ok) return;
    try {
      await aFetch("DELETE", `/admin/users/${id}`);
      setUsers(p => p.map(u => u.id === id ? { ...u, email: `deleted_${id}@deleted`, is_active: false } : u));
      addToast("Compte anonymisé", "success");
    } catch (e) { addToast(e.message, "error"); }
  };

  // Génère un slug depuis un nom (utilisé dans le formulaire seed)
  const toSlug = (name) =>
    name.toLowerCase()
      .normalize("NFD").replace(/[̀-ͯ]/g, "")
      .replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");

  // ── Login screen ────────────────────────────────────────────────────────────
  if (!key) return (
    <div className="app app--desktop">
      <div className="page" style={{ display:"flex", alignItems:"center", justifyContent:"center", minHeight:"80vh" }}>
        <Glass style={{ maxWidth:380, width:"100%", padding:40, textAlign:"center" }}>
          <div style={{ fontSize:32, marginBottom:12 }}>🔐</div>
          <div style={{ fontWeight:700, fontSize:20, marginBottom:8, color:"var(--gold)" }}>Panel Admin</div>
          <p style={{ color:"var(--muted)", marginBottom:24, fontSize:14 }}>Accès réservé aux administrateurs WatchEagle</p>
          <input
            className="input"
            type="password"
            placeholder="Clé admin"
            value={keyInput}
            onChange={e => setKeyInput(e.target.value)}
            onKeyDown={e => e.key === "Enter" && handleLogin()}
            style={{ marginBottom:12 }}
          />
          <button className="btn btn--gold" style={{ width:"100%" }} onClick={handleLogin}>
            Accéder
          </button>
          <button className="btn btn--glass" style={{ width:"100%", marginTop:8 }} onClick={() => go("search")}>
            Retour
          </button>
        </Glass>
      </div>
    </div>
  );

  // ── Status badge ─────────────────────────────────────────────────────────────
  const StatusBadge = ({ s }) => {
    const colors = { available:"#22c55e", archived:"#ef4444", reserved:"#f59e0b", sold:"#6366f1" };
    return (
      <span style={{
        background: (colors[s] || "#888") + "22",
        color: colors[s] || "#888",
        padding:"2px 8px", borderRadius:6, fontSize:12, fontWeight:600
      }}>{s}</span>
    );
  };

  const filteredListings = lFilter === "all" ? listings : listings.filter(l => l.status === lFilter);

  // ── Main panel ──────────────────────────────────────────────────────────────
  return (
    <div className="app app--desktop">
      <PageHeader backTo="search" go={go} user={null}>
        <div style={{ fontWeight:700, color:"var(--gold)", fontSize:16 }}>🔐 Panel Admin</div>
      </PageHeader>

      <div className="page">
        {/* Tabs */}
        <div style={{ display:"flex", gap:8, marginBottom:24 }}>
          {[["stats","Stats"], ["listings","Annonces"], ["dealers","Dealers"], ["categories","Catégories"], ["revendications","Revendications" + (claims.length ? ` (${claims.length})` : "")], ["rgpd","RGPD"]].map(([id,label]) => (
            <button key={id}
              className={tab === id ? "btn btn--gold" : "btn btn--glass"}
              style={{ fontSize:13, padding:"8px 16px" }}
              onClick={() => setTab(id)}
            >{label}</button>
          ))}
          <button className="btn btn--glass" style={{ marginLeft:"auto", fontSize:13 }}
            onClick={() => { sessionStorage.removeItem("we_admin_key"); setKey(""); }}>
            Déconnexion
          </button>
        </div>

        {/* ── STATS ── */}
        {tab === "stats" && (
          <div>
            {!stats ? (
              <div className="empty"><div className="spinner" /></div>
            ) : (
              <>
                <div style={{ display:"grid", gridTemplateColumns:"repeat(auto-fill,minmax(180px,1fr))", gap:16, marginBottom:32 }}>
                  {[
                    ["👤 Utilisateurs",   stats.users_total,          "#c9a84c"],
                    ["🏪 Dealers actifs", stats.dealers_active,       "#22c55e"],
                    ["✅ Dealers inscrits",stats.dealers_claimed,      "#3b82f6"],
                    ["📋 Annonces dispo", stats.listings_available,   "#a855f7"],
                    ["🔔 Alertes actives",stats.alerts_active,        "#f59e0b"],
                  ].map(([label, val, color]) => (
                    <Glass key={label} style={{ textAlign:"center", padding:24 }}>
                      <div style={{ fontSize:32, fontWeight:800, color }}>{val ?? "—"}</div>
                      <div style={{ fontSize:12, color:"var(--muted)", marginTop:6 }}>{label}</div>
                    </Glass>
                  ))}
                </div>
                <div style={{ textAlign:"right", fontSize:12, color:"var(--muted)" }}>
                  Mis à jour : {new Date(stats.generated_at).toLocaleTimeString("fr-FR")}
                  <button className="btn btn--glass" style={{ marginLeft:12, fontSize:12, padding:"4px 10px" }} onClick={loadStats}>
                    ↻ Refresh
                  </button>
                </div>
              </>
            )}
          </div>
        )}

        {/* ── LISTINGS ── */}
        {tab === "listings" && (
          <div>
            <div style={{ display:"flex", gap:8, marginBottom:16, flexWrap:"wrap", alignItems:"center" }}>
              <span style={{ color:"var(--muted)", fontSize:13 }}>Filtrer :</span>
              {["all","available","archived","reserved","sold"].map(s => (
                <button key={s}
                  className={lFilter === s ? "btn btn--gold" : "btn btn--glass"}
                  style={{ fontSize:12, padding:"4px 10px" }}
                  onClick={() => setLFilter(s)}
                >{s === "all" ? "Tout" : s}</button>
              ))}
              <span style={{ marginLeft:"auto", fontSize:13, color:"var(--muted)" }}>
                {filteredListings.length} annonce{filteredListings.length !== 1 ? "s" : ""}
              </span>
            </div>
            {loading ? <div className="empty"><div className="spinner" /></div> : (
              <div style={{ overflowX:"auto" }}>
                <table style={{ width:"100%", borderCollapse:"collapse", fontSize:13 }}>
                  <thead>
                    <tr style={{ color:"var(--muted)", borderBottom:"1px solid rgba(255,255,255,0.06)" }}>
                      {["ID","Marque / Modèle","Prix","Dealer","Statut","Vues","Actions"].map(h => (
                        <th key={h} style={{ padding:"8px 10px", textAlign:"left", fontWeight:500 }}>{h}</th>
                      ))}
                    </tr>
                  </thead>
                  <tbody>
                    {filteredListings.map(l => (
                      <tr key={l.id} style={{ borderBottom:"1px solid rgba(255,255,255,0.04)" }}>
                        <td style={{ padding:"8px 10px", color:"var(--muted)" }}>#{l.id}</td>
                        <td style={{ padding:"8px 10px", fontWeight:500 }}>
                          {l.brand} {l.model}
                        </td>
                        <td style={{ padding:"8px 10px", color:"var(--gold)" }}>
                          {l.price ? l.price.toLocaleString("fr-FR") + " " + l.currency : "—"}
                        </td>
                        <td style={{ padding:"8px 10px", color:"var(--muted)", fontSize:12 }}>
                          {l.dealer_name}<br />{l.dealer_city}
                        </td>
                        <td style={{ padding:"8px 10px" }}><StatusBadge s={l.status} /></td>
                        <td style={{ padding:"8px 10px", color:"var(--muted)" }}>{l.views_count}</td>
                        <td style={{ padding:"8px 10px" }}>
                          <div style={{ display:"flex", gap:4, flexWrap:"wrap" }}>
                            {l.status !== "available" && (
                              <button className="btn btn--glass"
                                style={{ fontSize:11, padding:"3px 8px", color:"#22c55e" }}
                                onClick={() => setListingStatus(l.id, "available")}
                                title="Valider">✓ Valider</button>
                            )}
                            {l.status !== "archived" && (
                              <button className="btn btn--glass"
                                style={{ fontSize:11, padding:"3px 8px", color:"#ef4444" }}
                                onClick={() => setListingStatus(l.id, "archived")}
                                title="Suspendre">⊘ Suspendre</button>
                            )}
                            <button className="btn btn--glass"
                              style={{ fontSize:11, padding:"3px 8px", color:"#ef4444" }}
                              onClick={() => deleteListing(l.id)}
                              title="Supprimer">🗑</button>
                          </div>
                        </td>
                      </tr>
                    ))}
                  </tbody>
                </table>
                {filteredListings.length === 0 && (
                  <div className="empty" style={{ minHeight:120 }}>Aucune annonce</div>
                )}
              </div>
            )}
          </div>
        )}

        {/* ── DEALERS ── */}
        {tab === "dealers" && (
          <div>
            {/* Barre supérieure */}
            <div style={{ display:"flex", alignItems:"center", justifyContent:"space-between", marginBottom:16 }}>
              <div style={{ fontSize:13, color:"var(--muted)" }}>
                {dealers.length} dealer{dealers.length !== 1 ? "s" : ""} au total
              </div>
              <div style={{ display:"flex", gap:8 }}>
                <button className="btn btn--glass" style={{ fontSize:13, padding:"6px 14px" }}
                  onClick={async () => {
                    try {
                      const r = await aFetch("POST", "/admin/dealers/geocode-missing");
                      addToast(`Géocodage : ${r.updated} mis à jour, ${r.failed} échecs`, r.updated > 0 ? "success" : "info");
                      if (r.updated > 0) loadDealers();
                    } catch (e) { addToast(e.message, "error"); }
                  }}>
                  📍 Géocoder manquants
                </button>
                <button className={showSeedForm ? "btn btn--glass" : "btn btn--gold"}
                  style={{ fontSize:13, padding:"6px 14px" }}
                  onClick={() => setShowSeedForm(v => !v)}>
                  {showSeedForm ? "✕ Annuler" : "+ Importer un dealer"}
                </button>
              </div>
            </div>

            {/* Formulaire d'import */}
            {showSeedForm && (
              <div className="glass" style={{ padding:20, marginBottom:20, borderRadius:12 }}>
                <div style={{ fontWeight:600, marginBottom:14, fontSize:14 }}>Nouveau dealer non réclamé</div>
                <div style={{ display:"grid", gridTemplateColumns:"repeat(auto-fill,minmax(200px,1fr))", gap:12 }}>
                  {[
                    ["Nom *", "name", "text", "Chrono Passion"],
                    ["Slug URL *", "slug", "text", "chrono-passion"],
                    ["Ville *", "city", "text", "Bruxelles"],
                    ["Pays", "country", "text", "BE"],
                    ["Adresse", "address", "text", "Rue de la Paix 1"],
                    ["Téléphone", "phone", "text", "+32 2 123 45 67"],
                    ["Site web", "website_url", "text", "https://..."],
                  ].map(([label, field, type, placeholder]) => (
                    <div key={field}>
                      <label style={{ fontSize:12, color:"var(--muted)", display:"block", marginBottom:4 }}>{label}</label>
                      <input className="input" type={type} placeholder={placeholder}
                        value={seedForm[field]}
                        onChange={e => {
                          const val = e.target.value;
                          setSeedForm(p => ({
                            ...p,
                            [field]: val,
                            // auto-slug depuis le nom
                            ...(field === "name" ? { slug: toSlug(val) } : {}),
                          }));
                        }}
                      />
                    </div>
                  ))}
                  <div>
                    <label style={{ fontSize:12, color:"var(--muted)", display:"block", marginBottom:4 }}>Priorité (0–100)</label>
                    <input className="input" type="number" min="0" max="100"
                      value={seedForm.priority_weight}
                      onChange={e => setSeedForm(p => ({ ...p, priority_weight: parseInt(e.target.value) || 0 }))}
                    />
                  </div>
                </div>
                <div style={{ display:"flex", gap:8, marginTop:16 }}>
                  <button className="btn btn--gold" onClick={seedDealer}>Créer le dealer</button>
                  <button className="btn btn--glass" onClick={() => setShowSeedForm(false)}>Annuler</button>
                </div>
              </div>
            )}

            {loading ? <div className="empty"><div className="spinner" /></div> : (
              <div style={{ overflowX:"auto" }}>
                <table style={{ width:"100%", borderCollapse:"collapse", fontSize:13 }}>
                  <thead>
                    <tr style={{ color:"var(--muted)", borderBottom:"1px solid rgba(255,255,255,0.06)" }}>
                      {["ID","Nom","Ville","Plan","Priorité","Statut","Actions"].map(h => (
                        <th key={h} style={{ padding:"8px 10px", textAlign:"left", fontWeight:500 }}>{h}</th>
                      ))}
                    </tr>
                  </thead>
                  <tbody>
                    {dealers.map(d => (
                      <React.Fragment key={d.id}>
                      <tr style={{ borderBottom:"1px solid rgba(255,255,255,0.04)" }}>
                        <td style={{ padding:"8px 10px", color:"var(--muted)" }}>#{d.id}</td>
                        <td style={{ padding:"8px 10px", fontWeight:500 }}>
                          {d.slug
                            ? <a href={`/dealer/${d.slug}`} target="_blank" rel="noopener"
                                style={{ color:"inherit", textDecoration:"none" }}>{d.name}</a>
                            : d.name}
                          {d.is_verified && <span style={{ marginLeft:6, color:"#22c55e", fontSize:11 }}>✓</span>}
                          {!d.is_claimed && <span style={{ marginLeft:6, color:"#9ca3af", fontSize:11 }}>non réclamé</span>}
                        </td>
                        <td style={{ padding:"8px 10px", color:"var(--muted)", fontSize:12 }}>
                          {d.city || "—"}{d.country ? ", " + d.country : ""}
                        </td>
                        <td style={{ padding:"8px 10px" }}>
                          <select value={d.plan} onChange={e => setDealerPlan(d.id, e.target.value)}
                            style={{ background:"rgba(255,255,255,0.05)", border:"1px solid rgba(255,255,255,0.1)",
                              color:"#fff", borderRadius:6, padding:"3px 6px", fontSize:12, cursor:"pointer" }}>
                            {["free","starter","pro","premium"].map(p => (
                              <option key={p} value={p} style={{ background:"#1a1a2e" }}>{p}</option>
                            ))}
                          </select>
                        </td>
                        <td style={{ padding:"8px 10px" }}>
                          <input type="number" min="0" max="100"
                            defaultValue={d.priority_weight ?? 0}
                            onBlur={e => setPriority(d.id, parseInt(e.target.value) || 0)}
                            style={{ width:54, background:"rgba(255,255,255,0.05)", border:"1px solid rgba(255,255,255,0.1)",
                              color:"#fff", borderRadius:6, padding:"3px 6px", fontSize:12, textAlign:"center" }}
                          />
                        </td>
                        <td style={{ padding:"8px 10px" }}>
                          <StatusBadge s={d.is_active ? "active" : "suspended"} />
                        </td>
                        <td style={{ padding:"8px 10px" }}>
                          <div style={{ display:"flex", gap:4, flexWrap:"wrap" }}>
                            <button className="btn btn--glass"
                              style={{ fontSize:11, padding:"3px 8px", color:"#c9a84c" }}
                              onClick={() => editDealer === d.id ? setEditDealer(null) : openEdit(d)}>
                              ✏️ {editDealer === d.id ? "Fermer" : "Modifier"}
                            </button>
                            {!d.is_verified && (
                              <button className="btn btn--glass"
                                style={{ fontSize:11, padding:"3px 8px", color:"#22c55e" }}
                                onClick={() => verifyDealer(d.id)}>✓ Vérifier</button>
                            )}
                            <button className="btn btn--glass"
                              style={{ fontSize:11, padding:"3px 8px", color: d.is_active ? "#ef4444" : "#22c55e" }}
                              onClick={() => toggleDealer(d.id, d.is_active)}>
                              {d.is_active ? "⊘ Suspendre" : "↺ Réactiver"}
                            </button>
                            <button className="btn btn--glass"
                              style={{ fontSize:11, padding:"3px 8px", color:"#ef4444" }}
                              onClick={() => deleteDealer(d.id, d.name)}
                              title="Supprimer (seulement si aucune annonce)">🗑</button>
                          </div>
                        </td>
                      </tr>
                      {editDealer === d.id && (
                        <tr key={`edit-${d.id}`} style={{ background:"rgba(201,168,76,0.05)", borderBottom:"1px solid rgba(255,255,255,0.06)" }}>
                          <td colSpan={7} style={{ padding:"16px 12px" }}>
                            <div style={{ display:"grid", gridTemplateColumns:"repeat(auto-fill,minmax(180px,1fr))", gap:10, marginBottom:12 }}>
                              {[
                                ["Nom", "name"], ["Slug URL", "slug"], ["Ville", "city"],
                                ["Pays", "country"], ["Adresse", "address"], ["Téléphone", "phone"],
                                ["Email", "email"], ["Site web", "website_url"],
                                ["Lat", "lat"], ["Lng", "lng"],
                              ].map(([label, field]) => (
                                <div key={field}>
                                  <label style={{ fontSize:11, color:"var(--muted)", display:"block", marginBottom:3 }}>{label}</label>
                                  <input className="input" style={{ fontSize:12, padding:"5px 8px" }}
                                    value={editForm[field]}
                                    onChange={e => setEditForm(p => ({ ...p, [field]: e.target.value }))}
                                  />
                                </div>
                              ))}
                              <div style={{ gridColumn:"1 / -1" }}>
                                <label style={{ fontSize:11, color:"var(--muted)", display:"block", marginBottom:3 }}>Description</label>
                                <textarea className="input" rows={2} style={{ fontSize:12, padding:"5px 8px", width:"100%", resize:"vertical" }}
                                  value={editForm.description}
                                  onChange={e => setEditForm(p => ({ ...p, description: e.target.value }))}
                                />
                              </div>
                              {categories.length > 0 && (
                                <div style={{ gridColumn:"1 / -1" }}>
                                  <label style={{ fontSize:11, color:"var(--muted)", display:"block", marginBottom:6 }}>Spécialités</label>
                                  <div style={{ display:"flex", flexWrap:"wrap", gap:6 }}>
                                    {categories.map(cat => {
                                      const checked = (editForm.category_ids || []).includes(cat.id);
                                      return (
                                        <label key={cat.id} style={{ display:"flex", alignItems:"center", gap:5, cursor:"pointer",
                                          fontSize:12, padding:"3px 10px", borderRadius:20,
                                          border:`1px solid ${checked ? "var(--gold)" : "var(--line)"}`,
                                          background: checked ? "rgba(201,168,76,.12)" : "transparent",
                                          color: checked ? "var(--gold)" : "var(--muted)" }}>
                                          <input type="checkbox" checked={checked} style={{ display:"none" }}
                                            onChange={() => setEditForm(p => ({
                                              ...p,
                                              category_ids: checked
                                                ? p.category_ids.filter(id => id !== cat.id)
                                                : [...(p.category_ids || []), cat.id],
                                            }))} />
                                          {cat.label_fr}
                                        </label>
                                      );
                                    })}
                                  </div>
                                </div>
                              )}
                            </div>
                            <div style={{ display:"flex", gap:8 }}>
                              <button className="btn btn--gold" style={{ fontSize:12 }} onClick={() => saveDealer(d.id)}>
                                Enregistrer
                              </button>
                              <button className="btn btn--glass" style={{ fontSize:12 }} onClick={() => setEditDealer(null)}>
                                Annuler
                              </button>
                            </div>
                          </td>
                        </tr>
                      )}
                      </React.Fragment>
                    ))}
                  </tbody>
                </table>
                {dealers.length === 0 && (
                  <div className="empty" style={{ minHeight:120 }}>Aucun dealer</div>
                )}
              </div>
            )}
          </div>
        )}

        {/* ── REVENDICATIONS ── */}
        {tab === "revendications" && (
          <div>
            <p style={{ fontSize:13, color:"var(--muted)", marginBottom:16 }}>
              Demandes de revendication en attente de validation.
              Vérifiez l'identité du demandeur avant d'approuver.
            </p>
            {loading ? <div className="empty"><div className="spinner" /></div> : claims.length === 0 ? (
              <div className="empty" style={{ minHeight:120 }}>Aucune demande en attente</div>
            ) : (
              <div style={{ display:"flex", flexDirection:"column", gap:12 }}>
                {claims.map(c => (
                  <div key={c.id} style={{ padding:"16px 20px", borderRadius:12,
                    border:"1px solid var(--line)", background:"rgba(255,255,255,.03)",
                    display:"flex", alignItems:"center", justifyContent:"space-between", gap:16, flexWrap:"wrap" }}>
                    <div>
                      <div style={{ display:"flex", alignItems:"center", gap:8, marginBottom:4 }}>
                        <span style={{ fontWeight:700, color:"var(--text)" }}>{c.name}</span>
                        <span style={{ fontSize:11, padding:"2px 8px", borderRadius:20,
                          background: c.type === "claim" ? "rgba(201,168,76,.15)" : "rgba(99,102,241,.15)",
                          color: c.type === "claim" ? "var(--gold)" : "#818cf8" }}>
                          {c.type === "claim" ? "Revendication" : "Nouvelle fiche"}
                        </span>
                      </div>
                      <div style={{ fontSize:13, color:"var(--muted)" }}>📍 {c.city} · #{c.id}</div>
                      {c.type === "claim" && (
                        <div style={{ fontSize:12, color:"var(--faint)", marginTop:4 }}>
                          Email boutique : {c.dealer_email || <em>non renseigné</em>}
                        </div>
                      )}
                      <div style={{ fontSize:12, color:"var(--gold)", marginTop:2 }}>
                        {c.type === "claim" ? "Revendiqué par" : "Créé par"} : {c.claimant_email}
                      </div>
                    </div>
                    <div style={{ display:"flex", gap:8 }}>
                      {c.type === "claim" ? (<>
                        <button className="btn btn--gold" style={{ fontSize:12, padding:"6px 16px" }}
                          onClick={() => approveClaim(c.id, c.name)}>✓ Approuver</button>
                        <button className="btn btn--glass" style={{ fontSize:12, padding:"6px 16px", color:"#ef4444" }}
                          onClick={() => rejectClaim(c.id, c.name)}>✕ Rejeter</button>
                      </>) : (<>
                        <button className="btn btn--gold" style={{ fontSize:12, padding:"6px 16px" }}
                          onClick={() => activateDealer(c.id, c.name)}>✓ Activer</button>
                        <button className="btn btn--glass" style={{ fontSize:12, padding:"6px 16px", color:"#ef4444" }}
                          onClick={() => rejectNewDealer(c.id, c.name)}>✕ Refuser</button>
                      </>)}
                    </div>
                  </div>
                ))}
              </div>
            )}
          </div>
        )}

        {/* ── RGPD ── */}
        {tab === "categories" && (
          <CategoriesTab categories={categories} aFetch={aFetch} reload={loadCategories} addToast={addToast} />
        )}

        {tab === "rgpd" && (
          <div>
            <div style={{ marginBottom:16 }}>
              <div style={{ fontSize:13, color:"var(--muted)", marginBottom:8 }}>
                {users.length} compte{users.length !== 1 ? "s" : ""} enregistré{users.length !== 1 ? "s" : ""}
              </div>
              <p style={{ fontSize:12, color:"var(--faint)", margin:0 }}>
                L'anonymisation supprime l'email, le mot de passe et le lien Telegram. Les annonces et alertes sont conservées sans identifiant personnel.
              </p>
            </div>
            {loading ? <div className="empty"><div className="spinner" /></div> : (
              <div style={{ overflowX:"auto" }}>
                <table style={{ width:"100%", borderCollapse:"collapse", fontSize:13 }}>
                  <thead>
                    <tr style={{ color:"var(--muted)", borderBottom:"1px solid rgba(255,255,255,0.06)" }}>
                      {["ID","Email","Rôle","Statut","Inscription","Action"].map(h => (
                        <th key={h} style={{ padding:"8px 10px", textAlign:"left", fontWeight:500 }}>{h}</th>
                      ))}
                    </tr>
                  </thead>
                  <tbody>
                    {users.map(u => {
                      const anonymized = u.email.startsWith("deleted_");
                      return (
                        <tr key={u.id} style={{ borderBottom:"1px solid rgba(255,255,255,0.04)", opacity: anonymized ? 0.4 : 1 }}>
                          <td style={{ padding:"8px 10px", color:"var(--muted)" }}>#{u.id}</td>
                          <td style={{ padding:"8px 10px" }}>{u.email}</td>
                          <td style={{ padding:"8px 10px", color:"var(--muted)", fontSize:12 }}>{u.role}</td>
                          <td style={{ padding:"8px 10px" }}>
                            <StatusBadge s={anonymized ? "archived" : (u.is_active !== false ? "available" : "suspended")} />
                          </td>
                          <td style={{ padding:"8px 10px", color:"var(--muted)", fontSize:12 }}>
                            {u.created_at ? new Date(u.created_at).toLocaleDateString("fr-FR") : "—"}
                          </td>
                          <td style={{ padding:"8px 10px" }}>
                            {!anonymized && (
                              <button className="btn btn--glass"
                                style={{ fontSize:11, padding:"3px 8px", color:"#ef4444" }}
                                onClick={() => deleteUser(u.id, u.email)}>
                                Anonymiser
                              </button>
                            )}
                          </td>
                        </tr>
                      );
                    })}
                  </tbody>
                </table>
                {users.length === 0 && (
                  <div className="empty" style={{ minHeight:120 }}>Aucun utilisateur</div>
                )}
              </div>
            )}
          </div>
        )}
      </div>
      {confirmModal}
    </div>
  );
}

/* ============================================================
   ROOT APP
   ============================================================ */
function AppInner() {
  const { toasts, addToast } = useToasts();
  const { user, login, logout, updateVerified } = useAuth();
  const { route, go } = useRouter();
  const { t } = useTranslation();
  useMeta();

  // Enregistrer le Service Worker tôt (sans bloquer le rendu)
  useEffect(() => {
    if ("serviceWorker" in navigator) {
      navigator.serviceWorker.register("/sw.js", { scope: "/" }).catch(() => {});
    }
  }, []);

  // Favorites — local cache, synced with API when logged in
  const [favIds, setFavIds] = useState(() => {
    try { return JSON.parse(localStorage.getItem("we_favs") || "[]"); }
    catch { return []; }
  });
  useEffect(() => { localStorage.setItem("we_favs", JSON.stringify(favIds)); }, [favIds]);

  const toggleFav = async id => {
    const has = favIds.includes(id);
    if (user) {
      try {
        if (has) await api.delete("/listings/favorites/" + id);
        else     await api.post("/listings/favorites/" + id);
      } catch (e) { addToast(e.message, "error"); return; }
    }
    setFavIds(p => has ? p.filter(x => x !== id) : [...p, id]);
    addToast(has ? t('toast.fav_removed') : t('toast.fav_added'), has ? "info" : "success");
  };

  // Après login : synchronise les favoris locaux vers l'API
  const handleLogin = async (userData, accessToken, refreshToken) => {
    login(userData, accessToken, refreshToken);
    const localFavs = (() => {
      try { return JSON.parse(localStorage.getItem("we_favs") || "[]"); } catch { return []; }
    })();
    if (localFavs.length > 0) {
      // Poster chaque favori local (ignore les erreurs 409 si déjà existant)
      await Promise.allSettled(localFavs.map(id => api.post("/listings/favorites/" + id)));
    }
  };

  const handleLogout = () => {
    logout();
    setFavIds([]);
    go("dealers");
  };

  const sharedProps = { go, user, favIds, onFav: toggleFav, addToast };

  const { page, param } = route;
  let content;
  switch (page) {
    case "watch":     content = <DetailPage      {...sharedProps} id={param} />; break;
    case "favorites": content = MARKETPLACE_ENABLED ? <FavoritesPage {...sharedProps} /> : <DealersPage {...sharedProps} onLogout={handleLogout} />; break;
    case "alerts":    content = <AlertsPage   {...sharedProps} />; break;
    case "login":     content = <AuthPage        go={go} mode="login"    onLogin={handleLogin} />; break;
    case "register":  content = <AuthPage        go={go} mode="register" onLogin={handleLogin} />; break;
    case "dashboard": content = <DashboardPage   {...sharedProps} onLogout={handleLogout} />; break;
    case "new":       content = <NewListingPage  {...sharedProps} editId={param} />; break;
    case "dealer":    content = <DealerPage      {...sharedProps} slug={param} />; break;
    case "reset":     content = <ResetPasswordPage go={go} addToast={addToast} />; break;
    case "verify-email": content = <VerifyEmailPage go={go} token={param} addToast={addToast} updateVerified={updateVerified} />; break;
    case "account":   content = <AccountPage go={go} user={user} addToast={addToast} onLogout={handleLogout} />; break;
    case "admin":     content = <AdminPage   go={go} addToast={addToast} />; break;
    case "legal":     content = <LegalPage   go={go} />; break;
    case "cgu":       content = <CguPage     go={go} />; break;
    case "privacy":   content = <PrivacyPage go={go} />; break;
    case "search":    content = <SearchPage  {...sharedProps} onLogout={handleLogout} />; break;
    default:          content = <DealersPage {...sharedProps} onLogout={handleLogout} />; break;
  }

  const isMobile = window.innerWidth < 768;

  return (
    <div className="root">
      <div className="bg-aura" />
      {user && !user.is_verified && !["login","register","reset","verify-email","admin"].includes(page) && (
        <EmailVerifyBanner addToast={addToast} />
      )}
      {content}
      {isMobile && !["login","register","reset","admin"].includes(page) && (
        <BottomNav page={page} go={go} favCount={favIds.length} user={user} />
      )}
      <CookieBanner go={go} />
      <Toasts toasts={toasts} />
    </div>
  );
}

function App() {
  return (
    <I18nProvider>
      <AppInner />
    </I18nProvider>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
