/** @jsx React.createElement */
const { useState, useEffect, useRef, useCallback, useMemo, memo } = React;

// ═══════════════════════════════════════════
// UTILITAIRES
// ═══════════════════════════════════════════
function escapeHtml(str = "") {
  return String(str).replace(/[&<>"']/g, (c) =>
    ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c])
  );
}
function parsePriceVal(str) {
  if (!str) return null;
  // On ne garde que les chiffres, les points et les virgules
  let s = String(str).replace(/[^\d.,]/g, "");
  
  // Gestion des formats 1,400.00 ou 1.400,00
  if (s.includes(".") && s.includes(",")) {
    if (s.indexOf(",") < s.indexOf(".")) {
      // Format US (1,400.00) -> on enlève la virgule
      s = s.replace(/,/g, "");
    } else {
      // Format FR (1.400,00) -> on enlève le point, on met un point à la place de la virgule
      s = s.replace(/\./g, "").replace(",", ".");
    }
  } else {
    // S'il n'y a qu'une virgule, on la change en point (format classique)
    s = s.replace(",", ".");
  }
  
  const n = parseFloat(s);
  return isNaN(n) ? null : n;
}
function calcProfit(item) {
  const sell = parsePriceVal(item.custom_price);
  const buy = parsePriceVal(item.price_eur);
  if (sell === null || buy === null || buy === 0) return { profit: null, mult: null };
  return { profit: sell - buy, mult: sell / buy };
}
function profitClass(mult) {
  if (mult === null) return "";
  if (mult >= 2.5) return "profit-good";
  if (mult >= 1.5) return "profit-mid";
  return "profit-low";
}
function generateIconDataUrl(size) {
  const canvas = document.createElement("canvas");
  canvas.width = canvas.height = size;
  const ctx = canvas.getContext("2d");
  ctx.fillStyle = "#0a0a0a";
  ctx.fillRect(0, 0, size, size);
  ctx.beginPath();
  ctx.arc(size / 2, size / 2, size * 0.42, 0, Math.PI * 2);
  ctx.fillStyle = "#dc2626";
  ctx.fill();
  ctx.fillStyle = "#ffffff";
  ctx.font = `bold ${size * 0.45}px Arial`;
  ctx.textAlign = "center";
  ctx.textBaseline = "middle";
  ctx.fillText("⚡", size / 2, size / 2 + size * 0.02);
  return canvas.toDataURL("image/png");
}
function authHeaders(key, extra = {}) {
  return { "Content-Type": "application/json", "X-License-Key": key, ...extra };
}

// ═══════════════════════════════════════════
// HOOK WebSocket avec reconnexion auto
// ═══════════════════════════════════════════
function useWebSocket(licenseKey, onMessage) {
  const wsRef = useRef(null);
  const timerRef = useRef(null);
  const pingRef = useRef(null);
  const onMessageRef = useRef(onMessage);
  const [connStatus, setConnStatus] = useState("connecting");

  // Met à jour la ref sans déclencher d'effet de reconnexion
  useEffect(() => {
    onMessageRef.current = onMessage;
  }, [onMessage]);

  const connect = useCallback(() => {
    if (!licenseKey) return;
    if (wsRef.current && (wsRef.current.readyState === WebSocket.OPEN || wsRef.current.readyState === WebSocket.CONNECTING)) {
        return;
    }

    const url = `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}/ws?key=${encodeURIComponent(licenseKey)}`;
    const ws = new WebSocket(url);
    wsRef.current = ws;
    setConnStatus("connecting");

    ws.onopen = () => {
      clearTimeout(timerRef.current);
      setConnStatus("open");
      clearInterval(pingRef.current);
      pingRef.current = setInterval(() => {
        if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
            wsRef.current.send("ping");
        }
      }, 20000);
    };
    ws.onmessage = (e) => {
          // On bloque les messages texte purs (ping/pong) pour ne pas faire planter le JSON.parse
          if (e.data === "pong" || e.data === "ping") {
            return;
          }

          if (onMessageRef.current) {
              onMessageRef.current(e);
          }
        };
    
    ws.onclose = () => {
      clearInterval(pingRef.current);
      setConnStatus("closed");
      wsRef.current = null;
      // Reconnexion automatique
      timerRef.current = setTimeout(() => connect(), 3000);
    };

    ws.onerror = (err) => {
        console.error("Erreur WebSocket:", err);
        if (wsRef.current) {
            wsRef.current.close();
        }
    };
  }, [licenseKey]); // Uniquement licenseKey en dépendance

  useEffect(() => {
    if (licenseKey) {
        connect();
    }
    return () => {
      clearTimeout(timerRef.current);
      clearInterval(pingRef.current);
      if (wsRef.current) {
        wsRef.current.onclose = null; // Empêche la reconnexion au démontage
        wsRef.current.close();
        wsRef.current = null;
      }
    };
  }, [licenseKey, connect]);

  return connStatus;
}

// ═══════════════════════════════════════════
// HOOK Notifications
// ═══════════════════════════════════════════
function useNotifications() {
  const [notifs, setNotifs] = useState([]);
  const [enabled, setEnabled] = useState(true);
  const idRef = useRef(0);

  const show = useCallback(
    (message, type = "default") => {
      if (!enabled) return;
      const id = ++idRef.current;
      setNotifs((prev) => [...prev, { id, message, type }]);
      setTimeout(() => dismiss(id), 5000);
    },
    [enabled]
  );

  const dismiss = useCallback((id) => {
    setNotifs((prev) => prev.filter((n) => n.id !== id));
  }, []);

  const toggle = useCallback(() => {
    setEnabled((v) => !v);
    if (enabled) setNotifs([]);
  }, [enabled]);

  return { notifs, show, dismiss, toggle, enabled };
}

// ═══════════════════════════════════════════
// COMPOSANT : Toast Notification
// ═══════════════════════════════════════════
const NotifToast = memo(({ notif, onDismiss }) => (
  <div
    className={`notif-toast notif-${notif.type}`}
    onClick={() => onDismiss(notif.id)}
    style={{ position: "relative", overflow: "hidden" }}
  >
    <span
      style={{ flex: 1, lineHeight: 1.4 }}
      dangerouslySetInnerHTML={{ __html: notif.message }}
    />
    <button
      onClick={(e) => { e.stopPropagation(); onDismiss(notif.id); }}
      style={{ background: "none", border: "none", color: "currentColor", opacity: 0.5, cursor: "pointer", fontSize: 14, flexShrink: 0 }}
    >✕</button>
    <div className="notif-bar" />
  </div>
));

// ═══════════════════════════════════════════
// COMPOSANT : Card Article
// ═══════════════════════════════════════════
const ItemCard = memo(({ item, isSaved, isBoutique, isBoutiqueTab, onOpen, onToggleSave, onToggleBoutique }) => {
  const { profit } = calcProfit(item);
  const stars = parseFloat(item.seller_stars) || 0;
  const starsBadge = stars > 0 ? (
    <div style={{ position: "absolute", top: 10, left: 10, background: "rgba(0,0,0,0.7)", backdropFilter: "blur(8px)", border: "1px solid rgba(255,255,255,0.1)", borderRadius: 10, padding: "4px 10px", fontSize: 11, fontWeight: 900, color: "#facc15", zIndex: 30 }}>
      ⭐ {stars}
    </div>
  ) : null;

  const sourceBadge = item.source_name ? (
    <div style={{ position: "absolute", top: 10, right: 10, background: "rgba(220,38,38,0.15)", backdropFilter: "blur(6px)", border: "1px solid rgba(220,38,38,0.25)", borderRadius: 8, padding: "3px 8px", fontSize: 9, fontWeight: 900, color: "#f87171", textTransform: "uppercase", letterSpacing: "0.08em", zIndex: 30 }}>
      {item.source_name}
    </div>
  ) : null;

  const priceDisplay = item._prev_price ? (
    <div style={{ display: "flex", flexDirection: "column", gap: 2 }}>
      <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
        <span style={{ textDecoration: "line-through", color: "#555", fontSize: 12, fontWeight: 700 }}>{item._prev_price}</span>
        <span style={{ fontSize: 10, fontWeight: 900, background: "rgba(239,68,68,0.12)", border: "1px solid rgba(239,68,68,0.25)", color: "#f87171", padding: "2px 6px", borderRadius: 6 }}>↓ BAISSE</span>
      </div>
      <div className="text-xl font-black text-red-500">{item.price_eur}</div>
    </div>
  ) : (
    <div className="text-xl font-black text-red-500">{item.price_eur}</div>
  );

  return (
    <div className="card-new p-5 flex flex-col group" onClick={() => onOpen(item, isBoutiqueTab)}>
      <div className="relative w-full h-60 bg-black/40 rounded-2xl overflow-hidden mb-4 border border-white/5 transition group-hover:border-red-600/30">
        {starsBadge}
        {sourceBadge}
        <img src={item.image} className="w-full h-full object-contain p-2 group-hover:scale-110 transition-transform duration-700" alt={item.title} loading="lazy" />
        {/* Heart */}
        <button
          className={`absolute bottom-2 right-14 w-9 h-9 rounded-xl flex items-center justify-center transition-all z-40 ${isSaved ? "bg-red-600 text-white" : "bg-black/60 text-white/40 hover:text-red-500"}`}
          onClick={(e) => { e.stopPropagation(); onToggleSave(item); }}
        >
          <svg width="16" height="16" viewBox="0 0 24 24" fill={isSaved ? "currentColor" : "none"} stroke="currentColor" strokeWidth="2"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>
        </button>
        {/* Shop */}
        <button
          className={`absolute bottom-2 right-3 w-9 h-9 rounded-xl flex items-center justify-center transition-all z-40 ${isBoutique ? "bg-blue-600 text-white" : "bg-black/60 text-white/40 hover:text-blue-500"}`}
          onClick={(e) => { e.stopPropagation(); onToggleBoutique(item); }}
        >🏪</button>
      </div>
      <div className="px-1 flex-1 flex flex-col justify-between text-left">
        <h3 className="text-[13px] font-bold text-gray-200 line-clamp-2 italic mb-1 group-hover:text-white">{item.title}</h3>
        {item.filter_name && <div style={{ fontSize: 9, fontWeight: 900, color: "#555", textTransform: "uppercase", letterSpacing: "0.08em", marginBottom: 4 }}>{item.filter_name}</div>}
        <div className="flex justify-between items-end mt-2">
          {priceDisplay}
          {isBoutiqueTab && profit !== null
            ? <div className={`text-[10px] font-black ${profit >= 0 ? "text-green-500" : "text-red-400"}`}>{profit >= 0 ? "+" : ""}{profit.toFixed(2)} €</div>
            : <div className="text-[10px] text-gray-600 font-mono pb-1">{item.detected_at || ""}</div>}
        </div>
      </div>
    </div>
  );
});

// ═══════════════════════════════════════════
// COMPOSANT : Modal Détail Article
// ═══════════════════════════════════════════
const ItemModal = memo(({ item, isBoutiqueTab, isSaved, isBoutique, onClose, onToggleSave, onToggleBoutique, onSavePrice, licenseKey }) => {
  const [images, setImages] = useState([]);
  const [slideIdx, setSlideIdx] = useState(0);
  const [priceInput, setPriceInput] = useState("");
  const [priceSaved, setPriceSaved] = useState(false);
  const [tagInput, setTagInput] = useState("");

  useEffect(() => {
    if (!item) return;
    const imgs = item.images?.length ? item.images : item.image ? [item.image] : [];
    setImages(imgs);
    setSlideIdx(0);
    setPriceInput(parsePriceVal(item.custom_price) || "");
    setPriceSaved(false);
    setTagInput("");
    // Enrichissement lazy
    if (!item._enriched && !isBoutiqueTab) {
      fetch("/fetch_item_details", {
        method: "POST",
        headers: authHeaders(licenseKey),
        body: JSON.stringify({ url: item.url }),
      })
        .then((r) => r.ok ? r.json() : null)
        .then((data) => {
          if (!data || data.status === "error") return;
          if (data.images?.length) setImages(data.images);
          item._enriched = true;
        })
        .catch(() => {});
    }
  }, [item]);

  if (!item) return null;

  const { profit, mult } = calcProfit(item);
  const lensUrl = item.image ? `https://lens.google.com/uploadbyurl?url=${encodeURIComponent(item.image)}&q=vestiaire+collective` : null;

  const handleSavePrice = async () => {
    const val = parseFloat(priceInput);
    if (isNaN(val) || val < 0) return;
    const newPrice = val.toFixed(2).replace(".", ",") + " €";
    await onSavePrice(item.url, newPrice);
    setPriceSaved(true);
    setTimeout(() => setPriceSaved(false), 1500);
  };

  return (
    <div className="modal-bg" style={{ display: "flex" }} onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}>
      <div style={{ background: "#0a0a0a", border: "1px solid rgba(255,255,255,0.1)", borderRadius: 28, padding: 32, width: "100%", maxWidth: 700, maxHeight: "90vh", overflowY: "auto", position: "relative" }}
        onClick={(e) => e.stopPropagation()}>
        <button onClick={onClose} style={{ position: "absolute", top: 16, right: 16, background: "rgba(255,255,255,0.05)", border: "1px solid rgba(255,255,255,0.1)", borderRadius: "50%", width: 36, height: 36, color: "#888", fontSize: 18, cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center" }}>✕</button>

        {/* Galerie */}
        <div className="gallery-box mb-4">
          {images.map((src, i) => (
            <img key={i} src={src} className={`gallery-main-img ${i === slideIdx ? "active" : ""}`} alt="" />
          ))}
          {images.length > 1 && <>
            <button onClick={() => setSlideIdx((i) => (i - 1 + images.length) % images.length)} style={{ position: "absolute", left: 10, top: "50%", transform: "translateY(-50%)", background: "rgba(0,0,0,0.6)", border: "none", color: "#fff", borderRadius: 10, width: 36, height: 36, cursor: "pointer", fontSize: 18, zIndex: 10 }}>‹</button>
            <button onClick={() => setSlideIdx((i) => (i + 1) % images.length)} style={{ position: "absolute", right: 10, top: "50%", transform: "translateY(-50%)", background: "rgba(0,0,0,0.6)", border: "none", color: "#fff", borderRadius: 10, width: 36, height: 36, cursor: "pointer", fontSize: 18, zIndex: 10 }}>›</button>
            <div style={{ position: "absolute", bottom: 10, right: 10, background: "rgba(0,0,0,0.6)", borderRadius: 8, padding: "4px 10px", fontSize: 11, color: "#fff", fontWeight: 700 }}>{slideIdx + 1} / {images.length}</div>
          </>}
        </div>
        {images.length > 1 && (
          <div style={{ display: "flex", gap: 8, flexWrap: "wrap", marginBottom: 16 }}>
            {images.map((src, i) => (
              <img key={i} src={src} className={`thumb-item ${i === slideIdx ? "active" : ""}`} onClick={() => setSlideIdx(i)} alt="" />
            ))}
          </div>
        )}

        <h2 style={{ fontSize: 18, fontWeight: 900, color: "#fff", fontStyle: "italic", marginBottom: 8 }}>{item.title}</h2>
        {!isBoutiqueTab && <p style={{ fontSize: 11, color: "#555", marginBottom: 4 }}>🎯 {item.filter_name || "Filtre inconnu"} · {item.condition || "Occasion"}</p>}
        {!isBoutiqueTab && <p style={{ fontSize: 11, color: "#555", marginBottom: 16 }}>👤 {(item.seller_name || "Vendeur")} ⭐ ({item.seller_stars || 0}/5)</p>}

        <div style={{ fontSize: 9, fontWeight: 900, color: "#555", textTransform: "uppercase", letterSpacing: "0.1em", marginBottom: 4 }}>{isBoutiqueTab ? "PRIX DE VENTE" : "PRIX"}</div>
        <div style={{ fontSize: 28, fontWeight: 900, color: "#dc2626", marginBottom: 16 }}>{isBoutiqueTab ? (item.custom_price || "--- EUR") : item.price_eur}</div>

        {/* Edit prix (boutique) */}
        {isBoutiqueTab && (
          <div style={{ display: "flex", gap: 8, marginBottom: 16 }}>
            <input
              type="number"
              value={priceInput}
              onChange={(e) => setPriceInput(e.target.value)}
              placeholder="Prix de vente €"
              style={{ flex: 1, background: "rgba(255,255,255,0.04)", border: "1px solid rgba(255,255,255,0.1)", padding: "10px 14px", borderRadius: 12, color: "#fff", fontSize: 14, outline: "none" }}
            />
            <button onClick={handleSavePrice} style={{ background: priceSaved ? "#16a34a" : "#dc2626", color: "#fff", padding: "10px 20px", borderRadius: 12, fontWeight: 900, fontSize: 11, textTransform: "uppercase", border: "none", cursor: "pointer", transition: "0.2s" }}>
              {priceSaved ? "✓ Sauvegardé !" : "✓ Enregistrer"}
            </button>
          </div>
        )}

        {/* Profit boutique */}
        {isBoutiqueTab && profit !== null && (
          <div style={{ display: "flex", gap: 12, marginBottom: 16, background: "rgba(255,255,255,0.03)", border: "1px solid rgba(255,255,255,0.07)", borderRadius: 14, padding: "12px 16px" }}>
            <div><div style={{ fontSize: 9, color: "#555", fontWeight: 900, textTransform: "uppercase", letterSpacing: "0.1em" }}>ACHETÉ</div><div style={{ fontWeight: 700, color: "#fff" }}>{parsePriceVal(item.price_eur)?.toFixed(2)} €</div></div>
            <div><div style={{ fontSize: 9, color: "#555", fontWeight: 900, textTransform: "uppercase", letterSpacing: "0.1em" }}>BÉNÉFICE</div><div className={`font-bold ${profit >= 0 ? "text-green-400" : "text-red-400"}`}>{profit >= 0 ? "+" : ""}{profit.toFixed(2)} €</div></div>
            <div><div style={{ fontSize: 9, color: "#555", fontWeight: 900, textTransform: "uppercase", letterSpacing: "0.1em" }}>MULT.</div><div className={`font-bold ${profitClass(mult)}`}>×{mult?.toFixed(2)}</div></div>
          </div>
        )}

        {/* Actions */}
        <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
          <button onClick={() => onToggleSave(item)} className={`w-full flex items-center justify-center gap-2 py-3 rounded-xl border transition font-bold text-[11px] uppercase ${isSaved ? "bg-red-600 border-red-600 text-white" : "bg-white/5 border-white/10 text-gray-400"}`}>
            ❤️ {isSaved ? "Retirer des Favoris" : "Ajouter aux Favoris"}
          </button>
          <button onClick={() => onToggleBoutique(item)} className={`w-full flex items-center justify-center gap-2 py-3 rounded-xl border transition font-bold text-[11px] uppercase ${isBoutique ? "bg-blue-600 border-blue-600 text-white" : "bg-white/5 border-white/10 text-gray-400"}`}>
            🏪 {isBoutique ? "Retirer de la Boutique" : "Ajouter à la Boutique"}
          </button>
          {lensUrl && !isBoutiqueTab && (
            <a href={lensUrl} target="_blank" rel="noopener noreferrer" className="w-full flex items-center justify-center gap-2 py-3 rounded-xl border border-white/10 bg-white/5 text-gray-400 font-bold text-[11px] uppercase hover:bg-indigo-600/20 hover:text-indigo-300 transition" style={{ textDecoration: "none" }}>
              🔍 Rechercher sur Google Lens
            </a>
          )}
          {!isBoutiqueTab && <a href={item.url} target="_blank" rel="noopener noreferrer" className="w-full flex items-center justify-center gap-2 py-3 rounded-xl border border-white/10 bg-white/5 text-gray-400 font-bold text-[11px] uppercase hover:bg-white/10 transition" style={{ textDecoration: "none" }}>🔗 Voir l'annonce</a>}
        </div>
      </div>
    </div>
  );
});

// ═══════════════════════════════════════════
// COMPOSANT : Licence Gate
// ═══════════════════════════════════════════
const LicenceGate = ({ onUnlock }) => {
  const [key, setKey] = useState("");
  const [error, setError] = useState("");
  const [loading, setLoading] = useState(false);

  const verify = async () => {
    if (!key.trim()) { setError("Entre ta clé de licence."); return; }
    setLoading(true); setError("");
    try {
      const r = await fetch("/verify_licence", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ license_key: key.trim() }) });
      const data = await r.json();
      if (data.valid) { localStorage.setItem("zentracker-licence-key", key.trim()); onUnlock(data.plan, key.trim()); }
      else setError(data.message || "Licence invalide ou expirée.");
    } catch { setError("Serveur injoignable. Assure-toi que server.py tourne."); }
    setLoading(false);
  };

  return (
    <div style={{ position: "fixed", inset: 0, zIndex: 9999, background: "#050505", display: "flex", alignItems: "center", justifyContent: "center", padding: 24 }}>
      {/* Blobs deco */}
      <div style={{ position: "absolute", inset: 0, overflow: "hidden", pointerEvents: "none" }}>
        <div style={{ position: "absolute", top: "15%", left: "20%", width: 300, height: 300, background: "radial-gradient(circle,rgba(220,38,38,0.06) 0%,transparent 70%)", borderRadius: "50%", animation: "pulse-slow 4s ease-in-out infinite" }} />
        <div style={{ position: "absolute", bottom: "20%", right: "15%", width: 400, height: 400, background: "radial-gradient(circle,rgba(220,38,38,0.04) 0%,transparent 70%)", borderRadius: "50%", animation: "pulse-slow 4s ease-in-out infinite 2s" }} />
      </div>
      <div style={{ width: "100%", maxWidth: 440, position: "relative", zIndex: 1 }}>
        <div style={{ textAlign: "center", marginBottom: 40 }}>
          <div style={{ display: "inline-flex", alignItems: "center", justifyContent: "center", width: 64, height: 64, background: "rgba(220,38,38,0.1)", border: "1px solid rgba(220,38,38,0.25)", borderRadius: 20, marginBottom: 20 }}>
            <span style={{ fontSize: 28 }}>⚡</span>
          </div>
          <h1 style={{ fontSize: 26, fontWeight: 900, color: "#fff", letterSpacing: "-0.03em", fontStyle: "italic", textTransform: "uppercase", margin: "0 0 8px" }}>ZEN<span style={{ color: "#dc2626" }}>TRACKER</span></h1>
          <p style={{ fontSize: 11, color: "#444", fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.15em", margin: 0 }}>SNIPER PRO · ACCÈS MEMBRES</p>
        </div>
        <div style={{ background: "#0a0a0a", border: "1px solid rgba(255,255,255,0.07)", borderRadius: 28, padding: 36, position: "relative", overflow: "hidden" }}>
          <div style={{ position: "absolute", top: 0, left: "10%", right: "10%", height: 1, background: "linear-gradient(90deg,transparent,rgba(220,38,38,0.4),transparent)" }} />
          <p style={{ fontSize: 12, color: "#555", fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.1em", margin: "0 0 8px" }}>Clé de licence</p>
          <p style={{ fontSize: 13, color: "#888", margin: "0 0 24px", lineHeight: 1.6 }}>Entre ta clé Whop pour accéder au bot. Tu la trouves dans ton espace membre sur <span style={{ color: "#dc2626", fontWeight: 700 }}>whop.com</span></p>
          <div style={{ position: "relative", marginBottom: 16 }}>
            <span style={{ position: "absolute", left: 16, top: "50%", transform: "translateY(-50%)", fontSize: 16 }}>🔑</span>
            <input
              type="text" value={key} onChange={(e) => { setKey(e.target.value); setError(""); }}
              onKeyDown={(e) => e.key === "Enter" && verify()}
              placeholder="XXXX-XXXX-XXXX-XXXX"
              style={{ width: "100%", background: "rgba(255,255,255,0.04)", border: `1.5px solid ${error ? "rgba(220,38,38,0.6)" : "rgba(255,255,255,0.08)"}`, padding: "15px 16px 15px 46px", borderRadius: 16, outline: "none", color: "#fff", fontSize: 14, fontWeight: 700, letterSpacing: "0.05em", boxSizing: "border-box", transition: "border-color 0.2s" }}
            />
          </div>
          {error && <div style={{ background: "rgba(220,38,38,0.08)", border: "1px solid rgba(220,38,38,0.2)", borderRadius: 12, padding: "12px 16px", marginBottom: 16, fontSize: 11, fontWeight: 700, color: "#f87171", textAlign: "center" }}>⚠️ {error}</div>}
          <button onClick={verify} disabled={loading} style={{ width: "100%", background: "#dc2626", color: "#fff", padding: 16, borderRadius: 16, fontWeight: 900, fontSize: 13, textTransform: "uppercase", letterSpacing: "0.1em", border: "none", cursor: loading ? "not-allowed" : "pointer", opacity: loading ? 0.7 : 1, transition: "all 0.2s" }}>
            {loading ? (
              <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2.5" style={{ animation: "spin 0.8s linear infinite" }}><path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" /></svg>
            ) : "Accéder au bot →"}
          </button>
          <div style={{ display: "flex", alignItems: "center", gap: 12, margin: "24px 0 20px" }}>
            <div style={{ flex: 1, height: 1, background: "rgba(255,255,255,0.05)" }} />
            <span style={{ fontSize: 10, color: "#333", fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.1em" }}>Pas encore membre ?</span>
            <div style={{ flex: 1, height: 1, background: "rgba(255,255,255,0.05)" }} />
          </div>
          <a href="https://whop.com/bot-de-luxe-ztracker" target="_blank" style={{ display: "block", textAlign: "center", padding: 13, border: "1px solid rgba(255,255,255,0.08)", borderRadius: 14, color: "#666", fontSize: 11, fontWeight: 800, textTransform: "uppercase", letterSpacing: "0.08em", textDecoration: "none" }}>
            Obtenir l'accès sur Whop →
          </a>
        </div>
        <p style={{ textAlign: "center", marginTop: 20, fontSize: 10, color: "#2a2a2a", fontWeight: 600 }}>Accès sécurisé · Licence personnelle · Non transférable</p>
      </div>
    </div>
  );
};

// ═══════════════════════════════════════════
// COMPOSANT : Sidebar
// ═══════════════════════════════════════════
const Sidebar = memo(({ isOpen, activeTab, onTab, isPaused, onTogglePause, onClearFeed, filterZero, onFilterZero, isLight, onTheme, liveHasNew, userPlan }) => (
  <div className={`sidebar ${isOpen ? "open" : ""} pt-24`}> {/* J'ai ajouté pt-16 ici pour baisser les onglets */}
    {[
      { id: "live", label: "📡 RADAR LIVE", badge: liveHasNew ? <span style={{ fontSize: 9, background: "rgba(220,38,38,0.9)", color: "#fff", padding: "2px 7px", borderRadius: 6, fontWeight: 900, letterSpacing: "0.05em", animation: "pulse-badge 1.5s ease-in-out infinite" }}>NEW</span> : null },
      { id: "filters", label: "⚙️ MES FILTRES" },
      { id: "saves", label: "❤️ MES SAUVEGARDES" },
      { 
        id: "boutique", 
        label: "🏪 MA BOUTIQUE", 
        badge: userPlan !== "bot_boutique" ? (
          <span style={{ fontSize: 9, background: "rgba(220,38,38,0.2)", color: "#ef4444", padding: "2px 7px", borderRadius: 6, fontWeight: 900, letterSpacing: "0.05em" }}>
            🔒 UPGRADE
          </span>
        ) : null 
      },
      { id: "banwords", label: "🚫 MOTS BANNIS" },
    ].map((t) => (
      <button key={t.id} onClick={() => onTab(t.id)} className={`tab-btn flex justify-between items-center ${activeTab === t.id ? "active" : ""}`} style={{ fontWeight: 400 }}>
        <span>{t.label}</span> {t.badge}
      </button>
    ))}
    <div className="mt-10 px-4 border-t border-white/10 pt-6">
      <h4 className="text-[10px] font-black text-gray-500 uppercase tracking-widest mb-4">Contrôle Flux</h4>
      <button onClick={onTogglePause} className={`w-full mb-3 bg-white/5 p-3 rounded-xl border border-white/5 text-[10px] font-black uppercase tracking-widest ${isPaused ? "text-green-400" : "text-blue-400"}`}>
        {isPaused ? "▶️ Reprendre le Feed" : "⏸️ Pause le Feed"}
      </button>
      <div className="flex items-center justify-between bg-white/5 p-3 rounded-xl border border-white/5 mb-3">
        <span className="text-[11px] font-bold text-gray-300">Masquer 0⭐</span>
        <label className="switch"><input type="checkbox" checked={filterZero} onChange={(e) => onFilterZero(e.target.checked)} /><span className="slider" /></label>
      </div>
      <div className="flex items-center justify-between bg-white/5 p-3 rounded-xl border border-white/5 mb-3">
        <span className="text-[11px] font-bold text-gray-300" id="theme-label">{isLight ? "🌙 Mode sombre" : "☀️ Mode clair"}</span>
        <label className="switch"><input type="checkbox" checked={isLight} onChange={(e) => onTheme(e.target.checked)} /><span className="slider" /></label>
      </div>
      <button onClick={onClearFeed} className="w-full bg-white/5 p-3 rounded-xl border border-white/5 text-[10px] font-black text-gray-500 hover:text-white hover:bg-red-600/20 transition uppercase tracking-widest">
        🗑️ Nettoyer le feed
      </button>
    </div>
  </div>
));

// ═══════════════════════════════════════════
// COMPOSANT : Tab RADAR LIVE
// ═══════════════════════════════════════════
const TabLive = memo(({ items, saves, boutique, banwords, filterZero, onOpen, onToggleSave, onToggleBoutique }) => {
  const [search, setSearch] = useState("");

  const filtered = useMemo(() => {
    const sq = search.toLowerCase();
    return items.filter((item) => {
      if (filterZero && (parseFloat(item.seller_stars) || 0) === 0) return false;
      const title = (item.title || "").toLowerCase();
      if (banwords.some((w) => title.includes(w.toLowerCase()))) return false;
      if (sq && !title.includes(sq) && !(item.filter_name || "").toLowerCase().includes(sq) && !(item.source_name || "").toLowerCase().includes(sq)) return false;
      return true;
    }).slice(0, 30);
  }, [items, search, filterZero, banwords]);

  return (
    <div id="tab-live" className="tab-content active">
      <div className="flex flex-wrap justify-between items-end gap-4 mb-8">
        <div>
          <h2 className="text-4xl font-black italic">RADAR <span className="text-red-600">LIVE</span></h2>
          <p className="text-xs text-gray-500 mt-2 uppercase tracking-widest font-bold">Flux en temps réel · {filtered.length} article{filtered.length !== 1 ? "s" : ""}</p>
        </div>
      </div>
      {/* Search */}
      <div style={{ position: "relative", marginBottom: 16 }}>
        <span style={{ position: "absolute", left: 14, top: "50%", transform: "translateY(-50%)", fontSize: 15 }}>🔍</span>
        <input type="text" value={search} onChange={(e) => setSearch(e.target.value)} placeholder="Rechercher dans le feed..." style={{ width: "100%", background: "rgba(255,255,255,0.04)", border: "1.5px solid rgba(255,255,255,0.08)", padding: "11px 14px 11px 42px", borderRadius: 12, outline: "none", color: "var(--text-primary)", fontSize: 12, fontWeight: 600, boxSizing: "border-box" }} />
        {search && <button onClick={() => setSearch("")} style={{ position: "absolute", right: 12, top: "50%", transform: "translateY(-50%)", background: "none", border: "none", color: "#555", cursor: "pointer", fontSize: 16 }}>✕</button>}
      </div>
      {filtered.length === 0 ? (
        <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill,minmax(240px,1fr))", gap: "2rem" }}>
          {[1, 2, 3].map((i) => <div key={i} className="skeleton-card"><div className="skeleton-img" /><div className="skeleton-line" /><div className="skeleton-line short" /></div>)}
        </div>
      ) : (
        <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8">
          {filtered.map((item) => (
            <ItemCard key={item.url} item={item} isSaved={saves.some((s) => s.url === item.url)} isBoutique={boutique.some((s) => s.url === item.url)} isBoutiqueTab={false} onOpen={onOpen} onToggleSave={onToggleSave} onToggleBoutique={onToggleBoutique} />
          ))}
        </div>
      )}
    </div>
  );
});

// ═══════════════════════════════════════════
// COMPOSANT : Tab SAUVEGARDES
// ═══════════════════════════════════════════
const TabSaves = memo(({ saves, boutique, onOpen, onToggleSave, onToggleBoutique }) => {
  const [search, setSearch] = useState("");
  const [sort, setSort] = useState("default");
  const [filterChip, setFilterChip] = useState(null);

  const filterNames = useMemo(() => [...new Set(saves.map((i) => i.filter_name).filter(Boolean))], [saves]);

  const sorted = useMemo(() => {
    const sq = search.toLowerCase();
    let list = saves.filter((i) => {
      if (filterChip && i.filter_name !== filterChip) return false;
      if (!sq) return true;
      return (i.title || "").toLowerCase().includes(sq) || (i.filter_name || "").toLowerCase().includes(sq) || (i.source_name || "").toLowerCase().includes(sq);
    });
    if (sort === "price-asc") list.sort((a, b) => (parsePriceVal(a.price_eur) ?? Infinity) - (parsePriceVal(b.price_eur) ?? Infinity));
    if (sort === "price-desc") list.sort((a, b) => (parsePriceVal(b.price_eur) ?? -Infinity) - (parsePriceVal(a.price_eur) ?? -Infinity));
    if (sort === "stars") list.sort((a, b) => (parseFloat(b.seller_stars) || 0) - (parseFloat(a.seller_stars) || 0));
    if (sort === "filter") list.sort((a, b) => (a.filter_name || "").localeCompare(b.filter_name || ""));
    if (sort === "source") list.sort((a, b) => (a.source_name || "").localeCompare(b.source_name || ""));
    return list;
  }, [saves, search, sort, filterChip]);

  const sortBtns = [
    { id: "default", label: "Par défaut" }, { id: "price-asc", label: "💶 Prix ↑" },
    { id: "price-desc", label: "💶 Prix ↓" }, { id: "stars", label: "⭐ Vendeur" },
    { id: "filter", label: "🎯 Filtre" }, { id: "source", label: "🏪 Source" }
  ];

  return (
    <div id="tab-saves" className="tab-content">
      <div className="flex justify-between items-end mb-8">
        <div>
          <h2 className="text-4xl font-black italic">MES <span className="text-red-600">SAUVEGARDES</span></h2>
          <p className="text-xs text-gray-500 mt-2 uppercase tracking-widest font-bold">{saves.length} article{saves.length !== 1 ? "s" : ""} sauvegardé{saves.length !== 1 ? "s" : ""}</p>
        </div>
      </div>
      <div className="bg-[#0a0a0a] border border-white/10 rounded-2xl p-4 mb-6 flex flex-col gap-3">
        <div style={{ position: "relative" }}>
          <span style={{ position: "absolute", left: 14, top: "50%", transform: "translateY(-50%)", fontSize: 15 }}>🔍</span>
          <input type="text" value={search} onChange={(e) => setSearch(e.target.value)} placeholder="Rechercher dans mes sauvegardes..." style={{ width: "100%", background: "rgba(255,255,255,0.04)", border: "1.5px solid rgba(255,255,255,0.08)", padding: "11px 14px 11px 42px", borderRadius: 12, outline: "none", color: "var(--text-primary)", fontSize: 12, fontWeight: 600, boxSizing: "border-box" }} />
        </div>
        <div className="flex flex-wrap gap-2 items-center">
          <span className="text-[9px] font-black text-gray-600 uppercase tracking-widest mr-1">Trier :</span>
          {sortBtns.map((b) => <button key={b.id} onClick={() => setSort(b.id)} className={`sort-btn ${sort === b.id ? "active" : ""}`}>{b.label}</button>)}
        </div>
        {filterNames.length > 0 && (
          <div className="flex flex-wrap gap-2">
            <span onClick={() => setFilterChip(null)} className={`boutique-tag ${filterChip === null ? "selected" : ""}`} style={{ cursor: "pointer" }}>Tous</span>
            {filterNames.map((n) => <span key={n} onClick={() => setFilterChip(n === filterChip ? null : n)} className={`boutique-tag ${filterChip === n ? "selected" : ""}`} style={{ cursor: "pointer" }}>{n}</span>)}
          </div>
        )}
      </div>
      <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8">
        {sorted.map((item) => <ItemCard key={item.url} item={item} isSaved={true} isBoutique={boutique.some((s) => s.url === item.url)} isBoutiqueTab={false} onOpen={onOpen} onToggleSave={onToggleSave} onToggleBoutique={onToggleBoutique} />)}
      </div>
    </div>
  );
});

// ═══════════════════════════════════════════
// COMPOSANT : Tab FILTRES
// ═══════════════════════════════════════════
const TabFilters = memo(({ filters, onAdd, onEdit, onDelete, onToggle }) => {
  const [showModal, setShowModal] = useState(false);
  const [editIdx, setEditIdx] = useState(-1);
  const [form, setForm] = useState({ name: "", brand: "", query: "", price_min: "", price_max: "", auto_save: false });

  const openAdd = () => { setEditIdx(-1); setForm({ name: "", brand: "", query: "", price_min: "", price_max: "", auto_save: false }); setShowModal(true); };
  const openEdit = (idx) => { setEditIdx(idx); setForm({ ...filters[idx] }); setShowModal(true); };
  
  const save = () => {
    if (!form.name || !form.query) { alert("Champs requis !"); return; }
    if (editIdx === -1) onAdd({ ...form, active: true });
    else onEdit(editIdx, form);
    setShowModal(false);
  };

  return (
    <div id="tab-filters" className="tab-content">
      {/* Header adaptable */}
      <div className="flex flex-col md:flex-row justify-between items-start md:items-end gap-4 mb-8">
        <div>
          <h2 className="text-3xl md:text-4xl font-black italic">MES <span className="text-red-600">FILTRES</span></h2>
          <p className="text-xs text-gray-500 mt-2 uppercase tracking-widest font-bold">
            {filters.length} filtre{filters.length !== 1 ? "s" : ""} actif{filters.length !== 1 ? "s" : ""}
          </p>
        </div>
        <button onClick={openAdd} className="w-full md:w-auto bg-red-600 text-white px-5 py-3 rounded-xl font-black text-xs hover:bg-red-700 transition shadow-lg shadow-red-600/20">
          + NOUVEAU FILTRE
        </button>
      </div>

      <div className="bg-[#0a0a0a] border border-white/10 rounded-3xl overflow-hidden shadow-2xl">
        <table className="w-full filter-table">
          <thead>
            <tr>
              <th>ON/OFF</th><th>NOM</th><th>MARQUE</th><th>LIEN</th><th>PRIX</th><th>AUTO-SAVE</th><th>ACTIONS</th>
            </tr>
          </thead>
          <tbody>
            {filters.map((f, idx) => (
              <tr key={idx}>
                {/* IMPORTANT: Chaque <td> a maintenant un data-label 
                  pour s'afficher correctement sur mobile ! 
                */}
                <td data-label="ON/OFF">
                  <label className="switch">
                    <input type="checkbox" checked={f.active} onChange={() => onToggle(idx)} />
                    <span className="slider" />
                  </label>
                </td>
                <td data-label="NOM" className="font-bold text-white italic">{f.name}</td>
                <td data-label="MARQUE">
                  <span className="bg-green-500/10 text-green-400 px-3 py-1 rounded-full text-[10px] font-black border border-green-500/20">
                    {f.brand}
                  </span>
                </td>
                <td data-label="LIEN" className="text-gray-500 font-mono text-[10px]">
                  <a href={`https://zenmarket.jp/fr/mercari.aspx?q=${f.query.replace(" ", "%2B")}`} target="_blank" className="hover:text-red-500 flex items-center gap-1 md:inline">
                    🔗 <span className="underline">Mercari</span>
                  </a>
                </td>
                <td data-label="PRIX" className="font-bold text-white">{f.price_min || 0}€ - {f.price_max || "∞"}€</td>
                <td data-label="AUTO-SAVE">
                  {f.auto_save ? (
                    <span className="text-[9px] font-black bg-red-600/15 text-red-500 border border-red-500/30 px-2 py-1 rounded-md uppercase">❤️ ON</span>
                  ) : (
                    <span className="text-[9px] font-bold text-gray-600 uppercase">OFF</span>
                  )}
                </td>
                <td data-label="ACTIONS" className="text-right space-x-4">
                  <button onClick={() => openEdit(idx)} className="text-lg hover:scale-120 transition inline-block">✏️</button>
                  <button onClick={() => { if (confirm("Supprimer ?")) onDelete(idx); }} className="text-lg hover:scale-120 transition inline-block">🗑️</button>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>

      {/* Modal - Largeur responsive déjà gérée par le CSS */}
      {showModal && (
        <div className="modal-bg p-4" style={{ display: "flex" }} onClick={(e) => { if (e.target === e.currentTarget) setShowModal(false); }}>
          <div className="bg-[#0a0a0a] border border-white/10 p-6 md:p-8 rounded-3xl w-full max-w-md shadow-2xl animate-pop" onClick={(e) => e.stopPropagation()}>
            <h2 className="text-xl font-black mb-6 italic uppercase text-white">
              {editIdx === -1 ? "Nouvelle Cible" : "Modifier le Filtre"}
            </h2>
            <div className="space-y-4">
              {[["name", "Nom du filtre"], ["brand", "Marque"], ["query", "Mots-clés"]].map(([k, p]) => (
                <div key={k}>
                  <p className="text-[9px] font-black text-gray-500 uppercase tracking-widest mb-1.5 ml-1">{p}</p>
                  <input type="text" placeholder={p} value={form[k]} onChange={(e) => setForm({ ...form, [k]: e.target.value })} className="w-full bg-white/5 border border-white/10 p-4 rounded-xl outline-none text-white focus:border-red-600/50 transition" />
                </div>
              ))}
              <div className="grid grid-cols-2 gap-4">
                <div>
                  <p className="text-[9px] font-black text-gray-500 uppercase tracking-widest mb-1.5 ml-1">Prix Min €</p>
                  <input type="number" value={form.price_min} onChange={(e) => setForm({ ...form, price_min: e.target.value })} className="w-full bg-white/5 border border-white/10 p-4 rounded-xl outline-none text-white focus:border-red-600/50 transition" />
                </div>
                <div>
                  <p className="text-[9px] font-black text-gray-500 uppercase tracking-widest mb-1.5 ml-1">Prix Max €</p>
                  <input type="number" value={form.price_max} onChange={(e) => setForm({ ...form, price_max: e.target.value })} className="w-full bg-white/5 border border-white/10 p-4 rounded-xl outline-none text-white focus:border-red-600/50 transition" />
                </div>
              </div>
              <div className="flex items-center justify-between bg-white/5 border border-white/10 p-4 rounded-xl mt-2">
                <div>
                  <p className="text-white text-xs font-black uppercase tracking-wider">❤️ Sauvegarde automatique</p>
                  <p className="text-gray-500 text-[10px] mt-0.5">Directement en Sauvegardes</p>
                </div>
                <label className="switch ml-4"><input type="checkbox" checked={form.auto_save} onChange={(e) => setForm({ ...form, auto_save: e.target.checked })} /><span className="slider" /></label>
              </div>
              <div className="flex gap-4 pt-4">
                <button onClick={save} className="flex-1 bg-red-600 py-4 rounded-xl font-black uppercase text-xs text-white hover:bg-red-700 transition">Enregistrer</button>
                <button onClick={() => setShowModal(false)} className="flex-1 bg-white/5 py-4 rounded-xl font-black uppercase text-xs text-gray-400 hover:bg-white/10 transition">Annuler</button>
              </div>
            </div>
          </div>
        </div>
      )}
    </div>
  );
});

// ═══════════════════════════════════════════
// COMPOSANT : Tab BOUTIQUE
// ═══════════════════════════════════════════
const TabBoutique = memo(({ boutique, userPlan, licenseKey, onOpen, onToggleSave, onToggleBoutique, onSavePrice, showNotification, saves }) => {
  const [sort, setSort] = useState("none");
  const [tagFilter, setTagFilter] = useState(null);
  const [configured, setConfigured] = useState(true);
  const [showSetup, setShowSetup] = useState(false);
  const [setupForm, setSetupForm] = useState({ github_token: "", github_repo: "", shop_name: "", instagram: "", contact_email: "" });
  const [setupError, setSetupError] = useState("");
  const [setupOk, setSetupOk] = useState("");
  const [publishing, setPublishing] = useState(false);

  const isLocked = userPlan === "bot_only";

  const allTags = useMemo(() => {
    const s = new Set();
    boutique.forEach((i) => (i.tags || []).forEach((t) => s.add(t)));
    return [...s];
  }, [boutique]);

  const sorted = useMemo(() => {
    let list = tagFilter ? boutique.filter((i) => (i.tags || []).includes(tagFilter)) : [...boutique];
    if (sort === "profit") list.sort((a, b) => (calcProfit(b).profit ?? -Infinity) - (calcProfit(a).profit ?? -Infinity));
    if (sort === "mult") list.sort((a, b) => (calcProfit(b).mult ?? -Infinity) - (calcProfit(a).mult ?? -Infinity));
    return list;
  }, [boutique, sort, tagFilter]);

  const pub = boutique.filter((i) => (i.visibility || "public") === "public").length;

  const publishBoutique = async () => {
    setPublishing(true);
    try {
      const r = await fetch("/publish_boutique", { method: "POST", headers: authHeaders(licenseKey) });
      const data = await r.json();
      if (r.status === 400) showNotification("⚙️ Configure d'abord GitHub dans ⚙️ CONFIG", "warning");
      else if (r.ok) showNotification(`🌐 Boutique publiée — ${data.published} article(s) en ligne`, "success");
      else showNotification(`❌ Erreur GitHub : ${data.detail || "inconnue"}`, "warning");
    } catch { showNotification("❌ Serveur injoignable", "warning"); }
    setPublishing(false);
  };

  const openSetup = async () => {
    setShowSetup(true); setSetupError(""); setSetupOk("");
    try {
      const r = await fetch("/get_boutique_config", { headers: authHeaders(licenseKey) });
      const cfg = await r.json();
      setSetupForm({ github_token: "", github_repo: cfg.github_repo || "", shop_name: cfg.shop_name || "", instagram: cfg.instagram || "", contact_email: cfg.contact_email || "" });
    } catch {}
  };

  const saveSetup = async () => {
    setSetupError(""); setSetupOk("");
    if (!setupForm.github_repo) { setSetupError("⚠️ Le dépôt GitHub est obligatoire."); return; }
    const payload = { github_repo: setupForm.github_repo, shop_name: setupForm.shop_name, instagram: setupForm.instagram, contact_email: setupForm.contact_email };
    if (setupForm.github_token) payload.github_token = setupForm.github_token;
    try {
      const r = await fetch("/update_boutique_config", { method: "POST", headers: authHeaders(licenseKey), body: JSON.stringify(payload) });
      const d = await r.json();
      if (r.status === 403) { setSetupError("⚠️ Plan insuffisant — upgrade requis."); return; }
      setConfigured(d.configured === true);
      setSetupOk("✅ Configuration sauvegardée !");
      showNotification("✅ Boutique configurée avec succès", "success");
      if (d.configured) setTimeout(() => setShowSetup(false), 2000);
    } catch { setSetupError("⚠️ Erreur réseau."); }
  };

  return (
    <div id="tab-boutique" className="tab-content" style={{ position: "relative" }}>
      {isLocked && (
        <div className="boutique-locked-overlay" style={{ display: "flex" }}>
          <div style={{ fontSize: 48 }}>🔒</div>
          <div style={{ textAlign: "center" }}>
            <p style={{ fontWeight: 900, fontSize: 18, color: "#fff", margin: "0 0 8px" }}>Fonctionnalité réservée</p>
            <p style={{ fontSize: 13, color: "#666", maxWidth: 320, lineHeight: 1.6, margin: "0 0 20px" }}>La boutique est incluse dans l'abonnement <span style={{ color: "#dc2626", fontWeight: 700 }}>Bot + Boutique</span>.</p>
            <a href="https://whop.com/bot-de-luxe-ztracker" target="_blank" style={{ display: "inline-block", background: "#dc2626", color: "#fff", padding: "12px 28px", borderRadius: 14, fontWeight: 900, fontSize: 11, textTransform: "uppercase", textDecoration: "none" }}>⬆️ Upgrader mon plan</a>
          </div>
        </div>
      )}

      {/* Setup overlay */}
      {showSetup && (
        <div style={{ position: "fixed", inset: 0, zIndex: 500, background: "rgba(5,5,5,0.97)", backdropFilter: "blur(12px)", display: "flex", alignItems: "center", justifyContent: "center", padding: 24 }}>
          <div style={{ width: "100%", maxWidth: 680, background: "#0a0a0a", border: "1px solid rgba(255,255,255,0.07)", borderRadius: 28, padding: 32, overflowY: "auto", maxHeight: "90vh", position: "relative" }}>
            <button onClick={() => setShowSetup(false)} style={{ position: "absolute", top: 16, right: 16, background: "rgba(255,255,255,0.05)", border: "1px solid rgba(255,255,255,0.1)", borderRadius: "50%", width: 36, height: 36, color: "#888", cursor: "pointer" }}>✕</button>
            <h2 style={{ fontSize: 22, fontWeight: 900, color: "#fff", margin: "0 0 6px", textTransform: "uppercase" }}>Configuration de ta <span style={{ color: "#dc2626" }}>Boutique</span></h2>
            <p style={{ fontSize: 12, color: "#555", marginBottom: 20 }}>Relie ton dépôt GitHub pour publier tes articles en ligne</p>
            <div style={{ display: "flex", flexDirection: "column", gap: 12, marginBottom: 16 }}>
              {[["password", "github_token", "Token GitHub *", "ghp_xxxxxxxxxxxxxxxx"], ["text", "github_repo", "Dépôt GitHub *", "mon-compte/ma-boutique"], ["text", "shop_name", "Nom de la boutique", "Ma Boutique Luxe"], ["text", "instagram", "Instagram (optionnel)", "@ma_boutique"], ["email", "contact_email", "Email de contact", "contact@maboutique.fr"]].map(([type, k, label, ph]) => (
                <div key={k}>
                  <label style={{ fontSize: 9, fontWeight: 900, textTransform: "uppercase", letterSpacing: "0.12em", color: "#555", display: "block", marginBottom: 6 }}>{label}</label>
                  <input type={type} placeholder={ph} value={setupForm[k]} onChange={(e) => setSetupForm({ ...setupForm, [k]: e.target.value })} style={{ width: "100%", background: "rgba(255,255,255,0.04)", border: "1.5px solid rgba(255,255,255,0.08)", padding: "13px 16px", borderRadius: 14, outline: "none", color: "#fff", fontSize: 13, boxSizing: "border-box" }} />
                </div>
              ))}
            </div>
            {setupError && <div style={{ background: "rgba(220,38,38,0.08)", border: "1px solid rgba(220,38,38,0.2)", borderRadius: 12, padding: "12px 16px", marginBottom: 12, fontSize: 11, fontWeight: 700, color: "#f87171" }}>{setupError}</div>}
            {setupOk && <div style={{ background: "rgba(34,197,94,0.08)", border: "1px solid rgba(34,197,94,0.2)", borderRadius: 12, padding: "12px 16px", marginBottom: 12, fontSize: 11, fontWeight: 700, color: "#4ade80" }}>{setupOk}</div>}
            <button onClick={saveSetup} style={{ width: "100%", background: "#dc2626", color: "#fff", padding: 16, borderRadius: 16, fontWeight: 900, fontSize: 12, textTransform: "uppercase", border: "none", cursor: "pointer" }}>✓ Enregistrer la configuration</button>
          </div>
        </div>
      )}

      <div className="flex flex-wrap justify-between items-end gap-4 mb-6">
        <div>
          <h2 className="text-4xl font-black italic">MA <span className="text-red-600">BOUTIQUE</span></h2>
          <p className="text-xs text-gray-500 mt-2 uppercase tracking-widest font-bold">
            {pub} public · <span className="text-red-400">{boutique.length - pub} privé{boutique.length - pub !== 1 ? "s" : ""}</span>
          </p>
        </div>
        <div className="flex gap-3 flex-wrap items-center">
          <div className="flex gap-2">
            {[["none", "Par défaut"], ["profit", "💰 Bénéfice"], ["mult", "✕ Multiplicateur"]].map(([id, label]) => (
              <button key={id} onClick={() => setSort(id)} className={`sort-btn ${sort === id ? "active" : ""}`}>{label}</button>
            ))}
          </div>
          <button onClick={publishBoutique} disabled={publishing} className="bg-green-600 text-white px-5 py-2.5 rounded-xl font-black text-xs hover:bg-green-700 transition">
            {publishing ? "⏳ Publication..." : "🌐 PUBLIER"}
          </button>
          <button onClick={openSetup} className="bg-white/5 border border-white/10 text-gray-400 px-4 py-2.5 rounded-xl font-black text-xs hover:bg-white/10 transition">⚙️ CONFIG</button>
        </div>
      </div>

      {allTags.length > 0 && (
        <div className="flex flex-wrap gap-2 mb-6">
          <span onClick={() => setTagFilter(null)} className={`boutique-tag ${tagFilter === null ? "selected" : ""}`} style={{ cursor: "pointer" }}>Tous</span>
          {allTags.map((t) => <span key={t} onClick={() => setTagFilter(t === tagFilter ? null : t)} className={`boutique-tag ${tagFilter === t ? "selected" : ""}`} style={{ cursor: "pointer" }}>{t}</span>)}
        </div>
      )}

      <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8">
        {sorted.map((item) => <ItemCard key={item.url} item={item} isSaved={saves.some((s) => s.url === item.url)} isBoutique={true} isBoutiqueTab={true} onOpen={onOpen} onToggleSave={onToggleSave} onToggleBoutique={onToggleBoutique} />)}
      </div>
    </div>
  );
});

// ═══════════════════════════════════════════
// COMPOSANT : Tab MOTS BANNIS
// ═══════════════════════════════════════════
const TabBanwords = memo(({ banwords, onAdd, onRemove }) => {
  const [input, setInput] = useState("");
  const add = () => {
    const w = input.trim();
    if (!w || banwords.some((b) => b.toLowerCase() === w.toLowerCase())) { setInput(""); return; }
    onAdd(w); setInput("");
  };
  return (
    <div id="tab-banwords" className="tab-content">
      <div className="flex justify-between items-end mb-8">
        <div>
          <h2 className="text-4xl font-black italic">MOTS <span className="text-red-600">BANNIS</span></h2>
          <p className="text-xs text-gray-500 mt-2 uppercase tracking-widest font-bold">Les articles contenant ces mots n'apparaîtront pas dans le feed</p>
        </div>
      </div>
      <div className="bg-[#0a0a0a] border border-white/10 rounded-3xl overflow-hidden shadow-2xl">
        <div className="p-6 border-b border-white/5 flex gap-3">
          <input type="text" value={input} onChange={(e) => setInput(e.target.value)} onKeyDown={(e) => e.key === "Enter" && add()} placeholder="Ajouter un mot banni (ex: DVD)" className="flex-1 bg-white/5 border border-white/10 px-4 py-3 rounded-xl outline-none text-white text-sm font-bold" />
          <button onClick={add} className="bg-red-600 text-white px-6 py-3 rounded-xl font-black text-xs hover:bg-red-700 transition uppercase">+ Ajouter</button>
        </div>
        <div className="p-6 flex flex-wrap gap-3 min-h-[80px]">
          {banwords.length === 0 ? <span className="text-gray-600 text-xs italic self-center">Aucun mot banni pour l'instant.</span>
            : banwords.map((w, i) => (
              <span key={i} className="banword-tag">🚫 {w}
                <button onClick={() => onRemove(i)} style={{ color: "#666", fontSize: 14, lineHeight: 1, transition: "color 0.15s", background: "none", border: "none", cursor: "pointer", padding: 0 }}>✕</button>
              </span>
            ))}
        </div>
      </div>
    </div>
  );
});

// ═══════════════════════════════════════════
// COMPOSANT PRINCIPAL
// ═══════════════════════════════════════════
function App() {
  const [licenseKey, setLicenseKey] = useState(() => localStorage.getItem("zentracker-licence-key") || "");
  const [unlocked, setUnlocked] = useState(false);
  const [userPlan, setUserPlan] = useState("bot_only");

  const [activeTab, setActiveTab] = useState("live");
  const [sidebarOpen, setSidebarOpen] = useState(false);
  const [isLight, setIsLight] = useState(() => localStorage.getItem("zentracker-theme") === "light");
  const [isPaused, setIsPaused] = useState(false);
  const [filterZero, setFilterZero] = useState(false);
  const [liveHasNew, setLiveHasNew] = useState(false);

  const [allItems, setAllItems] = useState([]);
  const [saves, setSaves] = useState([]);
  const [boutique, setBoutique] = useState([]);
  const [filters, setFilters] = useState([]);
  const [banwords, setBanwords] = useState([]);

  const [modalItem, setModalItem] = useState(null);
  const [modalIsBoutique, setModalIsBoutique] = useState(false);

  const priceHistory = useRef({});

  // 1. D'abord les notifications (car on en a besoin plus bas)
const { notifs, show: showNotification, dismiss: dismissNotif, toggle: toggleNotifs, enabled: notifsEnabled } = useNotifications();

// 2. Ensuite la sauvegarde (car on en a besoin plus bas aussi)
const syncSaves = useCallback(async (s) => { 
    await fetch("/update_saves", { 
        method: "POST", 
        headers: authHeaders(licenseKey), 
        body: JSON.stringify(s) 
    }); 
}, [licenseKey]);

// 3. Enfin, le Toggle qui rassemble les deux !
const handleToggleSave = useCallback(async (item) => {
    setSaves((prev) => {
      const exists = prev.some((s) => s.url === item.url);
      const next = exists ? prev.filter((s) => s.url !== item.url) : [item, ...prev];
      
      syncSaves(next);
      
      if (typeof showNotification === 'function') {
          showNotification(exists ? "💔 Retiré des favoris" : "❤️ Ajouté aux favoris", exists ? "warning" : "success");
      }
      return next;
    });
}, [syncSaves, showNotification]);

  // ── Thème ──
  useEffect(() => {
    document.body.classList.toggle("light-mode", isLight);
    localStorage.setItem("zentracker-theme", isLight ? "light" : "dark");
  }, [isLight]);

  // ── Auto-check licence stockée ──
  useEffect(() => {
    const key = localStorage.getItem("zentracker-licence-key");
    if (!key) return;
    fetch("/check_stored_licence", { headers: { "X-License-Key": key } })
      .then((r) => r.json())
      .then((data) => {
        if (data.valid) { setLicenseKey(key); setUserPlan(data.plan || "bot_only"); setUnlocked(true); }
        else localStorage.removeItem("zentracker-licence-key");
      }).catch(() => {});
  }, []);

  // ── Chargement initial après unlock ──
  useEffect(() => {
    if (!unlocked) return;
    const h = authHeaders(licenseKey);
    Promise.all([
      fetch("/get_filters", { headers: h }).then((r) => r.json()).catch(() => []),
      fetch("/get_saves", { headers: h }).then((r) => r.json()).catch(() => []),
      fetch("/get_banwords", { headers: h }).then((r) => r.json()).catch(() => []),
    ]).then(([f, s, b]) => {
      setFilters(Array.isArray(f) ? f : []);
      setSaves(Array.isArray(s) ? s : []);
      setBanwords(Array.isArray(b) ? b : []);
    });
    fetch("/get_boutique", { headers: h }).then(async (r) => {
      if (r.status === 403) return;
      const data = await r.json();
      setBoutique(Array.isArray(data) ? data : []);
    }).catch(() => {});
  }, [unlocked, licenseKey]);

  // ── WebSocket message handler ──
  // ── WebSocket message handler ──
  const onWsMessage = useCallback(async (e) => {
  if (isPaused) return;

  try {
    const parsed = JSON.parse(e.data);

    // CAS 1 : Historique complet (Tableau)
    if (Array.isArray(parsed)) {
      // On prend les 30 derniers de l'historique reçu
      setAllItems(parsed.slice(-30).reverse());
      return;
    }

    // CAS 2 : Un nouvel article unique (Objet ou {item: ...})
    const item = parsed.type === "new_item" ? parsed.item : parsed;

    if (item && item.url) {
      // Gestion de l'historique des prix
      if (item.price_eur && item.price_eur !== "N/A") {
        const prev = priceHistory.current[item.url];
        const curr = parsePriceVal(item.price_eur);
        if (prev !== undefined) {
          const prevVal = parsePriceVal(prev);
          // On ignore les variations de centimes liées aux taux de change ZenMarket
          if (curr !== null && prevVal !== null && (prevVal/curr >= 10)) {
            item._prev_price = prev;
          }
        }
        priceHistory.current[item.url] = item.price_eur;
      }

      // MISE À JOUR DU FEED (Accumulation)
      // MISE À JOUR DU FEED (Accumulation et Fast-Track)
      setAllItems((prev) => {
        // 1. On cherche l'index de l'article s'il existe déjà
        const existingIndex = prev.findIndex((ex) => ex.url === item.url);

        if (existingIndex !== -1) {
          // ✅ L'ARTICLE EXISTE DÉJÀ (Mise à jour HD)
          // On le met à jour silencieusement sans notification
          const updatedList = [...prev];
          updatedList[existingIndex] = { ...updatedList[existingIndex], ...item };
          return updatedList;
        } else {
          // 🆕 NOUVEL ARTICLE (Alerte rapide)
          // On l'ajoute au début et on garde les 30 derniers
          const updatedList = [item, ...prev].slice(0, 30);
          
          // Notifications UNIQUEMENT pour les nouveaux articles
          showNotification(`🎯 <strong>Nouveau match</strong><br><span style="opacity:0.7;font-size:10px;">${item.source_name} · ${item.price_eur}</span>`, "default");
          setLiveHasNew(true);
          
          return updatedList;
        }
      });

      // Logique Auto-save (reste inchangée)
      const isAutoSave = filters.some((f) => f.auto_save && item.title.toLowerCase().includes(f.query.toLowerCase()));
      if (isAutoSave) {
        handleToggleSave(item);
      }
    }
  } catch (err) {
    console.error("Erreur de traitement WS:", err);
  }
}, [isPaused, filters, handleToggleSave, showNotification]);


  const connStatus = useWebSocket(unlocked ? licenseKey : null, onWsMessage);

  // ── Sync helpers ──
  
  const syncBoutique = useCallback(async (b) => {
  const r = await fetch("/update_boutique", { method: "POST", headers: authHeaders(licenseKey), body: JSON.stringify(b) });
    if (r.status === 403) { setBoutique([]); }
  }, [licenseKey]);
  const syncFilters = useCallback(async (f) => { await fetch("/update_filters", { method: "POST", headers: authHeaders(licenseKey), body: JSON.stringify(f) }); }, [licenseKey]);
  const syncBanwords = useCallback(async (b) => { await fetch("/update_banwords", { method: "POST", headers: authHeaders(licenseKey), body: JSON.stringify(b) }); }, [licenseKey]);

  // ── Handlers ──
  const handleUnlock = (plan, key) => { setLicenseKey(key); setUserPlan(plan || "bot_only"); setUnlocked(true); };

  const handleTab = (tab) => {
    setActiveTab(tab);
    if (tab === "live") setLiveHasNew(false);
    if (window.innerWidth < 768) setSidebarOpen(false);
  };

  

  const handleToggleBoutique = useCallback(async (item) => {
    setBoutique((prev) => {
      const exists = prev.some((s) => s.url === item.url);
      const next = exists ? prev.filter((s) => s.url !== item.url) : [item, ...prev];
      syncBoutique(next);
      showNotification(exists ? "🏪 Retiré de la boutique" : "🏪 Ajouté à la boutique", exists ? "warning" : "success");
      return next;
    });
  }, [syncBoutique, showNotification]);

  const handleSavePrice = useCallback(async (url, newPrice) => {
    setBoutique((prev) => {
      const next = prev.map((i) => i.url === url ? { ...i, custom_price: newPrice } : i);
      syncBoutique(next);
      return next;
    });
  }, [syncBoutique]);

  const openModal = useCallback((item, isBoutiqueTab) => {
    setModalItem(item);
    setModalIsBoutique(!!isBoutiqueTab);
  }, []);

  // PWA init
  useEffect(() => {
    const manifest = { name: "ZenTracker ELITE", short_name: "ZenTracker", display: "standalone", background_color: "#050505", theme_color: "#dc2626", start_url: "./", icons: [{ src: generateIconDataUrl(192), sizes: "192x192", type: "image/png" }], lang: "fr" };
    const blob = new Blob([JSON.stringify(manifest)], { type: "application/json" });
    const el = document.querySelector('link[rel="manifest"]');
    if (el) el.href = URL.createObjectURL(blob);
  }, []);

  const connLabel = connStatus === "open" ? "SERVEUR ACTIF" : connStatus === "connecting" ? "En connexion..." : "RECONNEXION...";
  const connColor = connStatus === "open" ? "#22c55e" : connStatus === "connecting" ? "#3b82f6" : "#ef4444";

  if (!unlocked) return <LicenceGate onUnlock={handleUnlock} />;

  const isSavedModal = modalItem ? saves.some((s) => s.url === modalItem.url) : false;
  const isBoutiqueModal = modalItem ? boutique.some((s) => s.url === modalItem.url) : false;

  return (
    <div className="p-4 md:p-10">
      {/* Sidebar toggle (Forcé au premier plan ABOSLU) */}
      <button 
        onClick={() => setSidebarOpen((v) => !v)} 
        className="fixed top-4 left-4 md:top-8 md:left-8 z-[9999] bg-[#0a0a0a] md:bg-white/5 p-3 rounded-xl md:rounded-full border border-white/10 hover:bg-white/10 transition shadow-xl"
        style={{ zIndex: "9999 !important" }} 
      >
        {sidebarOpen ? (
          <svg width="24" height="24" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" /></svg>
        ) : (
          <svg width="24" height="24" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 6h16M4 12h16M4 18h16" /></svg>
        )}
      </button>

      {/* Overlay qui assombrit le fond quand le menu est ouvert (optionnel mais recommandé) */}
      {sidebarOpen && (
        <div 
          className="fixed inset-0 z-[990] bg-black/60 backdrop-blur-sm md:hidden"
          onClick={() => setSidebarOpen(false)} 
        />
      )}
      {/* Conn status */}
      <div className="fixed top-8 right-8 z-[60] flex items-center gap-3 bg-white/5 px-4 py-2.5 rounded-full border border-white/10 shadow-lg">
        <div style={{ width: 10, height: 10, borderRadius: "50%", background: connColor, animation: "pulse 2s ease-in-out infinite" }} />
        <span className="text-[10px] font-black text-gray-300 uppercase tracking-widest">{connLabel}</span>
      </div>

      {/* Notif bell */}
      <button onClick={toggleNotifs} title="Notifications" style={{ position: "fixed", top: 32, right: 200, zIndex: 61, background: "rgba(255,255,255,0.05)", border: "1px solid rgba(255,255,255,0.1)", borderRadius: "50%", width: 42, height: 42, display: "flex", alignItems: "center", justifyContent: "center", cursor: "pointer", transition: "0.2s" }}>
        <span style={{ fontSize: 18 }}>{notifsEnabled ? "🔔" : "🔕"}</span>
        {notifs.length > 0 && <span style={{ position: "absolute", top: 4, right: 4, background: "#dc2626", color: "#fff", fontSize: 8, fontWeight: 900, borderRadius: "50%", width: 14, height: 14, lineHeight: "14px", textAlign: "center" }}>{notifs.length}</span>}
      </button>

      {/* Notifications */}
      <div id="notif-container">
        {notifs.map((n) => <NotifToast key={n.id} notif={n} onDismiss={dismissNotif} />)}
      </div>

      {/* Sidebar */}
      {sidebarOpen && <div style={{ position: "fixed", inset: 0, zIndex: 49 }} onClick={() => setSidebarOpen(false)} />}
      <Sidebar 
        isOpen={sidebarOpen} 
        activeTab={activeTab} 
        onTab={handleTab} 
        isPaused={isPaused} 
        onTogglePause={() => setIsPaused((v) => !v)} 
        onClearFeed={() => { if (confirm("Vider le feed en cours ?")) { setAllItems([]); priceHistory.current = {}; } }} 
        filterZero={filterZero} 
        onFilterZero={setFilterZero} 
        isLight={isLight} 
        onTheme={setIsLight} 
        liveHasNew={liveHasNew} 
        
        /* 👇 AJOUTE JUSTE CETTE LIGNE 👇 */
        userPlan={userPlan} 
      />
      
      {/* Main content */}
      {/* Main content */}
      <div className="pt-20 md:pt-12" style={{ marginLeft: 0 }}>
        {activeTab === "live" && (
          <TabLive items={allItems} saves={saves} boutique={boutique} banwords={banwords} filterZero={filterZero} onOpen={openModal} onToggleSave={handleToggleSave} onToggleBoutique={handleToggleBoutique} />
        )}
        {activeTab === "saves" && (
          <TabSaves saves={saves} boutique={boutique} onOpen={openModal} onToggleSave={handleToggleSave} onToggleBoutique={handleToggleBoutique} />
        )}
        {activeTab === "filters" && (
          <TabFilters filters={filters} onAdd={(f) => { const n = [...filters, f]; setFilters(n); syncFilters(n); }} onEdit={(idx, f) => { const n = filters.map((v, i) => i === idx ? f : v); setFilters(n); syncFilters(n); }} onDelete={(idx) => { const n = filters.filter((_, i) => i !== idx); setFilters(n); syncFilters(n); }} onToggle={(idx) => { const n = filters.map((v, i) => i === idx ? { ...v, active: !v.active } : v); setFilters(n); syncFilters(n); }} />
        )}
        {activeTab === "boutique" && (
          <TabBoutique boutique={boutique} saves={saves} userPlan={userPlan} licenseKey={licenseKey} onOpen={openModal} onToggleSave={handleToggleSave} onToggleBoutique={handleToggleBoutique} onSavePrice={handleSavePrice} showNotification={showNotification} />
        )}
        {activeTab === "banwords" && (
          <TabBanwords banwords={banwords} onAdd={(w) => { const n = [...banwords, w]; setBanwords(n); syncBanwords(n); }} onRemove={(i) => { const n = banwords.filter((_, j) => j !== i); setBanwords(n); syncBanwords(n); }} />
        )}
      </div>

      {/* Modal */}
      {modalItem && (
        <ItemModal item={modalItem} isBoutiqueTab={modalIsBoutique} isSaved={isSavedModal} isBoutique={isBoutiqueModal} onClose={() => setModalItem(null)} onToggleSave={handleToggleSave} onToggleBoutique={handleToggleBoutique} onSavePrice={handleSavePrice} licenseKey={licenseKey} />
      )}
    </div>
  );
}
  const root = ReactDOM.createRoot(document.getElementById('root'));
  root.render(<App />);
