/* ClientBase — In-app dashboard (fully functional) */

// Per-module accent colors (OKLCH) — used in sidebar, topbar title, and module-specific highlights
const MODULE_TONES = {
  accueil:      { bg: "oklch(95% 0.06 268)",  ink: "oklch(38% 0.18 268)", solid: "oklch(55% 0.22 268)" }, // violet
  agenda:       { bg: "oklch(95% 0.05 30)",   ink: "oklch(42% 0.14 30)",  solid: "oklch(60% 0.17 30)"  }, // orange/coral
  bookings:     { bg: "oklch(94% 0.06 200)",  ink: "oklch(38% 0.12 200)", solid: "oklch(55% 0.15 200)" }, // teal
  clients:      { bg: "oklch(95% 0.05 340)",  ink: "oklch(42% 0.14 340)", solid: "oklch(60% 0.17 340)" }, // pink
  fidelite:     { bg: "oklch(95% 0.06 85)",   ink: "oklch(42% 0.14 85)",  solid: "oklch(62% 0.14 85)"  }, // gold
  stats:        { bg: "oklch(94% 0.05 160)",  ink: "oklch(38% 0.1 160)",  solid: "oklch(55% 0.12 160)" }, // sage/green
  factures:     { bg: "oklch(94% 0.05 240)",  ink: "oklch(40% 0.14 240)", solid: "oklch(55% 0.16 240)" }, // blue
  stock:        { bg: "oklch(95% 0.06 50)",   ink: "oklch(42% 0.14 50)",  solid: "oklch(60% 0.15 50)"  }, // amber
  subscription: { bg: "oklch(95% 0.06 300)",  ink: "oklch(40% 0.18 300)", solid: "oklch(55% 0.22 300)" }, // magenta
  settings:     { bg: "oklch(94% 0.008 260)", ink: "oklch(38% 0.012 260)",solid: "oklch(50% 0.012 260)" }, // neutral
};

const APP_MODULES = [
  { id: "accueil",      icon: "sparkle",  label: "Accueil" },
  { id: "agenda",       icon: "calendar", label: "Agenda" },
  { id: "services",     icon: "sparkle",  label: "Prestations" },
  { id: "bookings",     icon: "link",     label: "Prise de RDV" },
  { id: "clients",      icon: "users",    label: "Clients" },
  { id: "fidelite",     icon: "gift",     label: "Fidélité" },
  { id: "stats",        icon: "chart",    label: "Statistiques" },
  { id: "factures",     icon: "invoice",  label: "Facturation" },
  { id: "stock",        icon: "box",      label: "Stock" },
  { id: "subscription", icon: "zap",      label: "Abonnement & nouveautés" },
];

// Code couleur discret — juste assez pour se repérer.
// `dot` : pastille colorée à côté du label de groupe (~6 px)
// `label` : couleur du texte du label (CAPS) — version douce du dot
// Le reste de l'UI (cartes, fonds) reste blanc/gris neutre.
const CB_TONE = {
  activity: { dot: "var(--accent)",       label: "var(--accent-ink)"     }, // indigo
  clients:  { dot: "oklch(60% 0.16 340)", label: "oklch(45% 0.13 340)"   }, // rose
  business: { dot: "oklch(60% 0.13 160)", label: "oklch(40% 0.10 160)"   }, // sage
  account:  { dot: "oklch(58% 0.16 290)", label: "oklch(45% 0.13 290)"   }, // lavande
  legal:    { dot: "oklch(58% 0.16 240)", label: "oklch(42% 0.13 240)"   }, // bleu
  contact:  { dot: "oklch(58% 0.13 200)", label: "oklch(42% 0.10 200)"   }, // teal
  data:     { dot: "oklch(60% 0.15 50)",  label: "oklch(45% 0.12 50)"    }, // peach
};

const APP_MODULE_GROUPS = [
  { label: "Mon activité",  ids: ["accueil", "agenda", "services", "bookings"], tone: CB_TONE.activity },
  { label: "Ma clientèle",  ids: ["clients", "fidelite"],                       tone: CB_TONE.clients  },
  { label: "Mon business",  ids: ["stats", "factures", "stock"],                tone: CB_TONE.business },
  { label: "Mon compte",    ids: ["subscription"],                              tone: CB_TONE.account  },
];

// Badge vérifié inspiré de Meta/Instagram — sunburst scalloped + check blanc
const VerifiedBadge = ({ size = 16, title = "Compte vérifié" }) => (
  <svg width={size} height={size} viewBox="0 0 24 24" style={{ flexShrink: 0 }}
       role="img" aria-label={title}>
    <title>{title}</title>
    <path
      fill="var(--accent)"
      d="M22.25 12L20.86 10.41L21.43 8.36L19.39 7.74L18.95 5.66L16.83 5.5L15.69 3.71L13.66 4.47L12 3.16L10.34 4.47L8.31 3.71L7.17 5.5L5.05 5.66L4.61 7.74L2.57 8.36L3.14 10.41L1.75 12L3.14 13.59L2.57 15.64L4.61 16.26L5.05 18.34L7.17 18.5L8.31 20.29L10.34 19.53L12 20.84L13.66 19.53L15.69 20.29L16.83 18.5L18.95 18.34L19.39 16.26L21.43 15.64L20.86 13.59L22.25 12Z"
    />
    <path
      d="M8.5 12L11 14.5L15.5 10"
      stroke="white" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" fill="none"
    />
  </svg>
);

// Variante "non vérifié" — même forme, gris-warn
const UnverifiedBadge = ({ size = 16, title = "Email non vérifié" }) => (
  <svg width={size} height={size} viewBox="0 0 24 24" style={{ flexShrink: 0 }}
       role="img" aria-label={title}>
    <title>{title}</title>
    <path
      fill="var(--warn)"
      d="M22.25 12L20.86 10.41L21.43 8.36L19.39 7.74L18.95 5.66L16.83 5.5L15.69 3.71L13.66 4.47L12 3.16L10.34 4.47L8.31 3.71L7.17 5.5L5.05 5.66L4.61 7.74L2.57 8.36L3.14 10.41L1.75 12L3.14 13.59L2.57 15.64L4.61 16.26L5.05 18.34L7.17 18.5L8.31 20.29L10.34 19.53L12 20.84L13.66 19.53L15.69 20.29L16.83 18.5L18.95 18.34L19.39 16.26L21.43 15.64L20.86 13.59L22.25 12Z"
    />
    <path
      d="M12 8v5M12 16.5v0.5"
      stroke="white" strokeWidth="2.5" strokeLinecap="round" fill="none"
    />
  </svg>
);

// Lundi de la semaine en cours (offset absolu — sert d'ancre pour l'agenda)
const _AGENDA_MONDAY_OFFSET = cbMondayOffset();
const DAY_LABELS = ["Lun", "Mar", "Mer", "Jeu", "Ven", "Sam"].map((lbl, i) => {
  const d = cbDateForOffset(_AGENDA_MONDAY_OFFSET + i);
  return `${lbl} ${d.getDate()}`;
});
const COLOR_MAP = {
  accent: { bg: "var(--accent-soft)", border: "var(--accent)", ink: "var(--accent-ink)" },
  sage:   { bg: "var(--sage-soft)",   border: "var(--sage)",   ink: "oklch(38% 0.08 160)" },
  warn:   { bg: "oklch(97% 0.025 30)",border: "oklch(70% 0.14 30)", ink: "oklch(42% 0.14 30)" },
};
const COLOR_CHOICES = [["accent", "Violet"], ["sage", "Vert"], ["warn", "Orange"]];

const fmtEUR = (n) => {
  if (n == null || isNaN(n)) return "0 €";
  return n.toLocaleString("fr-FR", { minimumFractionDigits: (n % 1 === 0 ? 0 : 2), maximumFractionDigits: 2 }) + " €";
};
const fmtH = (h) => {
  const hh = Math.floor(h);
  const mm = Math.round((h - hh) * 60);
  return `${hh.toString().padStart(2,"0")}:${mm.toString().padStart(2,"0")}`;
};

/* Dynamic, time- & context-aware greeting line.
   Picks a short friendly phrase + emoji based on hour, weekday, and today's load. */
const cbDynamicGreeting = (firstName, todayCount = 0) => {
  const now = new Date();
  const h = now.getHours();
  const m = now.getMinutes();
  const dow = now.getDay(); // 0 dim … 6 sam
  const minutes = h * 60 + m;
  const name = firstName || "";

  // Specific weekday flourishes
  if (dow === 1 && h >= 6 && h < 11) return { msg: `Bonne semaine, ${name}`, emoji: "✨" };
  if (dow === 5 && h >= 16 && h < 20) return { msg: `Bon vendredi, ${name}`, emoji: "🎉" };
  if ((dow === 0 || dow === 6) && h >= 8 && h < 20) return { msg: `Bon week-end, ${name}`, emoji: "🌿" };

  // Time-of-day buckets
  if (minutes < 6 * 60)            return { msg: `Couche-tôt, ${name} ?`,         emoji: "🌙" };
  if (minutes < 9 * 60)            return { msg: `Bonjour, ${name}`,              emoji: "☀️" };
  if (minutes < 11 * 60 + 30)      return { msg: `Belle matinée, ${name}`,        emoji: "☕" };
  if (minutes < 12 * 60 + 30)      return { msg: `Presque midi, ${name}`,         emoji: "🍽️" };
  if (minutes < 14 * 60)           return { msg: `Bonne pause déjeuner, ${name}`, emoji: "🥗" };
  if (minutes < 16 * 60)           return { msg: `Bel après-midi, ${name}`,       emoji: "🌤️" };
  if (minutes < 18 * 60)           return { msg: `Bonne fin d'après-midi, ${name}`, emoji: "🍵" };
  if (minutes < 20 * 60)           return todayCount > 0
    ? { msg: `Belle journée bouclée, ${name}`, emoji: "✅" }
    : { msg: `Bonne fin de journée, ${name}`,  emoji: "🌇" };
  if (minutes < 22 * 60 + 30)      return { msg: `Bonne soirée, ${name}`,         emoji: "🌙" };
  return { msg: `Bonne nuit, ${name}`,                                            emoji: "✨" };
};

/* Fixed-date holidays/events + a curated saints calendar.
   Returns null when nothing notable for that MM-DD. */
const _CB_EVENTS = {
  "01-01": { kind: "fete",   label: "Jour de l'An",            emoji: "🎊" },
  "01-06": { kind: "fete",   label: "Épiphanie",               emoji: "👑" },
  "02-02": { kind: "fete",   label: "Chandeleur",              emoji: "🥞" },
  "02-14": { kind: "fete",   label: "Saint-Valentin",          emoji: "💌" },
  "03-08": { kind: "fete",   label: "Journée des droits des femmes", emoji: "💜" },
  "03-17": { kind: "saint",  label: "Saint Patrick",           emoji: "🍀" },
  "03-19": { kind: "saint",  label: "Saint Joseph",            emoji: "🌿" },
  "03-21": { kind: "fete",   label: "Premier jour du printemps", emoji: "🌸" },
  "04-01": { kind: "fete",   label: "1er avril",               emoji: "🐟" },
  "04-22": { kind: "fete",   label: "Jour de la Terre",        emoji: "🌍" },
  "05-01": { kind: "ferie",  label: "Fête du Travail",         emoji: "🌷" },
  "05-08": { kind: "ferie",  label: "Victoire 1945",           emoji: "🇫🇷" },
  "06-21": { kind: "fete",   label: "Fête de la Musique",      emoji: "🎶" },
  "07-14": { kind: "ferie",  label: "Fête nationale",          emoji: "🎆" },
  "08-15": { kind: "ferie",  label: "Assomption",              emoji: "✨" },
  "09-23": { kind: "fete",   label: "Premier jour de l'automne", emoji: "🍂" },
  "10-31": { kind: "fete",   label: "Halloween",               emoji: "🎃" },
  "11-01": { kind: "ferie",  label: "Toussaint",               emoji: "🕯️" },
  "11-11": { kind: "ferie",  label: "Armistice 1918",          emoji: "🌹" },
  "11-25": { kind: "saint",  label: "Sainte Catherine",        emoji: "💜" },
  "12-06": { kind: "saint",  label: "Saint Nicolas",           emoji: "🎁" },
  "12-21": { kind: "fete",   label: "Premier jour de l'hiver", emoji: "❄️" },
  "12-24": { kind: "fete",   label: "Réveillon de Noël",       emoji: "🎄" },
  "12-25": { kind: "ferie",  label: "Noël",                    emoji: "🎄" },
  "12-31": { kind: "fete",   label: "Saint-Sylvestre",         emoji: "🥂" },
  // Saints familiers (jour de la fête)
  "01-21": { kind: "saint", label: "Sainte Agnès",       emoji: "🌼" },
  "02-05": { kind: "saint", label: "Sainte Agathe",      emoji: "🌼" },
  "03-09": { kind: "saint", label: "Sainte Françoise",   emoji: "🌼" },
  "04-29": { kind: "saint", label: "Sainte Catherine de Sienne", emoji: "🌼" },
  "05-30": { kind: "saint", label: "Sainte Jeanne d'Arc", emoji: "⚔️" },
  "06-13": { kind: "saint", label: "Saint Antoine",      emoji: "🌿" },
  "06-24": { kind: "saint", label: "Saint Jean",         emoji: "🔥" },
  "07-22": { kind: "saint", label: "Sainte Marie-Madeleine", emoji: "🌼" },
  "07-26": { kind: "saint", label: "Sainte Anne",        emoji: "🌼" },
  "08-10": { kind: "saint", label: "Saint Laurent",      emoji: "🌿" },
  "08-28": { kind: "saint", label: "Saint Augustin",     emoji: "🌿" },
  "09-08": { kind: "saint", label: "Nativité de Marie",  emoji: "🌼" },
  "09-29": { kind: "saint", label: "Saint Michel",       emoji: "🌿" },
  "10-04": { kind: "saint", label: "Saint François",     emoji: "🌿" },
  "10-15": { kind: "saint", label: "Sainte Thérèse d'Avila", emoji: "🌼" },
  "12-04": { kind: "saint", label: "Sainte Barbe",       emoji: "🌼" },
  "12-08": { kind: "fete",  label: "Immaculée Conception", emoji: "✨" },
  "12-13": { kind: "saint", label: "Sainte Lucie",       emoji: "🕯️" },
  "12-26": { kind: "saint", label: "Saint Étienne",      emoji: "🌿" },
};
const cbEventOfDay = (d = new Date()) => {
  const mm = String(d.getMonth() + 1).padStart(2, "0");
  const dd = String(d.getDate()).padStart(2, "0");
  return _CB_EVENTS[`${mm}-${dd}`] || null;
};

/* "DD/MM" → "MM-DD" key, or null if invalid. */
const _bdayKey = (s) => {
  if (!s) return null;
  const m = String(s).match(/^(\d{1,2})[\/\-\.](\d{1,2})$/);
  if (!m) return null;
  const dd = +m[1], mm = +m[2];
  if (mm < 1 || mm > 12 || dd < 1 || dd > 31) return null;
  return `${String(mm).padStart(2,"0")}-${String(dd).padStart(2,"0")}`;
};
const cbBirthdaysToday = (clients = []) => {
  const today = new Date();
  const key = `${String(today.getMonth()+1).padStart(2,"0")}-${String(today.getDate()).padStart(2,"0")}`;
  return clients.filter(c => _bdayKey(c.birthday) === key);
};

/* Réseaux sociaux — labels, placeholders, lien profil pour un pseudo donné. */
const CB_SOCIAL_LABEL = {
  instagram: "Instagram",
  facebook:  "Facebook",
  tiktok:    "TikTok",
  snapchat:  "Snapchat",
  whatsapp:  "WhatsApp",
};
const CB_SOCIAL_PLACEHOLDER = {
  instagram: "@pseudo",
  facebook:  "Pseudo ou prénom.nom",
  tiktok:    "@pseudo",
  snapchat:  "pseudo",
  whatsapp:  "06 12 34 56 78",
};
const cbSocialUrl = (network, handle) => {
  if (!handle) return null;
  const h = String(handle).trim().replace(/^@/, "");
  if (!h) return null;
  switch (network) {
    case "instagram": return `https://instagram.com/${encodeURIComponent(h)}`;
    case "tiktok":    return `https://tiktok.com/@${encodeURIComponent(h)}`;
    case "facebook":  return `https://facebook.com/${encodeURIComponent(h)}`;
    case "snapchat":  return `https://snapchat.com/add/${encodeURIComponent(h)}`;
    case "whatsapp":  return `https://wa.me/${h.replace(/[^0-9+]/g, "")}`;
    default:          return null;
  }
};

/* Returns the active vacation (covering today), or null. */
const cbActivePause = (bs) => {
  const vacs = (bs && bs.vacations) || [];
  const today = new Date();
  const ymd = `${today.getFullYear()}-${String(today.getMonth()+1).padStart(2,"0")}-${String(today.getDate()).padStart(2,"0")}`;
  return vacs.find(v => ymd >= v.start && ymd <= v.end) || null;
};

/* Animated number — eases from 0 to target over duration ms, then stays. */
const useAnimatedNumber = (target, duration = 700) => {
  const [val, setVal] = React.useState(0);
  React.useEffect(() => {
    const start = performance.now();
    const from = 0;
    let raf;
    const tick = (t) => {
      const k = Math.min(1, (t - start) / duration);
      const eased = 1 - Math.pow(1 - k, 3); // easeOutCubic
      setVal(from + (target - from) * eased);
      if (k < 1) raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [target, duration]);
  return val;
};

/* ================================================================
   APP SHELL
================================================================ */
const AppSplash = () => {
  const firstName = (() => {
    try {
      const u = window.cbAuth && window.cbAuth.getCurrentUser && window.cbAuth.getCurrentUser();
      return u ? (u.ownerName || "").split(" ")[0] : "";
    } catch { return ""; }
  })();
  const hour = new Date().getHours();
  const greeting = hour < 6 ? "Bonne nuit" : hour < 12 ? "Bonjour" : hour < 18 ? "Bon après-midi" : "Bonsoir";

  return (
    <div style={{
      position: "fixed", inset: 0, zIndex: 200,
      background: "#FFFFFF",
      display: "flex", alignItems: "center", justifyContent: "center",
      flexDirection: "column", gap: 12,
    }}>
      <style>{`
        @keyframes cbSplashLogoIn {
          0%   { opacity: 0; transform: scale(0.85); }
          100% { opacity: 1; transform: scale(1); }
        }
        @keyframes cbSplashFadeUp {
          from { opacity: 0; transform: translateY(6px); }
          to   { opacity: 1; transform: translateY(0); }
        }
        @keyframes cbSplashProgress {
          from { transform: scaleX(0); }
          to   { transform: scaleX(1); }
        }
      `}</style>
      <div style={{ animation: "cbSplashLogoIn 0.4s cubic-bezier(0.22, 1, 0.36, 1)" }}>
        <svg width="44" height="44" viewBox="0 0 32 32" style={{ display: "block" }}>
          <rect x="0" y="0" width="32" height="32" rx="8.5" fill="#3C2FBD"/>
          <path d="M11.5 20.5 L20.5 11.5 M13.5 11.5 L20.5 11.5 L20.5 18.5"
            stroke="white" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" fill="none"/>
        </svg>
      </div>
      {firstName ? (
        <div style={{
          fontFamily: "var(--ff-display)", fontSize: 17, fontWeight: 560,
          letterSpacing: "-0.015em", color: "var(--ink)",
          animation: "cbSplashFadeUp 0.4s ease 0.1s both",
        }}>
          {greeting}, {firstName}
        </div>
      ) : (
        <div style={{
          fontFamily: "var(--ff-display)", fontSize: 16, fontWeight: 560,
          letterSpacing: "-0.015em", color: "var(--ink)",
          animation: "cbSplashFadeUp 0.4s ease 0.1s both",
        }}>
          ClientBase
        </div>
      )}
      <div style={{
        width: 120, height: 2, borderRadius: 999,
        background: "color-mix(in oklab, var(--accent) 15%, transparent)",
        overflow: "hidden",
        animation: "cbSplashFadeUp 0.4s ease 0.2s both",
      }}>
        <div style={{
          width: "100%", height: "100%",
          background: "var(--accent)",
          transformOrigin: "left center",
          animation: "cbSplashProgress 0.8s cubic-bezier(0.22, 1, 0.36, 1) 0.15s both",
        }}/>
      </div>
    </div>
  );
};

/* Bannière persistante en haut de l'espace pro tant que l'email n'est pas
   vérifié. Non bloquante — l'utilisateur peut continuer à utiliser l'app
   et cliquer le lien email plus tard. */
const UnverifiedBanner = () => {
  const user = useCurrentUser();
  const [busy, setBusy] = React.useState(false);
  const [sent, setSent] = React.useState(false);
  const [dismissed, setDismissed] = React.useState(false);

  if (!user) return null;
  if (user.emailVerified) return null;     // session cloud déjà vérifiée
  if (!user.unverified && !user.email) return null;
  if (dismissed) return null;

  const resend = async () => {
    setBusy(true);
    const res = await cbAuth.resendVerification();
    setBusy(false);
    if (res && res.error) {
      showToast(res.error, "warn");
      return;
    }
    setSent(true);
    showToast("Lien de vérification renvoyé ✉️");
  };

  return (
    <div style={{
      padding: "10px 16px",
      background: "var(--warn-soft)",
      borderBottom: "1px solid var(--warn-soft-2)",
      color: "var(--warn-ink)",
      display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap",
      fontSize: 13.5,
    }}>
      <span style={{
        width: 22, height: 22, borderRadius: 50, flexShrink: 0,
        background: "var(--warn)", color: "white",
        display: "inline-flex", alignItems: "center", justifyContent: "center",
        fontWeight: 700, fontSize: 13,
      }}>!</span>
      <div style={{ flex: 1, minWidth: 200, lineHeight: 1.4 }}>
        <strong>Email non vérifié.</strong> Cliquez sur le lien envoyé à <strong>{user.email}</strong> pour
        activer la synchronisation cloud. Vous pouvez le faire plus tard — l'app fonctionne déjà.
      </div>
      <button onClick={resend} disabled={busy || sent} style={{
        padding: "6px 12px", fontSize: 12.5, fontWeight: 580,
        background: sent ? "var(--sage)" : "var(--warn-ink)",
        color: "white", border: "none", borderRadius: 999,
        cursor: (busy || sent) ? "default" : "pointer", fontFamily: "inherit",
        opacity: busy ? 0.6 : 1, whiteSpace: "nowrap",
      }}>
        {sent ? "✓ Renvoyé" : busy ? "Envoi…" : "Renvoyer le lien"}
      </button>
      <button onClick={() => setDismissed(true)} style={{
        padding: "6px 10px", fontSize: 12.5, fontWeight: 500,
        background: "transparent", color: "var(--warn-ink)", border: "1px solid var(--warn-soft-2)",
        borderRadius: 999, cursor: "pointer", fontFamily: "inherit", whiteSpace: "nowrap",
      }}>
        Plus tard
      </button>
    </div>
  );
};

const AppShell = (props) => {
  const [splashDone, setSplashDone] = React.useState(() => sessionStorage.getItem("cb_splash_shown") === "1");
  React.useEffect(() => {
    if (splashDone) return;
    const t = setTimeout(() => {
      setSplashDone(true);
      sessionStorage.setItem("cb_splash_shown", "1");
    }, 900);
    return () => clearTimeout(t);
  }, [splashDone]);
  return splashDone ? <AppShellInner {...props}/> : <AppSplash/>;
};

const AppShellInner = ({ go }) => {
  const [data, setData, resetData] = useAppData();
  // Toujours repartir sur Accueil quand on ouvre l'espace pro
  const [module, setModule] = React.useState("accueil");
  const [modal, setModal] = React.useState(null);
  const [drawerOpen, setDrawerOpen] = React.useState(false);

  const setMod = (m) => {
    setModule(m);
    try { localStorage.removeItem("cb_app_mod"); } catch {}
    setDrawerOpen(false);
  };
  const openModal = (type, ctx = {}) => setModal({ type, ctx });
  const closeModal = () => setModal(null);

  /* ---------- mutations ---------- */
  const newId = () => (window.crypto && window.crypto.randomUUID) ? window.crypto.randomUUID() : "local_" + uid();
  const cloud = (label, fn) => {
    if (!window.cbCloud || !window.cbCloud.isActive()) return;
    Promise.resolve().then(fn).catch(err => {
      console.error("[cloud]", label, err);
      showToast("Échec synchro — " + label, "warn");
    });
  };

  const actions = React.useMemo(() => ({
    addClient: (c) => {
      const id = newId();
      const client = {
        id, visits: 0, spent: 0, fid: 0, last: "—",
        tags: c.tags && c.tags.length ? c.tags : ["Nouvelle"],
        hue: (Math.random() * 360) | 0,
        notes: "",
        phone: "",
        email: "",
        ...c,
      };
      setData(d => ({ ...d, clients: [client, ...d.clients] }));
      cloud("ajout client", () => window.cbCloud.upsertClient(client, true));
      showToast(`${client.name} ajoutée`);
      return id;
    },
    updateClient: (id, patch) => {
      setData(d => ({ ...d, clients: d.clients.map(c => c.id === id ? { ...c, ...patch } : c) }));
      cloud("modif client", () => window.cbCloud.upsertClient({ id, ...patch }, false));
      showToast("Cliente modifiée");
    },
    deleteClient: (id) => {
      if (!window.confirm("Supprimer ce client ? Cette action est définitive.")) return;
      setData(d => ({ ...d, clients: d.clients.filter(c => c.id !== id) }));
      cloud("suppression client", () => window.cbCloud.removeClient(id));
      showToast("Cliente supprimée", "warn");
    },
    /* ---- Services CRUD (prestations proposées) ---- */
    addService: (s) => {
      const svc = {
        id: newId(),
        name: s.name || "Nouvelle prestation",
        price: Number(s.price) || 0,
        duration: Number(s.duration) || 1,
        description: s.description || "",
        active: s.active !== false,
      };
      setData(d => ({ ...d, services: [svc, ...(d.services || [])] }));
      cloud("ajout prestation", () => window.cbCloud.upsertService(svc, true));
      showToast(`${svc.name} ajoutée`);
      return svc.id;
    },
    updateService: (id, patch) => {
      setData(d => ({
        ...d,
        services: (d.services || []).map(s => s.id === id ? { ...s, ...patch } : s),
      }));
      cloud("modif prestation", () => window.cbCloud.upsertService({ id, ...patch }, false));
      showToast("Prestation mise à jour");
    },
    deleteService: (id) => {
      const used = (data.appointments || []).some(a => a.serviceId === id);
      const msg = used
        ? "Cette prestation est utilisée dans des RDV existants. Supprimer quand même ? Les RDV concernés garderont juste le nom."
        : "Supprimer cette prestation ?";
      if (!window.confirm(msg)) return;
      setData(d => ({ ...d, services: (d.services || []).filter(s => s.id !== id) }));
      cloud("suppression prestation", () => window.cbCloud.removeService(id));
      showToast("Prestation supprimée", "warn");
    },
    toggleServiceActive: (id) => {
      const cur = (data.services || []).find(s => s.id === id);
      const nextActive = !(cur && cur.active !== false);
      setData(d => ({
        ...d,
        services: (d.services || []).map(s => s.id === id ? { ...s, active: nextActive } : s),
      }));
      cloud("visibilité prestation", () => window.cbCloud.upsertService({ id, active: nextActive }, false));
    },
    addAppointment: (a) => {
      const ap = { id: newId(), color: "accent", ...a };
      setData(d => ({ ...d, appointments: [...d.appointments, ap] }));
      cloud("ajout RDV", () => window.cbCloud.upsertAppointment(ap, true));
      showToast("Rendez-vous ajouté");
    },
    deleteAppointment: (id) => {
      setData(d => ({ ...d, appointments: d.appointments.filter(a => a.id !== id) }));
      cloud("suppression RDV", () => window.cbCloud.removeAppointment(id));
      showToast("Rendez-vous supprimé", "warn");
    },
    markAppointmentDone: (id) => {
      const ap = data.appointments.find(a => a.id === id);
      if (!ap || ap.done) return;
      const svc = findService(data, ap.serviceId);
      const cli = findClient(data, ap.clientId);
      if (!svc || !cli) return;
      const nextNum = data.invoiceCounter + 1;
      const invoiceNumber = `F-${new Date().getFullYear()}-${String(nextNum).padStart(4, "0")}`;
      const invoiceRowId = newId();
      const today = new Date();
      const todayFR = today.toLocaleDateString("fr-FR", { day: "numeric", month: "long", year: "numeric" });
      const business = data.business || {};
      const dueDays = Number(business.paymentTermsDays || 0);
      const dueDateObj = new Date(today.getTime() + dueDays * 86400000);
      const dueDateFR = dueDateObj.toLocaleDateString("fr-FR", { day: "numeric", month: "long", year: "numeric" });
      // Date de prestation = date du RDV si calculable, sinon aujourd'hui
      const apptDate = (() => {
        try {
          if (typeof ap.day === "number" && typeof ap.h === "number") {
            const base = new Date(); base.setHours(0, 0, 0, 0);
            const cbToday = (typeof cbTodayOffset === "function") ? cbTodayOffset() : 0;
            const dayOffset = ap.day - cbToday;
            const d = new Date(base.getTime() + dayOffset * 86400000);
            return d.toLocaleDateString("fr-FR", { day: "numeric", month: "long", year: "numeric" });
          }
        } catch {}
        return todayFR;
      })();
      const invoice = {
        id: invoiceRowId,
        number: invoiceNumber,
        clientId: cli.id,
        date: todayFR,
        amount: svc.price,
        paid: true,
        designation: svc.name,
        prestationDate: apptDate,
        dueDate: dueDays > 0 ? dueDateFR : todayFR,
        vatRate: business.vatStatus === "applicable" ? Number(business.vatRate || 0) : 0,
      };
      const newFid = Math.min((cli.fid || 0) + 1, data.fideliteRules.visits);
      const newVisits = cli.visits + 1;
      const newSpent = cli.spent + svc.price;
      setData(d => ({
        ...d,
        invoiceCounter: nextNum,
        invoices: [invoice, ...d.invoices],
        appointments: d.appointments.map(x => x.id === id
          ? { ...x, done: true, invoiceId: invoiceRowId, completedAt: Date.now() }
          : x),
        clients: d.clients.map(c => c.id === cli.id ? {
          ...c, visits: newVisits, spent: newSpent, fid: newFid, last: "aujourd'hui",
        } : c),
      }));
      cloud("facture auto", async () => {
        await window.cbCloud.upsertInvoice(invoice, true);
        await window.cbCloud.upsertAppointment({ id, done: true, invoiceId: invoiceRowId, completedAt: Date.now() }, false);
        await window.cbCloud.upsertClient({ id: cli.id, visits: newVisits, spent: newSpent, fid: newFid, last: "aujourd'hui" }, false);
        await window.cbCloud.incrementInvoiceCounter(nextNum);
      });
      showToast(`Prestation facturée — ${fmtEUR(svc.price)}`, "success", {
        action: { label: "Annuler", onClick: () => actions.undoAppointmentDone(id) },
      });
    },
    undoAppointmentDone: (id) => {
      const ap = data.appointments.find(a => a.id === id);
      if (!ap || !ap.done) return;
      const svc = findService(data, ap.serviceId);
      const cli = findClient(data, ap.clientId);
      const deletedInvoiceId = ap.invoiceId;
      const newVisits = cli ? Math.max(0, cli.visits - 1) : 0;
      const newSpent = cli ? Math.max(0, cli.spent - (svc ? svc.price : 0)) : 0;
      const newFid = cli ? Math.max(0, (cli.fid || 0) - 1) : 0;
      setData(d => ({
        ...d,
        invoices: d.invoices.filter(inv => inv.id !== deletedInvoiceId),
        appointments: d.appointments.map(x => x.id === id
          ? { ...x, done: false, invoiceId: null, completedAt: null }
          : x),
        clients: cli ? d.clients.map(c => c.id === cli.id
          ? { ...c, visits: newVisits, spent: newSpent, fid: newFid }
          : c) : d.clients,
      }));
      cloud("annulation facture", async () => {
        if (deletedInvoiceId) await window.cbCloud.removeInvoice(deletedInvoiceId);
        await window.cbCloud.upsertAppointment({ id, done: false, invoiceId: null, completedAt: null }, false);
        if (cli) await window.cbCloud.upsertClient({ id: cli.id, visits: newVisits, spent: newSpent, fid: newFid }, false);
      });
      showToast("Annulé — facture supprimée", "warn");
    },
    sendMessage: (convId, text) => {
      if (!text.trim()) return;
      const now = new Date();
      const time = `${now.getHours()}:${String(now.getMinutes()).padStart(2,"0")}`;
      const msg = { id: "msg_" + uid(), from: "me", text: text.trim(), time };
      setData(d => {
        const existing = d.messages[convId] || [];
        const convos = d.conversations.some(c => c.clientId === convId)
          ? d.conversations
          : [{ clientId: convId, last: text.trim(), time, unread: 0 }, ...d.conversations];
        return {
          ...d,
          messages: { ...d.messages, [convId]: [...existing, msg] },
          conversations: convos.map(c => c.clientId === convId
            ? { ...c, last: text.trim(), time, unread: 0 }
            : c),
        };
      });
    },
    openConv: (convId) => {
      setData(d => ({
        ...d,
        activeConv: convId,
        conversations: d.conversations.map(c => c.clientId === convId ? { ...c, unread: 0 } : c),
      }));
    },
    addInvoice: (inv) => {
      const nextNum = data.invoiceCounter + 1;
      const invoiceRowId = newId();
      const invoiceNumber = `F-${new Date().getFullYear()}-${String(nextNum).padStart(4, "0")}`;
      const today = new Date();
      const todayFR = today.toLocaleDateString("fr-FR", { day: "numeric", month: "long", year: "numeric" });
      const business = data.business || {};
      const dueDays = Number(business.paymentTermsDays || 0);
      const dueDateFR = dueDays > 0
        ? new Date(today.getTime() + dueDays * 86400000)
            .toLocaleDateString("fr-FR", { day: "numeric", month: "long", year: "numeric" })
        : todayFR;
      const invoice = {
        id: invoiceRowId,
        number: invoiceNumber,
        paid: false,
        date: todayFR,
        prestationDate: inv.prestationDate || todayFR,
        dueDate: dueDateFR,
        vatRate: business.vatStatus === "applicable" ? Number(business.vatRate || 0) : 0,
        designation: "Prestation",
        ...inv,
      };
      setData(d => ({ ...d, invoiceCounter: nextNum, invoices: [invoice, ...d.invoices] }));
      cloud("création facture", async () => {
        await window.cbCloud.upsertInvoice(invoice, true);
        await window.cbCloud.incrementInvoiceCounter(nextNum);
      });
      showToast("Facture créée");
    },
    toggleInvoicePaid: (id) => {
      const current = data.invoices.find(i => i.id === id);
      const newPaid = current ? !current.paid : true;
      setData(d => ({ ...d, invoices: d.invoices.map(i => i.id === id ? { ...i, paid: newPaid } : i) }));
      cloud("statut facture", () => window.cbCloud.upsertInvoice({ id, paid: newPaid }, false));
      if (newPaid) {
        showToast("Paiement encaissé", "success", {
          action: { label: "Annuler", onClick: () => actions.toggleInvoicePaid(id) },
        });
      }
    },
    deleteInvoice: (id) => {
      // Une facture émise ne peut PAS être supprimée (obligation comptable).
      // On crée à la place un avoir (facture de montant négatif) qui annule la facture.
      const orig = data.invoices.find(i => i.id === id);
      if (!orig) return;
      if (!window.confirm(
        `Émettre un avoir d'annulation pour la facture ${orig.number} ?\n\n` +
        `Une facture émise ne peut pas être supprimée (obligation comptable). ` +
        `Un avoir sera créé pour le même montant en négatif.`
      )) return;
      const nextNum = data.invoiceCounter + 1;
      const avoirRowId = newId();
      const avoirNumber = `A-${new Date().getFullYear()}-${String(nextNum).padStart(4, "0")}`;
      const avoir = {
        id: avoirRowId,
        number: avoirNumber,
        clientId: orig.clientId,
        date: new Date().toLocaleDateString("fr-FR", { day: "numeric", month: "long", year: "numeric" }),
        amount: -Math.abs(orig.amount),
        paid: !!orig.paid,
        designation: `Avoir d'annulation — facture ${orig.number}`,
        creditOf: orig.number,
      };
      setData(d => ({ ...d, invoiceCounter: nextNum, invoices: [avoir, ...d.invoices] }));
      cloud("création avoir", async () => {
        await window.cbCloud.upsertInvoice(avoir, true);
        await window.cbCloud.incrementInvoiceCounter(nextNum);
      });
      showToast(`Avoir ${avoirNumber} créé — la facture ${orig.number} est annulée comptablement.`, "ok");
    },
    addStockItem: (item) => {
      const it = { id: newId(), qty: 0, min: 0, price: 0, ...item };
      setData(d => ({ ...d, stock: [...d.stock, it] }));
      cloud("ajout stock", () => window.cbCloud.upsertStock(it, true));
      showToast("Produit ajouté");
    },
    adjustStock: (id, delta) => {
      const cur = data.stock.find(s => s.id === id);
      const newQty = Math.max(0, (cur ? cur.qty : 0) + delta);
      setData(d => ({
        ...d,
        stock: d.stock.map(s => s.id === id ? { ...s, qty: newQty } : s),
      }));
      cloud("ajustement stock", () => window.cbCloud.upsertStock({ id, qty: newQty }, false));
    },
    setStockQty: (id, qty) => {
      const q = Math.max(0, qty);
      setData(d => ({ ...d, stock: d.stock.map(s => s.id === id ? { ...s, qty: q } : s) }));
      cloud("stock", () => window.cbCloud.upsertStock({ id, qty: q }, false));
    },
    deleteStockItem: (id) => {
      setData(d => ({ ...d, stock: d.stock.filter(s => s.id !== id) }));
      cloud("suppression stock", () => window.cbCloud.removeStock(id));
      showToast("Produit retiré", "warn");
    },
    addPromo: (p) => {
      setData(d => ({ ...d, promos: [{ id: "pr_" + uid(), status: "Brouillon", ...p }, ...d.promos] }));
      showToast("Promotion créée");
    },
    togglePromoStatus: (id) => {
      const order = ["Brouillon", "Programmé", "Actif"];
      setData(d => ({
        ...d,
        promos: d.promos.map(p => p.id === id
          ? { ...p, status: order[(order.indexOf(p.status) + 1) % order.length] }
          : p),
      }));
    },
    deletePromo: (id) => {
      setData(d => ({ ...d, promos: d.promos.filter(p => p.id !== id) }));
      showToast("Promotion supprimée", "warn");
    },
    updateFideliteRules: (rules) => {
      setData(d => ({ ...d, fideliteRules: { ...d.fideliteRules, ...rules } }));
      cloud("règles fidélité", () => window.cbCloud.setFideliteRules(rules));
      showToast("Règles de fidélité enregistrées");
    },
    addVisit: (clientId) => {
      const cli = data.clients.find(c => c.id === clientId);
      if (!cli) return;
      const visits = cli.visits + 1;
      const fid = Math.min((cli.fid || 0) + 1, data.fideliteRules.visits);
      setData(d => ({
        ...d,
        clients: d.clients.map(c => c.id === clientId
          ? { ...c, visits, fid, last: "aujourd'hui" }
          : c),
      }));
      cloud("visite", () => window.cbCloud.upsertClient({ id: clientId, visits, fid, last: "aujourd'hui" }, false));
      showToast("Visite enregistrée");
    },
    adjustFidelite: (clientId, delta) => {
      const cli = data.clients.find(c => c.id === clientId);
      if (!cli) return;
      const fid = Math.max(0, Math.min((cli.fid || 0) + delta, data.fideliteRules.visits));
      setData(d => ({
        ...d,
        clients: d.clients.map(c => c.id === clientId ? { ...c, fid } : c),
      }));
      cloud("fidélité", () => window.cbCloud.upsertClient({ id: clientId, fid }, false));
    },
    resetFidelite: (clientId) => {
      setData(d => ({
        ...d,
        clients: d.clients.map(c => c.id === clientId ? { ...c, fid: 0 } : c),
      }));
      cloud("reset fidélité", () => window.cbCloud.upsertClient({ id: clientId, fid: 0 }, false));
      showToast("Carte remise à zéro");
    },
    updateBusiness: (patch) => {
      setData(d => ({ ...d, business: { ...d.business, ...patch } }));
      cloud("paramètres", () => window.cbCloud.updateBusiness(patch));
      showToast("Paramètres sauvegardés");
    },
    updateBookingSettings: (patch) => {
      // Vacations ont leur propre table : on les gère séparément
      if (patch.vacations !== undefined) {
        const prev = (data.bookingSettings && data.bookingSettings.vacations) || [];
        const next = patch.vacations;
        const added   = next.filter(v => !prev.find(p => p.id === v.id));
        const removed = prev.filter(v => !next.find(p => p.id === v.id));
        added.forEach(v => cloud("vacation+", () => window.cbCloud.addVacation(v)));
        removed.forEach(v => cloud("vacation-", () => window.cbCloud.removeVacation(v.id)));
      }
      setData(d => ({ ...d, bookingSettings: { ...(d.bookingSettings || {}), ...patch } }));
      // Les autres clés (schedule/slotDuration…) vont sur booking_settings
      const rest = { ...patch }; delete rest.vacations;
      if (Object.keys(rest).length) {
        cloud("réservation", () => window.cbCloud.updateBookingSettings(rest));
      }
      showToast("Paramètres de réservation mis à jour");
    },
    markAllNotificationsRead: () => {
      setData(d => ({
        ...d,
        notifications: (d.notifications || []).map(n => ({ ...n, read: true })),
      }));
      cloud("notifications", () => window.cbCloud.markAllNotificationsRead());
    },
    deleteNotification: (id) => {
      setData(d => ({
        ...d,
        notifications: (d.notifications || []).filter(n => n.id !== id),
      }));
      cloud("notification-", () => window.cbCloud.removeNotification(id));
    },
    reset: async () => {
      try {
        await resetData();
        showToast("Toutes les données ont été supprimées", "warn");
      } catch (e) {
        showToast("Échec de la réinitialisation : " + (e && e.message ? e.message : "erreur inconnue"), "warn");
      }
    },
    deleteAccount: async () => {
      try {
        if (window.cbCloud && window.cbCloud.isActive()) {
          await window.cbCloud.deleteMyAccount();
        }
        // Le delete cascade côté Supabase invalide la session côté serveur,
        // mais on force aussi un signOut local + on vide le localStorage.
        try { await cbAuth.logout(); } catch {}
        try {
          localStorage.removeItem("cb_data_v1");
          localStorage.removeItem("cb_data_v1_cloud");
        } catch {}
        // Hard reload pour repartir de zéro côté UI
        window.location.href = "/";
      } catch (e) {
        showToast("Échec de la suppression : " + (e && e.message ? e.message : "erreur inconnue"), "warn");
      }
    },
  }), [data, setData, resetData]);

  return (
    <div style={{
      height: "100vh",
      display: "grid",
      gridTemplateColumns: "240px 1fr",
      background: "var(--bg)",
    }} className="app-grid">
      <AppSidebar module={module} setMod={setMod} data={data} go={go}
        drawerOpen={drawerOpen} onCloseDrawer={() => setDrawerOpen(false)}/>
      <main style={{ overflow: "auto", minWidth: 0 }} className="app-main">
        <AppTopBar module={module} openModal={openModal} data={data} actions={actions} go={go} setMod={setMod}
          onOpenDrawer={() => setDrawerOpen(true)}/>
        <div className="app-content" style={{ padding: "28px 36px" }}>
          {module === "accueil"  && <AppHome data={data} actions={actions} openModal={openModal} setMod={setMod}/>}
          {module === "agenda"   && <AppAgenda data={data} actions={actions} openModal={openModal}/>}
          {module === "services" && <AppServices data={data} actions={actions}/>}
          {module === "bookings" && <AppBookings data={data} actions={actions}/>}
          {module === "clients"  && <AppClients data={data} actions={actions} openModal={openModal}/>}
          {module === "fidelite" && <AppFidelite data={data} actions={actions} openModal={openModal}/>}
          {module === "stats"    && <AppStats data={data}/>}
          {module === "factures" && <AppFactures data={data} actions={actions} openModal={openModal}/>}
          {module === "stock"    && <AppStock data={data} actions={actions} openModal={openModal}/>}
          {module === "settings"     && <AppSettings data={data} actions={actions} go={go}/>}
          {module === "subscription" && <AppSubscription data={data} go={go} setMod={setMod}/>}
        </div>
      </main>

      {/* Modals */}
      <ClientFormModal modal={modal} onClose={closeModal} data={data} actions={actions}/>
      <AppointmentFormModal modal={modal} onClose={closeModal} data={data} actions={actions}/>
      <InvoiceFormModal modal={modal} onClose={closeModal} data={data} actions={actions}/>
      <StockFormModal modal={modal} onClose={closeModal} actions={actions}/>
      <PromoFormModal modal={modal} onClose={closeModal} actions={actions}/>
      <FideliteRulesModal modal={modal} onClose={closeModal} data={data} actions={actions}/>

      <Toasts/>
      <OnboardingTour data={data} setMod={setMod}/>
    </div>
  );
};

/* First-connection onboarding — 3 short cards, skippable, never returns. */
const OnboardingTour = ({ data, setMod }) => {
  const [step, setStep] = React.useState(() => {
    try { return localStorage.getItem("cb_onboarded_v1") === "1" ? -1 : 0; }
    catch { return -1; }
  });
  const close = () => {
    try { localStorage.setItem("cb_onboarded_v1", "1"); } catch {}
    setStep(-1);
  };
  if (step < 0) return null;

  const firstName = (data.business.owner || "").split(" ")[0];
  const cards = [
    {
      emoji: "👋",
      title: `Bienvenue, ${firstName} !`,
      body: "Voici votre tableau de bord. Vos rendez-vous du jour, votre CA, vos clientes — tout est ici, rien de plus.",
    },
    {
      emoji: "💜",
      title: "Votre clientèle est précieuse",
      body: "Ajoutez vos clientes, leurs préférences, leurs anniversaires. ClientBase vous rappelle qui pense à vous, et qui mérite un petit mot.",
    },
    {
      emoji: "🔗",
      title: "Partagez votre lien de réservation",
      body: "Vos clientes choisissent un créneau elles-mêmes — sur Insta, WhatsApp, votre carte de visite. Plus de DM à 22h pour caler un rendez-vous.",
    },
  ];
  const c = cards[step];
  const last = step === cards.length - 1;

  return (
    <div style={{
      position: "fixed", inset: 0, zIndex: 400,
      background: "rgba(20, 20, 30, 0.45)", backdropFilter: "blur(4px)",
      display: "flex", alignItems: "center", justifyContent: "center",
      padding: 20, animation: "cbOnbFade 220ms ease-out",
    }}>
      <style>{`
        @keyframes cbOnbFade { from { opacity: 0; } to { opacity: 1; } }
        @keyframes cbOnbCardIn { from { opacity: 0; transform: translateY(8px) scale(.98); } to { opacity: 1; transform: translateY(0) scale(1); } }
      `}</style>
      <div key={step} style={{
        width: "100%", maxWidth: 420,
        background: "var(--surface)", borderRadius: 16,
        boxShadow: "0 24px 60px -20px rgba(0,0,0,.35)",
        padding: 28, animation: "cbOnbCardIn 280ms cubic-bezier(.22,.61,.36,1)",
      }}>
        <div style={{ fontSize: 38, lineHeight: 1, marginBottom: 14 }}>{c.emoji}</div>
        <h3 style={{ fontSize: 20, letterSpacing: "-0.02em", marginBottom: 8 }}>{c.title}</h3>
        <p style={{ fontSize: 14, color: "var(--ink-3)", lineHeight: 1.55, marginBottom: 22 }}>{c.body}</p>

        {/* Progress dots */}
        <div style={{ display: "flex", gap: 6, marginBottom: 20 }}>
          {cards.map((_, i) => (
            <div key={i} style={{
              flex: 1, height: 3, borderRadius: 2,
              background: i <= step ? "var(--accent)" : "var(--line)",
              transition: "background .25s",
            }}/>
          ))}
        </div>

        <div style={{ display: "flex", gap: 10, justifyContent: "space-between", alignItems: "center" }}>
          <button className="btn btn-ghost" onClick={close}
            style={{ fontSize: 13, color: "var(--ink-4)" }}>
            Passer
          </button>
          <button className="btn btn-primary"
            onClick={() => last ? close() : setStep(s => s + 1)}
            style={{ minWidth: 120 }}>
            {last ? "C'est parti !" : "Suivant"}
            <Icon name="arrow" size={13}/>
          </button>
        </div>
      </div>
    </div>
  );
};

/* ================================================================
   SIDEBAR
================================================================ */
const AppSidebar = ({ module, setMod, data, go, drawerOpen, onCloseDrawer }) => {
  return (
    <>
    {drawerOpen && (
      <div onClick={onCloseDrawer} className="app-drawer-backdrop" style={{
        position: "fixed", inset: 0, background: "rgba(15,18,30,0.4)",
        zIndex: 55, display: "none",
      }}/>
    )}
    <aside className={"app-sidebar" + (drawerOpen ? " app-sidebar-open" : "")} style={{
      borderRight: "1px solid var(--line)",
      background: "var(--surface)",
      padding: "20px 12px",
      display: "flex", flexDirection: "column",
      overflow: "auto",
    }}>
      <div style={{ padding: "2px 6px 14px", display: "flex", alignItems: "center", gap: 10 }}>
        <ProAvatar
          url={data.business.avatar}
          initials={(data.business.name || "CB").split(" ").map(x => x[0]).slice(0,2).join("").toUpperCase()}
          hue={data.business.hue} size={34}
        />
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{
            fontFamily: "var(--ff-display)", fontSize: 16, fontWeight: 600,
            color: "var(--ink)", letterSpacing: "-0.015em",
            display: "flex", alignItems: "center", gap: 6, minWidth: 0,
          }}>
            <span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
              {data.business.name || "Votre activité"}
            </span>
            {(() => {
              const u = window.cbAuth && window.cbAuth.getCurrentUser && window.cbAuth.getCurrentUser();
              if (!u) return null;
              return u.emailVerified ? <VerifiedBadge size={16}/> : <UnverifiedBadge size={16}/>;
            })()}
          </div>
        </div>
        {/* Petit bouton paramètres à droite du nom */}
        <button onClick={() => setMod("settings")}
          aria-label="Paramètres"
          title="Paramètres"
          style={{
            width: 32, height: 32, borderRadius: 9, flexShrink: 0,
            background: module === "settings" ? "var(--ink)" : "var(--bg-alt)",
            color: module === "settings" ? "white" : "var(--ink-3)",
            border: "none", cursor: "pointer", padding: 0,
            display: "flex", alignItems: "center", justifyContent: "center",
            transition: "background .15s, color .15s, transform .12s",
            fontFamily: "inherit",
          }}
          onMouseEnter={e => { if (module !== "settings") { e.currentTarget.style.background = "var(--line)"; e.currentTarget.style.color = "var(--ink)"; } }}
          onMouseLeave={e => { if (module !== "settings") { e.currentTarget.style.background = "var(--bg-alt)"; e.currentTarget.style.color = "var(--ink-3)"; } }}>
          <Icon name="settings" size={15}/>
        </button>
      </div>

      {/* Séparateur fin entre le header (avatar + nom) et la nav */}
      <div style={{ height: 1, background: "var(--line)", margin: "0 12px 14px", opacity: 0.6 }}/>

      <div className="cb-app-nav" style={{ display: "flex", flexDirection: "column", gap: 18, marginTop: 0 }}>
        {APP_MODULE_GROUPS.map((group, gi) => (
          <div key={group.label} className="cb-app-nav-group" style={{
            display: "flex", flexDirection: "column", gap: 2,
          }}>
            <div className="cb-app-nav-group-label" style={{
              fontSize: 10.5, fontWeight: 600, color: group.tone.label,
              letterSpacing: "0.06em", textTransform: "uppercase",
              padding: "0 12px 6px",
              display: "flex", alignItems: "center", gap: 7,
            }}>
              <span aria-hidden style={{
                width: 6, height: 6, borderRadius: 50,
                background: group.tone.dot, flexShrink: 0,
              }}/>
              {group.label}
            </div>
            {group.ids.map(id => {
              const m = APP_MODULES.find(x => x.id === id);
              if (!m) return null;
              const isActive = module === m.id;
              return (
                <button key={m.id} onClick={() => setMod(m.id)}
                  onMouseEnter={e => { if (!isActive) e.currentTarget.style.background = "var(--bg-alt)"; }}
                  onMouseLeave={e => { if (!isActive) e.currentTarget.style.background = "transparent"; }}
                  className="cb-app-nav-btn"
                  style={{
                    display: "flex", alignItems: "center", gap: 11,
                    padding: "9px 12px", border: "none", width: "100%",
                    borderRadius: 10, cursor: "pointer",
                    fontSize: 13.5, fontWeight: isActive ? 580 : 500,
                    background: isActive ? "var(--accent-soft)" : "transparent",
                    color: isActive ? "var(--accent-ink)" : "var(--ink-2)",
                    textAlign: "left", fontFamily: "inherit",
                    transition: "background .2s cubic-bezier(0.22, 1, 0.36, 1), color .2s ease",
                    position: "relative",
                    letterSpacing: "-0.005em",
                  }}>
                  <span aria-hidden style={{
                    position: "absolute", left: 0, top: 9, bottom: 9,
                    width: 3, borderRadius: 2,
                    background: "var(--accent)",
                    transform: isActive ? "scaleY(1)" : "scaleY(0)",
                    transformOrigin: "center",
                    transition: "transform .25s cubic-bezier(0.22, 1, 0.36, 1)",
                  }}/>
                  <span style={{
                    width: 28, height: 28, borderRadius: 8,
                    background: isActive ? "var(--accent)" : "var(--bg-alt)",
                    color: isActive ? "white" : group.tone.dot,
                    display: "flex", alignItems: "center", justifyContent: "center",
                    flexShrink: 0,
                    transition: "background .2s ease, color .2s ease",
                  }}>
                    <Icon name={m.icon} size={14}/>
                  </span>
                  <span style={{ flex: 1 }}>{m.label}</span>
                </button>
              );
            })}
            {/* Séparateur fin entre groupes (sauf le dernier) */}
            {gi < APP_MODULE_GROUPS.length - 1 && (
              <div style={{ height: 1, background: "var(--line)", margin: "12px 12px 0", opacity: 0.6 }}/>
            )}
          </div>
        ))}
      </div>
      <style>{`
        @media (max-width: 760px) {
          .cb-app-nav { gap: 22px !important; }
          .cb-app-nav-group-label { font-size: 11px !important; padding: 0 16px 8px !important; }
          .cb-app-nav-btn { padding: 12px 16px !important; font-size: 15px !important; gap: 14px !important; border-radius: 12px !important; }
          .cb-app-nav-btn span:first-child + span { width: 34px !important; height: 34px !important; border-radius: 10px !important; }
        }
      `}</style>
    </aside>
    </>
  );
};

/* ================================================================
   TOP BAR
================================================================ */
const AppTopBar = ({ module, openModal, data, actions, go, setMod, onOpenDrawer }) => {
  const [newOpen, setNewOpen] = React.useState(false);
  const [notifOpen, setNotifOpen] = React.useState(false);
  const [query, setQuery] = React.useState("");
  const title = APP_MODULES.find(m => m.id === module)?.label || "";

  const notifs = data.notifications || [];
  const unreadCount = notifs.filter(n => !n.read).length;

  const toggleNotif = () => {
    const willOpen = !notifOpen;
    setNotifOpen(willOpen);
    if (willOpen && unreadCount > 0) {
      // Mark all as read a moment after opening so the UI still shows the unread dots briefly
      setTimeout(() => actions && actions.markAllNotificationsRead && actions.markAllNotificationsRead(), 500);
    }
  };

  const handleNew = () => {
    if (module === "clients")  { openModal("client");       return; }
    if (module === "agenda")   { openModal("appointment");  return; }
    if (module === "factures") { openModal("invoice");      return; }
    if (module === "stock")    { openModal("stock");        return; }
    setNewOpen(v => !v);
  };

  const newMenuItem = (label, icon, action) => (
    <button onClick={() => { setNewOpen(false); action(); }} style={{
      display: "flex", alignItems: "center", gap: 10,
      width: "100%", padding: "9px 12px",
      background: "transparent", border: "none", cursor: "pointer",
      textAlign: "left", fontSize: 13.5, fontFamily: "inherit", color: "var(--ink-2)",
      borderRadius: 6,
    }} className="cb-row-hover">
      <Icon name={icon} size={14} style={{ color: "var(--ink-4)" }}/> {label}
    </button>
  );

  return (
    <div className="app-topbar" style={{
      display: "flex", alignItems: "center", gap: 16,
      padding: "18px 36px", borderBottom: "1px solid var(--line)",
      background: "var(--surface)", position: "relative", zIndex: 20,
    }}>
      <button onClick={onOpenDrawer} className="app-mobile-burger" aria-label="Menu" style={{
        display: "none",
        background: "transparent", border: "1px solid var(--line)",
        borderRadius: 8, width: 36, height: 36,
        alignItems: "center", justifyContent: "center",
        cursor: "pointer", color: "var(--ink)",
      }}>
        <Icon name="menu" size={18}/>
      </button>
      <h2 className="app-topbar-title" style={{ fontSize: 20, fontWeight: 580, letterSpacing: "-0.02em" }}>{title}</h2>
      <div className="app-topbar-search" style={{ flex: 1, maxWidth: 420, marginLeft: "auto", position: "relative" }}>
        <Icon name="search" size={14} style={{ position: "absolute", left: 12, top: "50%", transform: "translateY(-50%)", color: "var(--ink-4)" }}/>
        <input value={query} onChange={e => setQuery(e.target.value)}
          placeholder="Rechercher un client…" style={{
          width: "100%", padding: "8px 12px 8px 34px",
          background: "var(--bg)", border: "1px solid var(--line)",
          borderRadius: 8, fontSize: 13, fontFamily: "inherit", color: "var(--ink)",
          outline: "none",
        }}/>
        <span style={{ position: "absolute", right: 10, top: "50%", transform: "translateY(-50%)" }}>
          <span className="kbd">⌘K</span>
        </span>
        {query.trim().length >= 2 && (
          <SearchResults data={data} query={query} openModal={(t, c) => { setQuery(""); openModal(t, c); }}/>
        )}
      </div>
      <div style={{ position: "relative" }}>
        <button onClick={toggleNotif} className="btn btn-sm btn-ghost" style={{ height: 34, position: "relative" }} aria-label="Notifications">
          <Icon name="bell" size={15}/>
          {unreadCount > 0 && (
            <span style={{
              position: "absolute", top: 3, right: 4,
              minWidth: 16, height: 16, padding: "0 4px",
              background: "var(--accent)", color: "white",
              borderRadius: 10, fontSize: 10, fontWeight: 700,
              display: "flex", alignItems: "center", justifyContent: "center",
              fontFamily: "inherit",
              border: "2px solid var(--surface)",
            }}>{unreadCount}</span>
          )}
        </button>
        {notifOpen && (
          <>
            <div onClick={() => setNotifOpen(false)} style={{ position: "fixed", inset: 0, zIndex: 30 }}/>
            <div className="app-notif-panel" style={{
              position: "absolute", right: 0, top: "calc(100% + 8px)",
              width: 360, maxWidth: "calc(100vw - 32px)",
              background: "var(--surface)", border: "1px solid var(--line)",
              borderRadius: 12, boxShadow: "var(--sh-3)",
              zIndex: 40, overflow: "hidden",
            }}>
              <div style={{
                padding: "12px 14px", borderBottom: "1px solid var(--line)",
                display: "flex", justifyContent: "space-between", alignItems: "center",
              }}>
                <div style={{ fontWeight: 520, fontSize: 14 }}>Notifications</div>
                {notifs.length > 0 && (
                  <button onClick={() => { actions.markAllNotificationsRead(); }} style={{
                    background: "transparent", border: "none", cursor: "pointer",
                    fontSize: 12, color: "var(--accent-ink)", fontFamily: "inherit",
                  }}>Tout marquer lu</button>
                )}
              </div>
              <div style={{ maxHeight: 400, overflowY: "auto" }}>
                {notifs.length === 0 ? (
                  <div style={{ padding: 32, textAlign: "center", color: "var(--ink-4)", fontSize: 13 }}>
                    Aucune notification pour le moment.
                  </div>
                ) : (
                  notifs.slice(0, 20).map(n => (
                    <NotificationRow key={n.id} n={n} data={data}
                      onClick={() => {
                        setNotifOpen(false);
                        if (n.type === "booking" && n.appointmentId && setMod) setMod("agenda");
                        if (n.type === "payment"  && setMod) setMod("factures");
                        if (n.type === "stock"    && setMod) setMod("stock");
                      }}
                      onDelete={() => actions.deleteNotification(n.id)}
                    />
                  ))
                )}
              </div>
            </div>
          </>
        )}
      </div>
      <div style={{ position: "relative" }}>
        <button onClick={handleNew} className="btn btn-sm" style={{ height: 34, background: "var(--ink)", color: "var(--bg)" }}>
          <Icon name="plus" size={14}/> Nouveau
        </button>
        {newOpen && (
          <>
            <div onClick={() => setNewOpen(false)} style={{ position: "fixed", inset: 0, zIndex: 30 }}/>
            <div style={{
              position: "absolute", right: 0, top: "calc(100% + 6px)",
              background: "var(--surface)", border: "1px solid var(--line)",
              borderRadius: 10, padding: 6, boxShadow: "var(--sh-2)",
              minWidth: 200, zIndex: 40,
            }}>
              {newMenuItem("Nouveau client", "users", () => openModal("client"))}
              {newMenuItem("Nouveau rendez-vous", "calendar", () => openModal("appointment"))}
              {newMenuItem("Nouvelle facture", "invoice", () => openModal("invoice"))}
              {newMenuItem("Nouveau produit", "box", () => openModal("stock"))}
            </div>
          </>
        )}
      </div>
    </div>
  );
};

const SearchResults = ({ data, query, openModal }) => {
  const q = query.trim().toLowerCase();
  const hits = data.clients.filter(c =>
    c.name.toLowerCase().includes(q) || (c.email || "").toLowerCase().includes(q)
  ).slice(0, 6);
  return (
    <div style={{
      position: "absolute", top: "calc(100% + 6px)", left: 0, right: 0,
      background: "var(--surface)", border: "1px solid var(--line)",
      borderRadius: 10, boxShadow: "var(--sh-2)", zIndex: 50,
      padding: 6, maxHeight: 320, overflow: "auto",
    }}>
      {hits.length === 0 && (
        <div style={{ padding: "14px 12px", fontSize: 13, color: "var(--ink-4)" }}>Aucun client trouvée.</div>
      )}
      {hits.map(c => (
        <button key={c.id} onClick={() => openModal("client", { id: c.id })} className="cb-row-hover" style={{
          display: "flex", alignItems: "center", gap: 10, width: "100%",
          padding: "8px 10px", background: "transparent", border: "none",
          cursor: "pointer", textAlign: "left", borderRadius: 6, fontFamily: "inherit",
        }}>
          <Avatar client={c} size={28}/>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontSize: 13.5, fontWeight: 520, color: "var(--ink)" }}>{c.name}</div>
            <div style={{ fontSize: 11.5, color: "var(--ink-4)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{c.email || c.phone || "—"}</div>
          </div>
        </button>
      ))}
    </div>
  );
};

const timeAgo = (ts) => {
  const diff = Date.now() - ts;
  const m = Math.floor(diff / 60000);
  if (m < 1) return "à l'instant";
  if (m < 60) return `il y a ${m} min`;
  const h = Math.floor(m / 60);
  if (h < 24) return `il y a ${h} h`;
  const d = Math.floor(h / 24);
  if (d === 1) return "hier";
  if (d < 7) return `il y a ${d} j`;
  const w = Math.floor(d / 7);
  return `il y a ${w} sem.`;
};

const NOTIF_ICON = {
  booking: { name: "calendar", bg: "var(--accent-soft)", c: "var(--accent-ink)" },
  payment: { name: "euro",     bg: "var(--sage-soft)",   c: "oklch(38% 0.08 160)" },
  stock:   { name: "bell",     bg: "oklch(97% 0.025 30)", c: "oklch(45% 0.14 30)" },
  default: { name: "bell",     bg: "var(--bg-alt)",      c: "var(--ink-3)" },
};

const NotificationRow = ({ n, data, onClick, onDelete }) => {
  const icon = NOTIF_ICON[n.type] || NOTIF_ICON.default;
  const client = n.clientId ? findClient(data, n.clientId) : null;
  return (
    <div style={{
      padding: "12px 14px", borderBottom: "1px solid var(--line)",
      display: "flex", gap: 10, alignItems: "flex-start",
      background: n.read ? "transparent" : "color-mix(in oklab, var(--accent-soft) 40%, transparent)",
      cursor: "pointer", position: "relative",
    }}
      onClick={onClick}
      onMouseEnter={e => e.currentTarget.style.background = "var(--bg-alt)"}
      onMouseLeave={e => e.currentTarget.style.background = n.read ? "transparent" : "color-mix(in oklab, var(--accent-soft) 40%, transparent)"}
    >
      <div style={{
        width: 32, height: 32, borderRadius: 8,
        background: icon.bg, color: icon.c,
        display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0,
      }}>
        <Icon name={icon.name} size={15}/>
      </div>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", gap: 8 }}>
          <div style={{ fontSize: 13.5, fontWeight: 520, color: "var(--ink)" }}>{n.title}</div>
          <div style={{ fontSize: 11, color: "var(--ink-4)", fontFamily: "inherit", flexShrink: 0 }}>{timeAgo(n.createdAt)}</div>
        </div>
        <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginTop: 2, lineHeight: 1.45 }}>{n.message}</div>
        {client && (
          <div style={{ marginTop: 8, display: "flex", alignItems: "center", gap: 8 }}>
            <Avatar client={client} size={22}/>
            <span style={{ fontSize: 12, color: "var(--ink-4)" }}>{client.name}</span>
          </div>
        )}
      </div>
      <button onClick={(e) => { e.stopPropagation(); onDelete(); }} aria-label="Supprimer" style={{
        background: "transparent", border: "none", cursor: "pointer",
        color: "var(--ink-4)", padding: 4, flexShrink: 0, opacity: 0.6,
      }}
        onMouseEnter={e => { e.currentTarget.style.opacity = 1; e.currentTarget.style.color = "oklch(55% 0.18 25)"; }}
        onMouseLeave={e => { e.currentTarget.style.opacity = 0.6; e.currentTarget.style.color = "var(--ink-4)"; }}
      >
        <Icon name="close" size={14}/>
      </button>
    </div>
  );
};

const Avatar = ({ client, size = 32 }) => (
  <div style={{
    width: size, height: size, borderRadius: 50,
    background: `linear-gradient(135deg, oklch(80% 0.12 ${client.hue}), oklch(70% 0.14 ${(client.hue+40)%360}))`,
    color: "white", display: "flex", alignItems: "center", justifyContent: "center",
    fontSize: size * 0.38, fontWeight: 600, flexShrink: 0,
  }}>{client.name.split(" ").map(x => x[0]).slice(0,2).join("")}</div>
);

/* ================================================================
   HOME DASHBOARD
================================================================ */
const AppHome = ({ data, actions, openModal, setMod }) => {
  const today = data.appointments.filter(a => a.day === cbTodayOffset() && !a.done).sort((x, y) => x.h - y.h);
  const next = today[0];
  const firstName = (data.business.owner || "").split(" ")[0];
  // Re-evaluate every minute so the line refreshes (e.g. from "Belle matinée" → "Presque midi").
  const [tick, setTick] = React.useState(0);
  React.useEffect(() => {
    const id = setInterval(() => setTick(t => t + 1), 60000);
    return () => clearInterval(id);
  }, []);
  const greet = cbDynamicGreeting(firstName, today.length);
  const event = cbEventOfDay();
  const bdays = cbBirthdaysToday(data.clients || []);
  const pause = cbActivePause(data.bookingSettings);
  const animCount = useAnimatedNumber(today.length, 650);
  const displayCount = today.length === 0 ? 0 : Math.max(1, Math.round(animCount));

  return (
    <>
      {/* Greeting — dynamic, time- & day-aware */}
      <div style={{ marginBottom: 18 }}>
        <div style={{ fontSize: 13, color: "var(--ink-4)", fontWeight: 500, display: "flex", alignItems: "center", gap: 6, flexWrap: "wrap" }}>
          <span>{greet.msg}</span>
          <span style={{ fontSize: 14, lineHeight: 1 }}>{greet.emoji}</span>
          {event && (
            <span style={{
              display: "inline-flex", alignItems: "center", gap: 5,
              marginLeft: 4, paddingLeft: 10, borderLeft: "1px solid var(--line)",
              color: "var(--ink-4)",
            }}>
              <span style={{ fontSize: 13 }}>{event.emoji}</span>
              <span>{event.label}</span>
            </span>
          )}
        </div>
        <h2 style={{
          fontSize: "clamp(22px, 2.8vw, 28px)", marginTop: 4, letterSpacing: "-0.025em",
          fontVariantNumeric: "tabular-nums",
        }}>
          {today.length === 0
            ? "Journée tranquille."
            : today.length === 1
              ? `${displayCount} rendez-vous aujourd'hui.`
              : `${displayCount} rendez-vous aujourd'hui.`}
          {next && (
            <span style={{ color: "var(--ink-4)", fontWeight: 500, fontSize: "0.72em", display: "block", marginTop: 4 }}>
              Prochain à {fmtH(next.h)} · {findClient(data, next.clientId)?.name}
            </span>
          )}
        </h2>
      </div>

      {/* Pause / vacances active — bandeau doux pinned tout en haut */}
      {pause && (
        <div style={{
          marginBottom: 14, padding: "10px 14px",
          background: "var(--surface)", border: "1px solid var(--line)",
          borderLeft: "3px solid oklch(72% 0.10 200)",
          borderRadius: 10, display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap",
        }}>
          <span style={{ fontSize: 16 }}>🌴</span>
          <div style={{ fontSize: 13, color: "var(--ink-2)", lineHeight: 1.4, flex: 1, minWidth: 0 }}>
            <strong style={{ fontWeight: 580 }}>En pause</strong>
            <span style={{ color: "var(--ink-4)" }}>
              {" "}· De retour le {new Date(pause.end).toLocaleDateString("fr-FR", { day: "numeric", month: "long" })} — réservations en ligne suspendues.
            </span>
          </div>
          <button className="btn btn-sm btn-ghost"
            onClick={() => setMod("bookings")}
            style={{ fontSize: 12, height: 28, flexShrink: 0 }}>
            Modifier <Icon name="arrow" size={11}/>
          </button>
        </div>
      )}

      {/* Anniversaires clients du jour — pinned, very discrete */}
      {bdays.length > 0 && (
        <div style={{
          marginBottom: 14, padding: "10px 14px",
          background: "var(--surface)", border: "1px solid var(--line)",
          borderLeft: "3px solid var(--accent)",
          borderRadius: 10, display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap",
        }}>
          <span style={{ fontSize: 16 }}>🎂</span>
          <div style={{ fontSize: 13, color: "var(--ink-2)", lineHeight: 1.4, flex: 1, minWidth: 0 }}>
            <strong style={{ fontWeight: 580 }}>
              {bdays.length === 1 ? "Anniversaire aujourd'hui" : `${bdays.length} anniversaires aujourd'hui`}
            </strong>
            <span style={{ color: "var(--ink-4)" }}>
              {" "}· {bdays.map(c => c.name.split(" ")[0]).join(", ")} — un petit message ferait plaisir ✨
            </span>
          </div>
          <button className="btn btn-sm btn-ghost"
            onClick={() => setMod("clients")}
            style={{ fontSize: 12, height: 28, flexShrink: 0 }}>
            Ouvrir <Icon name="arrow" size={11}/>
          </button>
        </div>
      )}

      {/* ONE rich interactive block — tabs for the key insights */}
      <div style={{ marginBottom: 18 }}>
        <HomePulse data={data} setMod={setMod}/>
      </div>

      {/* Today's schedule — the main actionable block */}
      <div style={{ padding: 20, background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 14 }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 12 }}>
          <h3 style={{ fontSize: 15.5 }}>Votre journée</h3>
          <button className="btn btn-sm btn-ghost" onClick={() => setMod("agenda")}>
            Voir la semaine <Icon name="arrow" size={12}/>
          </button>
        </div>
        {today.length === 0 ? (
          <div style={{
            padding: 28, textAlign: "center", background: "var(--bg-alt)",
            borderRadius: 10, border: "1px dashed var(--line-strong)",
          }}>
            <Icon name="sparkle" size={20} style={{ color: "var(--ink-4)" }}/>
            <div style={{ marginTop: 8, fontSize: 13.5, color: "var(--ink-3)" }}>
              Pas de rendez-vous aujourd'hui. Bonne journée&nbsp;!
            </div>
          </div>
        ) : (
          <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
            {today.map(a => {
              const c = findClient(data, a.clientId);
              const s = findService(data, a.serviceId);
              const col = COLOR_MAP[a.color];
              return (
                <div key={a.id} className="cb-home-rdv" style={{
                  display: "flex", alignItems: "center", gap: 12,
                  padding: "10px 12px", background: col.bg,
                  borderLeft: `3px solid ${col.border}`, borderRadius: 10,
                  minWidth: 0,
                }}>
                  <div style={{
                    fontVariantNumeric: "tabular-nums", fontSize: 13, fontWeight: 600,
                    color: col.ink, flexShrink: 0, whiteSpace: "nowrap",
                  }}>{fmtH(a.h)}</div>
                  {c && <Avatar client={c} size={30}/>}
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{
                      fontWeight: 520, fontSize: 13.5, color: "var(--ink)",
                      whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis",
                    }}>{c ? c.name : "—"}</div>
                    <div style={{
                      fontSize: 12, color: "var(--ink-3)",
                      whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis",
                    }}>{s ? s.name : ""}</div>
                  </div>
                  {s && (
                    <div style={{
                      fontSize: 12.5, fontWeight: 580, color: "var(--ink-2)",
                      fontVariantNumeric: "tabular-nums", flexShrink: 0,
                      whiteSpace: "nowrap",
                    }}>{fmtEUR(s.price)}</div>
                  )}
                  <button
                    onClick={() => actions.markAppointmentDone(a.id)}
                    className="btn btn-sm cb-home-rdv-btn"
                    aria-label="Fait & facturer"
                    title="Fait & facturer"
                    style={{
                      background: "var(--ink)", color: "var(--bg)",
                      height: 30, fontSize: 12, padding: "0 10px",
                      flexShrink: 0, gap: 6,
                    }}>
                    <Icon name="check" size={13} stroke={2.4}/>
                    <span className="cb-home-rdv-btn-label">Fait &amp; facturer</span>
                  </button>
                </div>
              );
            })}
          </div>
        )}
        <style>{`
          @media (max-width: 560px) {
            .cb-home-rdv-btn-label { display: none; }
            .cb-home-rdv-btn { width: 30px; padding: 0 !important; justify-content: center; }
          }
        `}</style>
      </div>
    </>
  );
};

/* ONE interactive widget with CA (day/week/month) + playful "Et si…" sims. */
const HomePulse = ({ data, setMod }) => {
  const [tab, setTab] = React.useState("ca");

  const monthPaid = data.invoices.filter(i => i.paid).reduce((s, i) => s + i.amount, 0);
  const pending = data.invoices.filter(i => !i.paid).reduce((s, i) => s + i.amount, 0);
  const avgTicket = data.invoices.length > 0 ? monthPaid / data.invoices.length : 45;

  const [goal, setGoal] = React.useState(() => {
    const saved = +(localStorage.getItem("cb_goal_monthly") || 0);
    return saved > 0 ? saved : Math.max(4000, Math.ceil(monthPaid / 1000) * 1000 + 500);
  });
  React.useEffect(() => { localStorage.setItem("cb_goal_monthly", String(goal)); }, [goal]);
  const pctGoal = Math.min(100, (monthPaid / goal) * 100);
  const remaining = Math.max(0, goal - monthPaid);
  const rdvToReach = Math.ceil(remaining / Math.max(avgTicket, 1));

  const TABS = [
    { id: "ca",       label: "CA",         icon: "euro" },
    { id: "goal",     label: "Objectif",   icon: "zap" },
    { id: "whatif",   label: "Et si…",     icon: "sparkle" },
  ];

  return (
    <div style={{
      background: "var(--surface)", border: "1px solid var(--line)",
      borderRadius: 14, overflow: "hidden",
    }}>
      <div style={{
        display: "flex", gap: 2, padding: 6,
        background: "var(--bg-alt)", borderBottom: "1px solid var(--line)",
        overflowX: "auto", WebkitOverflowScrolling: "touch",
      }}>
        {TABS.map(t => {
          const active = tab === t.id;
          return (
            <button key={t.id} onClick={() => setTab(t.id)} style={{
              flex: 1, minWidth: 88,
              padding: "8px 10px", fontSize: 12.5, fontWeight: 520,
              display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 6,
              background: active ? "var(--surface)" : "transparent",
              color: active ? "var(--accent-ink)" : "var(--ink-3)",
              boxShadow: active ? "var(--sh-1)" : "none",
              border: "none", borderRadius: 8, cursor: "pointer", fontFamily: "inherit",
              transition: "background .15s, color .15s",
            }}>
              <Icon name={t.icon} size={13}/>
              <span>{t.label}</span>
            </button>
          );
        })}
      </div>

      <div style={{ padding: 20 }}>
        {tab === "ca" && (
          <PulseCA monthPaid={monthPaid} pending={pending} avgTicket={avgTicket}
            invoices={data.invoices}
            appointments={data.appointments || []}
            services={data.services || []}
            onMore={() => setMod("factures")}/>
        )}
        {tab === "goal" && (
          <PulseGoal goal={goal} setGoal={setGoal} monthPaid={monthPaid}
            pct={pctGoal} remaining={remaining} rdvToReach={rdvToReach} avgTicket={avgTicket}/>
        )}
        {tab === "whatif" && (
          <PulseWhatIf monthPaid={monthPaid} avgTicket={avgTicket} goal={goal}/>
        )}
      </div>
    </div>
  );
};

const PulseCA = ({ monthPaid, pending, avgTicket, invoices, appointments, services, onMore }) => {
  const [range, setRange] = React.useState("week"); // day | week | upcoming | month
  const [hover, setHover] = React.useState(null);

  // Valeur en € pour un offset jour donné = somme des prix des prestations
  // de tous les RDV de ce jour (passés ou futurs).
  const valueForDay = React.useCallback((off) => {
    let sum = 0;
    for (const a of appointments) {
      if (a.day !== off) continue;
      const svc = services.find(s => s.id === a.serviceId);
      if (svc) sum += Number(svc.price) || 0;
    }
    return Math.round(sum);
  }, [appointments, services]);

  const todayOff = window.cbTodayOffset ? window.cbTodayOffset() : 0;

  const series = React.useMemo(() => {
    if (range === "day") {
      // RDV d'aujourd'hui par heure (8h → 19h)
      return Array.from({ length: 12 }, (_, i) => {
        const hour = 8 + i;
        let s = 0;
        for (const a of appointments) {
          if (a.day !== todayOff) continue;
          if (Math.floor(a.h) === hour) {
            const svc = services.find(svc => svc.id === a.serviceId);
            if (svc) s += Number(svc.price) || 0;
          }
        }
        return Math.round(s);
      });
    }
    if (range === "week") {
      // 3 jours passés + aujourd'hui + 3 jours suivants
      return Array.from({ length: 7 }, (_, i) => valueForDay(todayOff - 3 + i));
    }
    if (range === "upcoming") {
      // Aujourd'hui + 6 prochains jours (focus prévisionnel)
      return Array.from({ length: 7 }, (_, i) => valueForDay(todayOff + i));
    }
    // 30 jours glissants : 7 passés + 23 à venir
    return Array.from({ length: 30 }, (_, i) => valueForDay(todayOff - 7 + i));
  }, [appointments, services, range, todayOff, valueForDay]);

  // Index du "aujourd'hui" dans la série, pour le repère visuel
  const todayIdx = range === "week" ? 3 : range === "upcoming" ? 0 : range === "month" ? 7 : -1;

  const total = series.reduce((a, b) => a + b, 0);
  const max = Math.max(1, ...series);
  const width = 300, height = 70;
  const pts = series.map((v, i) => [
    series.length === 1 ? width / 2 : (i / (series.length - 1)) * width,
    height - (v / max) * (height - 6) - 3,
  ]);
  const poly = pts.map(p => p.join(",")).join(" ");
  const area = `M 0 ${height} L ${poly.split(" ").join(" L ")} L ${width} ${height} Z`;

  // Libellé d'un point i de la série, basé sur son offset jour absolu
  const _hintForOffset = (off) => {
    const d = window.cbDateForOffset ? window.cbDateForOffset(off) : new Date();
    const days = window.CB_FR_DAYS_SHORT || ["Dim","Lun","Mar","Mer","Jeu","Ven","Sam"];
    const months = window.CB_FR_MONTHS_SHORT || ["janv.","févr.","mars","avril","mai","juin","juil.","août","sept.","oct.","nov.","déc."];
    return `${days[d.getDay()]} ${d.getDate()} ${months[d.getMonth()]}`;
  };
  const labelMap = {
    day:      { title: "Aujourd'hui",            hint: (i) => `${8 + i}h` },
    week:     { title: "Cette semaine",          hint: (i) => _hintForOffset(todayOff - 3 + i) },
    upcoming: { title: "Les 7 prochains jours",  hint: (i) => _hintForOffset(todayOff + i) },
    month:    { title: "30 jours glissants",     hint: (i) => _hintForOffset(todayOff - 7 + i) },
  };
  const L = labelMap[range];

  return (
    <div>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 10, flexWrap: "wrap", marginBottom: 10 }}>
        <div style={{ fontSize: 12, color: "var(--ink-4)", fontWeight: 500 }}>{L.title}</div>
        <div style={{
          display: "inline-flex", padding: 3,
          background: "var(--bg-alt)", border: "1px solid var(--line)",
          borderRadius: 999, gap: 2,
        }}>
          {[["day", "Jour"], ["week", "Semaine"], ["upcoming", "À venir"], ["month", "Mois"]].map(([k, l]) => (
            <button key={k} onClick={() => { setRange(k); setHover(null); }} style={{
              padding: "4px 10px", fontSize: 11.5, fontWeight: 500,
              background: range === k ? "var(--surface)" : "transparent",
              color: range === k ? "var(--ink)" : "var(--ink-3)",
              boxShadow: range === k ? "var(--sh-1)" : "none",
              border: "none", borderRadius: 999, cursor: "pointer", fontFamily: "inherit",
              transition: "background .15s, color .15s",
            }}>{l}</button>
          ))}
        </div>
      </div>

      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-end", gap: 12, flexWrap: "wrap" }}>
        <div>
          <div style={{
            fontFamily: "var(--ff-display)", fontSize: "clamp(28px, 3.5vw, 36px)",
            fontWeight: 580, letterSpacing: "-0.03em", color: "var(--accent-ink)",
            fontVariantNumeric: "tabular-nums",
          }}>
            {hover != null ? fmtEUR(series[hover]) : fmtEUR(total)}
          </div>
          <div style={{ fontSize: 12, color: "var(--ink-4)", marginTop: 2 }}>
            {hover != null
              ? L.hint(hover)
              : range === "upcoming" ? "potentiel à venir"
              : range === "week"     ? "cette semaine (réel + prévu)"
              : range === "month"    ? "sur 30 jours"
              : "aujourd'hui"}
          </div>
        </div>
        <div style={{
          padding: "6px 10px", background: "var(--warn-soft)",
          color: "var(--warn-ink)", borderRadius: 8,
          fontSize: 12, fontWeight: 520,
        }}>
          {fmtEUR(pending)} en attente
        </div>
      </div>

      <svg viewBox={`0 0 ${width} ${height}`} style={{ width: "100%", height: 70, marginTop: 12, overflow: "visible" }}>
        <defs>
          <linearGradient id="pulseCaFill" x1="0" y1="0" x2="0" y2="1">
            <stop offset="0%" stopColor="var(--accent)" stopOpacity="0.25"/>
            <stop offset="100%" stopColor="var(--accent)" stopOpacity="0"/>
          </linearGradient>
          <style>{`
            @keyframes cbSparklineDraw { from { stroke-dashoffset: 1000; } to { stroke-dashoffset: 0; } }
            @keyframes cbSparklineFill { from { opacity: 0; } to { opacity: 1; } }
            .cb-sparkline-line { stroke-dasharray: 1000; animation: cbSparklineDraw 900ms cubic-bezier(.22,.61,.36,1) both; }
            .cb-sparkline-fill { animation: cbSparklineFill 600ms ease-out 300ms both; }
          `}</style>
        </defs>
        <path className="cb-sparkline-fill" key={`fill-${range}`} d={area} fill="url(#pulseCaFill)"/>
        {/* Repère "aujourd'hui" — sépare le passé réel du prévisionnel */}
        {todayIdx > 0 && todayIdx < pts.length && (
          <line x1={pts[todayIdx][0]} y1={0} x2={pts[todayIdx][0]} y2={height}
            stroke="var(--ink-4)" strokeWidth="1" strokeDasharray="2 3" opacity="0.5"/>
        )}
        <polyline className="cb-sparkline-line" key={`line-${range}`}
          points={poly} fill="none" stroke="var(--accent)" strokeWidth="2.2"
          strokeLinecap="round" strokeLinejoin="round"/>
        {pts.map(([x, y], i) => (
          <g key={i}>
            <circle cx={x} cy={y} r={hover === i ? 4 : 0}
              fill="var(--accent)" stroke="white" strokeWidth="2"
              style={{ transition: "r .15s" }}/>
            <rect x={x - width / (pts.length * 2)} y={0} width={width / pts.length} height={height}
              fill="transparent"
              onMouseEnter={() => setHover(i)} onMouseLeave={() => setHover(null)}
              onTouchStart={() => setHover(i)} onTouchEnd={() => setHover(null)}
              style={{ cursor: "pointer" }}/>
          </g>
        ))}
      </svg>

      <button onClick={onMore} className="btn btn-sm btn-ghost" style={{ marginTop: 10, height: 30, fontSize: 12 }}>
        Voir les factures <Icon name="arrow" size={12}/>
      </button>
    </div>
  );
};

/* "Et si j'ajoute X prestations ce mois ?" */
const PulseWhatIf = ({ monthPaid, avgTicket, goal }) => {
  const [extra, setExtra] = React.useState(5);
  const projected = monthPaid + extra * avgTicket;
  const pct = Math.min(100, (projected / goal) * 100);
  const hours = Math.round(extra * 1.1);
  return (
    <div>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", flexWrap: "wrap", gap: 8 }}>
        <div style={{ fontSize: 12, color: "var(--ink-4)", fontWeight: 500 }}>
          Et si je fais
        </div>
        <div style={{
          fontFamily: "var(--ff-display)", fontSize: "clamp(24px, 3vw, 30px)",
          fontWeight: 580, letterSpacing: "-0.025em", color: "var(--accent-ink)",
          fontVariantNumeric: "tabular-nums",
        }}>
          +{extra} <span style={{ fontSize: 13, color: "var(--ink-3)", fontWeight: 450, fontFamily: "var(--ff-text)" }}>prestations</span>
        </div>
      </div>
      <input type="range" min={1} max={40} step={1}
        value={extra} onChange={e => setExtra(+e.target.value)}
        style={{ width: "100%", accentColor: "var(--accent)", cursor: "pointer", marginTop: 10 }}/>

      <div style={{
        marginTop: 14, display: "grid",
        gridTemplateColumns: "repeat(3, 1fr)", gap: 10,
      }} className="app-col-3">
        {[
          { l: "CA ajouté",    v: fmtEUR(extra * avgTicket), sub: `${extra} × ${fmtEUR(avgTicket)}` },
          { l: "Nouveau total", v: fmtEUR(projected),        sub: pct >= 100 ? "objectif atteint 🎉" : `${Math.round(pct)}% de l'objectif` },
          { l: "Temps estimé",  v: `${hours} h`,             sub: "prestation + pause" },
        ].map((x, i) => (
          <div key={i} style={{
            padding: "12px 14px", background: "var(--bg-alt)",
            border: "1px solid var(--line)", borderRadius: 10,
          }}>
            <div style={{ fontSize: 11, color: "var(--ink-4)", fontWeight: 500 }}>{x.l}</div>
            <div style={{
              fontFamily: "var(--ff-display)", fontSize: 18, fontWeight: 580,
              letterSpacing: "-0.025em", marginTop: 4, fontVariantNumeric: "tabular-nums",
            }}>{x.v}</div>
            <div style={{ fontSize: 10.5, color: "var(--ink-4)", marginTop: 2 }}>{x.sub}</div>
          </div>
        ))}
      </div>
    </div>
  );
};

const PulseGoal = ({ goal, setGoal, monthPaid, pct, remaining, rdvToReach, avgTicket }) => (
  <div>
    <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", flexWrap: "wrap", gap: 8 }}>
      <div style={{ fontSize: 12, color: "var(--ink-4)", fontWeight: 500 }}>Objectif du mois</div>
      <div style={{
        fontFamily: "var(--ff-display)", fontSize: "clamp(22px, 3vw, 28px)",
        fontWeight: 580, letterSpacing: "-0.025em", color: "var(--accent-ink)",
      }}>
        {fmtEUR(goal)}
      </div>
    </div>
    <input type="range" min={1000} max={12000} step={100}
      value={goal} onChange={e => setGoal(+e.target.value)}
      style={{ width: "100%", accentColor: "var(--accent)", cursor: "pointer", marginTop: 8 }}/>

    <div style={{
      height: 12, background: "var(--bg-alt)", borderRadius: 999,
      overflow: "hidden", marginTop: 10,
    }}>
      <div style={{
        width: `${pct}%`, height: "100%",
        background: "linear-gradient(90deg, var(--accent) 0%, oklch(45% 0.22 288) 100%)",
        borderRadius: 999, transition: "width .4s ease",
      }}/>
    </div>
    <div style={{
      marginTop: 8, display: "flex", justifyContent: "space-between",
      fontSize: 12.5, color: "var(--ink-2)",
    }}>
      <span><strong style={{ fontVariantNumeric: "tabular-nums" }}>{fmtEUR(monthPaid)}</strong> encaissés</span>
      <span style={{ color: "var(--ink-4)" }}>
        {pct >= 100 ? "🎉 Objectif atteint" : `${Math.round(pct)}%`}
      </span>
    </div>
    <div style={{
      marginTop: 12, padding: "10px 12px",
      background: "var(--bg-alt)", borderRadius: 10,
      fontSize: 13, color: "var(--ink-2)", lineHeight: 1.5,
    }}>
      {pct >= 100
        ? <>Objectif dépassé de <strong>{fmtEUR(monthPaid - goal)}</strong> ✨</>
        : <>Encore <strong>{fmtEUR(remaining)}</strong> · environ {rdvToReach} prestations au panier moyen de {fmtEUR(avgTicket)}.</>
      }
    </div>
  </div>
);


/* ================================================================
   AGENDA — click cell to add, click appointment to delete / mark done
================================================================ */
const AppAgenda = (props) => {
  const isMobile = useIsMobile();
  return isMobile ? <AppAgendaMobile {...props}/> : <AppAgendaDesktop {...props}/>;
};

const AppAgendaMobile = ({ data, actions, openModal }) => {
  const todayOff = cbTodayOffset();
  const [day, setDay] = React.useState(todayOff);
  const [selected, setSelected] = React.useState(null);
  const stripRef = React.useRef(null);
  const activeRef = React.useRef(null);

  // Bande défilable : aujourd'hui + 52 jours suivants. Tous les indices
  // sont en OFFSET ABSOLU (jours depuis l'ancre 2026-01-01) — comparable
  // directement avec a.day stocké en DB.
  const DAYS_BEFORE = 0;
  const DAYS_AFTER = 52;
  const TOTAL_DAYS = DAYS_BEFORE + 1 + DAYS_AFTER;
  const anchor = (() => { const d = new Date(); d.setHours(0,0,0,0); return d; })();
  const DAY_NAMES_SHORT = ["Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam"];
  const MONTHS_SHORT = ["jan", "fév", "mar", "avr", "mai", "jui", "jui", "aoû", "sep", "oct", "nov", "déc"];

  const daysList = React.useMemo(() => Array.from({ length: TOTAL_DAYS }, (_, idx) => {
    const offset = idx - DAYS_BEFORE; // 0 = today
    const d = new Date(anchor);
    d.setDate(anchor.getDate() + offset);
    return {
      idx: todayOff + offset, // offset absolu — comparable avec a.day en DB
      label: DAY_NAMES_SHORT[d.getDay()],
      dayNum: d.getDate(),
      monthLabel: MONTHS_SHORT[d.getMonth()],
      isToday: offset === 0,
      isPast: offset < 0,
    };
  }), []);

  const dayAppts = data.appointments
    .filter(a => a.day === day)
    .sort((a, b) => a.h - b.h);

  const current = daysList.find(d => d.idx === day) || daysList[DAYS_BEFORE];

  // Center the active day on mount + when day changes
  React.useEffect(() => {
    if (activeRef.current && stripRef.current) {
      const el = activeRef.current;
      const strip = stripRef.current;
      strip.scrollTo({ left: el.offsetLeft - strip.clientWidth / 2 + el.clientWidth / 2, behavior: "smooth" });
    }
  }, [day]);

  return (
    <>
      <div style={{ marginBottom: 14, display: "flex", justifyContent: "space-between", alignItems: "center", gap: 10 }}>
        <div>
          <div style={{ fontFamily: "var(--ff-display)", fontSize: 18, fontWeight: 580, letterSpacing: "-0.02em" }}>
            {current.label} {current.dayNum} {current.monthLabel}
          </div>
          <div style={{ fontSize: 12.5, color: "var(--ink-4)", marginTop: 2 }}>
            {dayAppts.length} RDV ce jour · {data.appointments.length} au total
          </div>
        </div>
        {!current.isToday && (
          <button className="btn btn-sm btn-ghost" onClick={() => setDay(todayOff)} style={{ fontSize: 12 }}>
            Aujourd'hui
          </button>
        )}
      </div>

      {/* Day strip — scrolls through ~2 months */}
      <div ref={stripRef} style={{
        display: "flex", gap: 6, marginBottom: 14, overflowX: "auto",
        paddingBottom: 6, marginLeft: -4, marginRight: -4, paddingLeft: 4, paddingRight: 4,
        WebkitOverflowScrolling: "touch", scrollBehavior: "smooth",
      }}>
        {daysList.map((d) => {
          const n = data.appointments.filter(a => a.day === d.idx).length;
          const isActive = day === d.idx;
          return (
            <button key={d.idx} ref={isActive ? activeRef : null}
              onClick={() => { setDay(d.idx); setSelected(null); }} style={{
              padding: "8px 10px",
              background: isActive ? "var(--accent)" : d.isToday ? "var(--accent-soft)" : "var(--surface)",
              color: isActive ? "white" : d.isToday ? "var(--accent-ink)" : d.isPast ? "var(--ink-4)" : "var(--ink-2)",
              border: "1px solid " + (isActive ? "var(--accent)" : d.isToday ? "var(--accent-soft-2)" : "var(--line)"),
              borderRadius: 10, cursor: "pointer",
              flexShrink: 0, fontFamily: "inherit",
              display: "flex", flexDirection: "column", gap: 2, alignItems: "center",
              minWidth: 52,
              transition: "background .15s, border-color .15s",
            }}>
              <span style={{ fontSize: 10, opacity: isActive ? 0.9 : 1, color: isActive ? "white" : "var(--ink-4)" }}>
                {d.label}
              </span>
              <span style={{ fontFamily: "var(--ff-display)", fontSize: 16, fontWeight: 600, letterSpacing: "-0.02em" }}>
                {d.dayNum}
              </span>
              {n > 0 && (
                <span style={{
                  fontSize: 9, fontWeight: 600,
                  background: isActive ? "rgba(255,255,255,0.25)" : "var(--accent-soft)",
                  color: isActive ? "white" : "var(--accent-ink)",
                  padding: "1px 5px", borderRadius: 10, marginTop: 1,
                }}>{n}</span>
              )}
            </button>
          );
        })}
      </div>


      {/* Appointments list */}
      {dayAppts.length === 0 ? (
        <div style={{
          padding: "36px 20px", textAlign: "center",
          background: "var(--surface)", border: "1px dashed var(--line-strong)",
          borderRadius: 12, color: "var(--ink-4)",
        }}>
          <div style={{ fontSize: 14 }}>Aucun rendez-vous ce jour.</div>
          <button className="btn btn-sm btn-ghost" style={{ marginTop: 12 }} onClick={() => openModal("appointment", { day })}>
            <Icon name="plus" size={13}/> Ajouter le premier
          </button>
        </div>
      ) : (
        <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
          {dayAppts.map(a => {
            const c = findClient(data, a.clientId);
            const s = findService(data, a.serviceId);
            const col = COLOR_MAP[a.color] || COLOR_MAP.accent;
            const isSelected = selected === a.id;
            return (
              <div key={a.id} style={{
                background: "var(--surface)",
                border: "1px solid var(--line)",
                borderLeft: `3px solid ${col.border}`,
                borderRadius: 10,
                overflow: "hidden",
              }}>
                <button onClick={() => setSelected(isSelected ? null : a.id)} style={{
                  width: "100%", padding: "12px 14px",
                  display: "flex", alignItems: "center", gap: 12,
                  background: "transparent", border: "none", cursor: "pointer",
                  textAlign: "left", fontFamily: "inherit",
                }}>
                  <div style={{
                    minWidth: 58, padding: "6px 4px",
                    background: col.bg, color: col.ink,
                    borderRadius: 8, fontVariantNumeric: "tabular-nums", fontSize: 13,
                    fontWeight: 600, textAlign: "center", lineHeight: 1.25,
                  }}>
                    <div>{fmtH(a.h)}</div>
                    <div style={{ fontSize: 10, opacity: 0.7, marginTop: 1 }}>{fmtH(a.h + a.d)}</div>
                  </div>
                  {c && <Avatar client={c} size={36}/>}
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{
                      fontWeight: 520, fontSize: 14, color: "var(--ink)",
                      overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
                      textDecoration: a.done ? "line-through" : "none",
                      opacity: a.done ? 0.7 : 1,
                    }}>
                      {c ? c.name : "—"}
                    </div>
                    <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginTop: 2, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
                      {s ? s.name : ""}{s ? ` · ${fmtEUR(s.price)}` : ""}
                    </div>
                  </div>
                  {a.done ? (
                    <span style={{
                      padding: "3px 8px", fontSize: 10.5, fontWeight: 600,
                      background: "var(--sage-soft)", color: "oklch(38% 0.08 160)",
                      borderRadius: 999, textTransform: "uppercase", letterSpacing: "0.04em",
                      display: "inline-flex", alignItems: "center", gap: 4,
                    }}>
                      <Icon name="check" size={10} stroke={2.8}/> Fait
                    </span>
                  ) : (
                    <Icon name="arrow" size={14} style={{
                      color: "var(--ink-4)",
                      transform: isSelected ? "rotate(90deg)" : "rotate(0deg)",
                      transition: "transform 0.15s",
                    }}/>
                  )}
                </button>
                {isSelected && (
                  <div style={{
                    padding: "10px 14px 14px",
                    borderTop: "1px solid var(--line)",
                    background: "var(--bg-alt)",
                    display: "flex", gap: 8, flexWrap: "wrap",
                  }}>
                    {a.done ? (
                      <button className="btn btn-sm btn-ghost" style={{ flex: 1, minWidth: 150 }} onClick={() => { actions.undoAppointmentDone(a.id); setSelected(null); }}>
                        <Icon name="arrow" size={13} style={{ transform: "rotate(180deg)" }}/> Annuler la facturation
                      </button>
                    ) : (
                      <button className="btn btn-sm" style={{ flex: 1, minWidth: 150, background: "var(--ink)", color: "var(--bg)" }} onClick={() => { actions.markAppointmentDone(a.id); setSelected(null); }}>
                        <Icon name="check" size={13} stroke={2.4}/> Marquer fait & facturer
                      </button>
                    )}
                    <button className="btn btn-sm btn-ghost" style={{ color: "oklch(55% 0.18 25)" }} onClick={() => { actions.deleteAppointment(a.id); setSelected(null); }}>
                      Supprimer
                    </button>
                  </div>
                )}
              </div>
            );
          })}
        </div>
      )}

    </>
  );
};

const AppAgendaDesktop = ({ data, actions, openModal }) => {
  const hours = Array.from({ length: 11 }, (_, i) => i + 8); // 8–18
  const [selected, setSelected] = React.useState(null);

  return (
    <>
      <div style={{ display: "flex", gap: 8, marginBottom: 20, alignItems: "center" }}>
        <button className="btn btn-sm btn-ghost" aria-label="Semaine précédente">‹</button>
        <div style={{ fontFamily: "var(--ff-display)", fontSize: 17, fontWeight: 560, padding: "0 8px", letterSpacing: "-0.01em" }}>
          21 — 26 avril 2026
        </div>
        <button className="btn btn-sm btn-ghost" aria-label="Semaine suivante">›</button>
      </div>

      <div className="app-scroll-x" style={{ background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 12, overflow: "auto" }}>
        <div className="app-agenda-grid" style={{ display: "grid", gridTemplateColumns: "60px repeat(6, minmax(110px, 1fr))", borderBottom: "1px solid var(--line)", minWidth: 720 }}>
          <div/>
          {DAY_LABELS.map((d, i) => (
            <div key={d} style={{
              padding: "12px 14px",
              borderLeft: "1px solid var(--line)",
              fontSize: 12.5, fontWeight: 520,
              color: i === 1 ? "var(--accent-ink)" : "var(--ink-2)",
              background: i === 1 ? "var(--accent-soft)" : "transparent",
            }}>
              <div>{d.split(" ")[0]}</div>
              <div style={{ fontFamily: "var(--ff-display)", fontSize: 18, fontWeight: 580, letterSpacing: "-0.025em" }}>{d.split(" ")[1]}</div>
            </div>
          ))}
        </div>

        <div className="app-agenda-grid" style={{ display: "grid", gridTemplateColumns: "60px repeat(6, minmax(110px, 1fr))", position: "relative", minWidth: 720 }}>
          <div>
            {hours.map(h => (
              <div key={h} style={{
                height: 56, padding: "4px 8px", textAlign: "right",
                fontSize: 10.5, fontVariantNumeric: "tabular-nums", color: "var(--ink-4)",
                borderBottom: "1px solid var(--line)", fontWeight: 500,
              }}>{h}:00</div>
            ))}
          </div>
          {DAY_LABELS.map((d, dayIdx) => (
            <div key={d} style={{ position: "relative", borderLeft: "1px solid var(--line)" }}>
              {hours.map(h => (
                <div key={h} onClick={() => openModal("appointment", { day: _AGENDA_MONDAY_OFFSET + dayIdx, h })}
                  style={{
                    height: 56, borderBottom: "1px solid var(--line)", cursor: "pointer",
                  }}
                  onMouseEnter={e => e.currentTarget.style.background = "var(--bg-alt)"}
                  onMouseLeave={e => e.currentTarget.style.background = "transparent"}
                />
              ))}
              {data.appointments.filter(a => a.day === _AGENDA_MONDAY_OFFSET + dayIdx).map(a => {
                const c = findClient(data, a.clientId);
                const s = findService(data, a.serviceId);
                const col = COLOR_MAP[a.color] || COLOR_MAP.accent;
                const top = (a.h - 8) * 56;
                const height = a.d * 56 - 4;
                return (
                  <button key={a.id}
                    onClick={(e) => { e.stopPropagation(); setSelected(a.id); }}
                    style={{
                      position: "absolute", top: top + 2, left: 4, right: 4,
                      height, padding: "5px 8px",
                      background: col.bg,
                      border: "none",
                      borderLeft: `2.5px solid ${col.border}`,
                      borderRadius: 6,
                      fontSize: 11, overflow: "hidden", cursor: "pointer",
                      textAlign: "left", fontFamily: "inherit",
                      boxShadow: selected === a.id ? `0 0 0 2px ${col.border}, var(--sh-2)` : "none",
                      opacity: a.done ? 0.55 : 1,
                    }}>
                    <div style={{
                      fontWeight: 520, color: col.ink, fontSize: 11.5,
                      overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
                      textDecoration: a.done ? "line-through" : "none",
                    }}>
                      {a.done && "✓ "}{c ? c.name : "—"}
                    </div>
                    <div style={{ color: "var(--ink-3)", fontSize: 10.5, marginTop: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{s ? s.name : ""}</div>
                  </button>
                );
              })}
            </div>
          ))}
        </div>
      </div>

      {selected && (() => {
        const a = data.appointments.find(x => x.id === selected);
        if (!a) return null;
        const c = findClient(data, a.clientId);
        const s = findService(data, a.serviceId);
        return (
          <div style={{
            marginTop: 16, padding: 18,
            background: "var(--surface)", border: "1px solid var(--line)",
            borderRadius: 12, display: "flex", alignItems: "center", gap: 16, flexWrap: "wrap",
          }}>
            {c && <Avatar client={c} size={40}/>}
            <div style={{ flex: 1, minWidth: 200 }}>
              <div style={{ fontSize: 15, fontWeight: 520, display: "flex", alignItems: "center", gap: 8 }}>
                <span style={{ textDecoration: a.done ? "line-through" : "none", opacity: a.done ? 0.7 : 1 }}>{c ? c.name : "—"}</span>
                {a.done && (
                  <span style={{
                    padding: "2px 8px", fontSize: 10.5, fontWeight: 600,
                    background: "var(--sage-soft)", color: "oklch(38% 0.08 160)",
                    borderRadius: 999, textTransform: "uppercase", letterSpacing: "0.04em",
                  }}>✓ Facturé</span>
                )}
              </div>
              <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginTop: 2 }}>
                {(() => {
                  const d = cbDateForOffset(a.day);
                  return `${CB_FR_DAYS_SHORT[d.getDay()]} ${d.getDate()} ${CB_FR_MONTHS_SHORT[d.getMonth()]}`;
                })()} · {fmtH(a.h)} — {fmtH(a.h + a.d)} · {s ? s.name : ""}{s ? ` · ${fmtEUR(s.price)}` : ""}
              </div>
            </div>
            {a.done ? (
              <button className="btn btn-sm btn-ghost" onClick={() => { actions.undoAppointmentDone(a.id); setSelected(null); }}>
                <Icon name="arrow" size={13} style={{ transform: "rotate(180deg)" }}/> Annuler la facturation
              </button>
            ) : (
              <button className="btn btn-sm" style={{ background: "var(--ink)", color: "var(--bg)" }} onClick={() => { actions.markAppointmentDone(a.id); setSelected(null); }}>
                <Icon name="check" size={13} stroke={2.4}/> Marquer fait & facturer
              </button>
            )}
            <button className="btn btn-sm btn-ghost" onClick={() => { actions.deleteAppointment(a.id); setSelected(null); }}>
              Supprimer
            </button>
            <button className="btn btn-sm btn-ghost" onClick={() => setSelected(null)}>Fermer</button>
          </div>
        );
      })()}

    </>
  );
};

/* ================================================================
   CLIENTS — filter, add, edit, delete
================================================================ */
const CLIENT_TAG_COLORS = {
  "VIP":      { bg: "var(--warn-soft)", c: "var(--warn-ink)" },
  "Fidèle":   { bg: "var(--accent-soft)", c: "var(--accent-ink)" },
  "Nouvelle": { bg: "var(--sage-soft)",   c: "oklch(38% 0.08 160)" },
  "Inactive": { bg: "var(--bg-alt)",      c: "var(--ink-3)" },
};
const tagColor = (t) => CLIENT_TAG_COLORS[t] || { bg: "var(--bg-alt)", c: "var(--ink-3)" };

/* À-venir d'un client : RDV non terminés et dans le futur (day >= today). */
const clientUpcoming = (c, data) => {
  const todayOff = (typeof window !== "undefined" && window.cbTodayOffset) ? window.cbTodayOffset() : 0;
  let count = 0, planned = 0;
  for (const a of (data.appointments || [])) {
    if (a.clientId !== c.id) continue;
    if (a.done) continue;
    if (a.day < todayOff) continue;
    count++;
    const svc = (data.services || []).find(s => s.id === a.serviceId);
    if (svc) planned += Number(svc.price) || 0;
  }
  return { count, planned };
};

const ClientCardMobile = ({ c, data, actions, openModal }) => {
  const up = clientUpcoming(c, data);
  return (
  <div style={{
    background: "var(--surface)", border: "1px solid var(--line)",
    borderRadius: 12, padding: 14, display: "flex", flexDirection: "column", gap: 12,
  }}>
    <button onClick={() => openModal("client", { id: c.id })} style={{
      display: "flex", alignItems: "center", gap: 12,
      background: "transparent", border: "none", cursor: "pointer",
      padding: 0, textAlign: "left", fontFamily: "inherit", width: "100%",
    }}>
      <Avatar client={c} size={44}/>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontWeight: 520, fontSize: 15, color: "var(--ink)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{c.name}</div>
        <div style={{ fontSize: 12.5, color: "var(--ink-4)", marginTop: 2, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
          {c.email || c.phone || "Aucun contact"}
        </div>
      </div>
    </button>

    <div style={{
      display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 8,
      padding: "10px 0", borderTop: "1px solid var(--line)", borderBottom: "1px solid var(--line)",
    }}>
      <div>
        <div style={{ fontSize: 11, color: "var(--ink-4)", fontWeight: 500 }}>Visites</div>
        <div style={{ fontFamily: "var(--ff-display)", fontSize: 17, fontWeight: 580, marginTop: 2 }}>{c.visits}</div>
        {up.count > 0 && (
          <div style={{ fontSize: 10.5, color: "var(--accent-ink)", fontWeight: 580, marginTop: 1 }}>
            +{up.count} à venir
          </div>
        )}
      </div>
      <div>
        <div style={{ fontSize: 11, color: "var(--ink-4)", fontWeight: 500 }}>Dépensé</div>
        <div style={{ fontFamily: "var(--ff-display)", fontSize: 17, fontWeight: 580, marginTop: 2 }}>{fmtEUR(c.spent)}</div>
        {up.planned > 0 && (
          <div style={{ fontSize: 10.5, color: "var(--accent-ink)", fontWeight: 580, marginTop: 1 }}>
            +{fmtEUR(up.planned)} prévus
          </div>
        )}
      </div>
      <div>
        <div style={{ fontSize: 11, color: "var(--ink-4)", fontWeight: 500 }}>Fidélité</div>
        <div style={{ display: "flex", alignItems: "center", gap: 6, marginTop: 5 }}>
          <div style={{ flex: 1, height: 5, background: "var(--bg-alt)", borderRadius: 10, overflow: "hidden", minWidth: 24 }}>
            <div style={{ width: `${(c.fid / data.fideliteRules.visits) * 100}%`, height: "100%", background: "var(--accent)" }}/>
          </div>
          <span style={{ fontSize: 11.5, color: "var(--ink-3)", fontFamily: "inherit" }}>{c.fid}/{data.fideliteRules.visits}</span>
        </div>
      </div>
    </div>

    <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
      <div style={{ fontSize: 12, color: "var(--ink-4)" }}>
        <Icon name="clock" size={12} style={{ verticalAlign: "-2px", marginRight: 4 }}/>
        {c.last}
      </div>
      <div style={{ display: "flex", gap: 6 }}>
        {c.email && (
          <a href={`mailto:${c.email}`} className="btn btn-sm btn-ghost" aria-label="Envoyer un mail" style={{ height: 34, width: 34, padding: 0, color: "var(--ink-2)" }}>
            <Icon name="mail" size={14}/>
          </a>
        )}
        {c.phone && (
          <a href={`tel:${c.phone.replace(/\s+/g, "")}`} className="btn btn-sm btn-ghost" aria-label="Appeler" style={{ height: 34, width: 34, padding: 0, color: "var(--ink-2)" }}>
            <Icon name="phone" size={14}/>
          </a>
        )}
        <button onClick={() => openModal("client", { id: c.id })} className="btn btn-sm btn-ghost" style={{ height: 34, fontSize: 12.5 }}>Modifier</button>
        <button onClick={() => actions.deleteClient(c.id)} aria-label="Supprimer" className="btn btn-sm btn-ghost" style={{ height: 34, width: 34, padding: 0, color: "oklch(55% 0.18 25)" }}>
          <Icon name="close" size={14}/>
        </button>
      </div>
    </div>
  </div>
  );
};

/* Modale "Message groupé" : permet d'envoyer un email à plusieurs clients
   d'un coup (annonce, promo, fermeture exceptionnelle…). Utilise un mailto:
   avec les adresses en BCC pour que les destinataires ne se voient pas
   entre elles — pas de backend nécessaire. */
const BULK_TEMPLATES = [
  {
    label: "Promotion",
    subject: "Une petite promo pour vous",
    body: "Bonjour,\n\nPour vous remercier de votre fidélité, je vous propose ce mois-ci [VOTRE OFFRE].\nValable jusqu'au [DATE].\n\nÀ très vite,\n[VOTRE PRÉNOM]",
  },
  {
    label: "Nouveauté",
    subject: "Une nouveauté à découvrir",
    body: "Bonjour,\n\nJ'ai le plaisir de vous annoncer [NOUVELLE PRESTATION / NOUVEAU PRODUIT].\nN'hésitez pas à réserver pour découvrir.\n\nÀ très vite,\n[VOTRE PRÉNOM]",
  },
  {
    label: "Fermeture",
    subject: "Fermeture exceptionnelle",
    body: "Bonjour,\n\nJe vous informe que je serai fermé·e du [DATE] au [DATE].\nJe vous recontacte dès la réouverture pour reprogrammer si besoin.\n\nMerci de votre compréhension,\n[VOTRE PRÉNOM]",
  },
];

const BulkMessageModal = ({ open, onClose, clients, business }) => {
  const withEmail = (clients || []).filter(c => (c.email || "").trim());
  const [selected, setSelected] = React.useState({}); // { [id]: true }
  const [subject, setSubject] = React.useState("");
  const [body, setBody] = React.useState("");

  // À l'ouverture : tout coché par défaut
  React.useEffect(() => {
    if (!open) return;
    const all = {};
    withEmail.forEach(c => { all[c.id] = true; });
    setSelected(all);
    setSubject("");
    setBody("");
    // eslint-disable-next-line
  }, [open]);

  const toggleAll = (val) => {
    const next = {};
    withEmail.forEach(c => { next[c.id] = val; });
    setSelected(next);
  };
  const selectedCount = Object.values(selected).filter(Boolean).length;
  const selectedEmails = withEmail.filter(c => selected[c.id]).map(c => c.email);

  const applyTemplate = (t) => {
    setSubject(t.subject);
    setBody(t.body);
  };

  const send = () => {
    if (selectedEmails.length === 0) { showToast("Sélectionnez au moins un client", "warn"); return; }
    if (!subject.trim() || !body.trim()) { showToast("Sujet et message requis", "warn"); return; }
    const from = (business && business.contactEmail) || "";
    const params = new URLSearchParams();
    params.set("bcc", selectedEmails.join(","));
    params.set("subject", subject.trim());
    params.set("body", body.trim());
    // mailto: ouvre le client mail par défaut. Le pro envoie depuis sa propre adresse.
    const mailto = `mailto:${from}?${params.toString()}`;
    window.location.href = mailto;
    showToast(`Message ouvert pour ${selectedEmails.length} client${selectedEmails.length > 1 ? "s" : ""}`);
    onClose();
  };

  if (!open) return null;
  return (
    <Modal open={open} onClose={onClose}
      title={`Message groupé · ${selectedCount}/${withEmail.length}`}
      footer={
        <>
          <button className="btn btn-ghost" onClick={onClose}>Annuler</button>
          <button className="btn btn-primary" onClick={send} disabled={selectedCount === 0}>
            Ouvrir dans mon mail
          </button>
        </>
      }>
      <div style={{ display: "grid", gap: 14 }}>
        <div style={{
          padding: "10px 12px",
          background: "var(--accent-soft)", border: "1px solid var(--accent-soft-2)",
          borderRadius: 9, fontSize: 12.5, color: "var(--accent-ink)", lineHeight: 1.45,
        }}>
          ✦ Le message s'ouvrira dans votre application de mail. Les destinataires sont en
          <strong> copie cachée (BCC)</strong> — elles ne se verront pas entre elles.
        </div>

        {/* Templates */}
        <div>
          <div style={{ fontSize: 12.5, color: "var(--ink-3)", fontWeight: 500, marginBottom: 6 }}>
            Modèles rapides
          </div>
          <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
            {BULK_TEMPLATES.map(t => (
              <button key={t.label} onClick={() => applyTemplate(t)} style={{
                padding: "6px 12px", fontSize: 12.5, fontWeight: 500,
                background: "white", border: "1px solid var(--line)",
                borderRadius: 999, cursor: "pointer", fontFamily: "inherit",
                color: "var(--ink-2)",
              }}>
                {t.label}
              </button>
            ))}
          </div>
        </div>

        <FormField label="Sujet" value={subject} onChange={setSubject}
          placeholder="Une petite promo pour vous"/>
        <div>
          <label style={fieldLabelStyle}>Message</label>
          <textarea value={body} onChange={e => setBody(e.target.value)}
            placeholder="Bonjour,&#10;&#10;Je vous propose…"
            rows={8}
            style={{ ...fieldInputStyle, resize: "vertical", minHeight: 160 }}/>
        </div>

        {/* Recipients */}
        <div>
          <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 6 }}>
            <label style={fieldLabelStyle}>Destinataires ({selectedCount}/{withEmail.length})</label>
            <div style={{ display: "flex", gap: 6 }}>
              <button onClick={() => toggleAll(true)} className="btn btn-sm btn-ghost" style={{ height: 26, fontSize: 11.5 }}>
                Tout cocher
              </button>
              <button onClick={() => toggleAll(false)} className="btn btn-sm btn-ghost" style={{ height: 26, fontSize: 11.5 }}>
                Tout décocher
              </button>
            </div>
          </div>
          {withEmail.length === 0 ? (
            <div style={{
              padding: 16, textAlign: "center",
              background: "var(--bg-alt)", border: "1px dashed var(--line-strong)",
              borderRadius: 9, color: "var(--ink-3)", fontSize: 13,
            }}>
              Aucun client n'a d'email enregistré.
            </div>
          ) : (
            <div style={{
              maxHeight: 220, overflowY: "auto",
              background: "var(--bg-alt)", border: "1px solid var(--line)",
              borderRadius: 9, padding: 6,
            }}>
              {withEmail.map(c => {
                const checked = !!selected[c.id];
                return (
                  <label key={c.id} style={{
                    display: "flex", alignItems: "center", gap: 10,
                    padding: "7px 10px",
                    background: checked ? "var(--surface)" : "transparent",
                    borderRadius: 7, cursor: "pointer", fontSize: 13.5,
                  }}>
                    <input type="checkbox" checked={checked}
                      onChange={() => setSelected(s => ({ ...s, [c.id]: !checked }))}
                      style={{ width: 15, height: 15, accentColor: "var(--accent)", cursor: "pointer" }}/>
                    <span style={{ fontWeight: 520, color: "var(--ink)", flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
                      {c.name}
                    </span>
                    <span style={{ fontSize: 12, color: "var(--ink-4)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", maxWidth: 200 }}>
                      {c.email}
                    </span>
                  </label>
                );
              })}
            </div>
          )}
        </div>
      </div>
    </Modal>
  );
};

const AppClients = ({ data, actions, openModal }) => {
  const isMobile = useIsMobile();
  const [search, setSearch] = React.useState("");
  const [bulkOpen, setBulkOpen] = React.useState(false);

  const filtered = data.clients.filter(c => {
    if (!search.trim()) return true;
    const q = search.trim().toLowerCase();
    return c.name.toLowerCase().includes(q) || (c.email || "").toLowerCase().includes(q);
  });

  const clientsWithEmail = data.clients.filter(c => (c.email || "").trim());

  return (
    <>
      <div style={{ display: "flex", gap: 8, marginBottom: 14, alignItems: "center", flexWrap: "wrap" }}>
        <input value={search} onChange={e => setSearch(e.target.value)} placeholder="Rechercher un client…" style={{
          padding: "9px 12px", flex: 1, minWidth: 160,
          background: "var(--surface)", border: "1px solid var(--line)",
          borderRadius: 8, fontSize: 13.5, fontFamily: "inherit", color: "var(--ink)", outline: "none",
        }}/>
        {clientsWithEmail.length > 0 && (
          <button className="btn btn-sm btn-ghost"
            style={{ flexShrink: 0, border: "1px solid var(--line)" }}
            title={`Envoyer un message à ${clientsWithEmail.length} client${clientsWithEmail.length > 1 ? "s" : ""}`}
            onClick={() => setBulkOpen(true)}>
            <Icon name="mail" size={14}/>{isMobile ? "" : ` Message groupé`}
          </button>
        )}
        <button className="btn btn-sm" style={{ background: "var(--ink)", color: "var(--bg)", flexShrink: 0 }} onClick={() => openModal("client")}>
          <Icon name="plus" size={14}/>{isMobile ? "" : " Nouvelle"}
        </button>
      </div>

      <BulkMessageModal open={bulkOpen} onClose={() => setBulkOpen(false)}
        clients={data.clients} business={data.business || {}}/>

      {filtered.length === 0 ? (
        <div style={{ padding: 40, textAlign: "center", color: "var(--ink-4)", background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 12 }}>
          {search ? "Aucun client trouvée." : "Vous n'avez pas encore de clients. Ajoutez-en une pour commencer."}
        </div>
      ) : isMobile ? (
        <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
          {filtered.map(c => (
            <ClientCardMobile key={c.id} c={c} data={data} actions={actions} openModal={openModal}/>
          ))}
        </div>
      ) : (
        <div className="app-scroll-x" style={{ background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 12, overflow: "auto" }}>
          <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 13, minWidth: 640 }}>
            <thead>
              <tr style={{ background: "var(--bg-alt)", textAlign: "left" }}>
                {["Cliente", "Visites", "Total dépensé", "Fidélité", "Dernière visite", ""].map(h => (
                  <th key={h} style={{ padding: "12px 16px", fontSize: 12.5, fontWeight: 520, color: "var(--ink-3)", borderBottom: "1px solid var(--line)" }}>{h}</th>
                ))}
              </tr>
            </thead>
            <tbody>
              {filtered.map((c, i) => {
                const up = clientUpcoming(c, data);
                return (
                <tr key={c.id} style={{ borderBottom: i < filtered.length - 1 ? "1px solid var(--line)" : "none" }} className="cb-row-hover">
                  <td style={{ padding: "12px 16px" }}>
                    <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
                      <Avatar client={c}/>
                      <div>
                        <div style={{ fontWeight: 520, color: "var(--ink)" }}>{c.name}</div>
                        <div style={{ fontSize: 11.5, color: "var(--ink-4)" }}>{c.email || c.phone || "—"}</div>
                      </div>
                    </div>
                  </td>
                  <td style={{ padding: "12px 16px", fontFamily: "inherit", color: "var(--ink-2)" }}>
                    {c.visits}
                    {up.count > 0 && (
                      <span style={{ marginLeft: 6, fontSize: 11, color: "var(--accent-ink)", fontWeight: 580 }}>
                        +{up.count} à venir
                      </span>
                    )}
                  </td>
                  <td style={{ padding: "12px 16px", fontFamily: "inherit", fontWeight: 520 }}>
                    {fmtEUR(c.spent)}
                    {up.planned > 0 && (
                      <span style={{ marginLeft: 6, fontSize: 11, color: "var(--accent-ink)", fontWeight: 580 }}>
                        +{fmtEUR(up.planned)} prévus
                      </span>
                    )}
                  </td>
                  <td style={{ padding: "12px 16px" }}>
                    <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
                      <div style={{ width: 60, height: 5, background: "var(--bg-alt)", borderRadius: 10, overflow: "hidden" }}>
                        <div style={{ width: `${(c.fid / data.fideliteRules.visits) * 100}%`, height: "100%", background: "var(--accent)" }}/>
                      </div>
                      <span style={{ fontSize: 11.5, color: "var(--ink-3)", fontFamily: "inherit" }}>{c.fid}/{data.fideliteRules.visits}</span>
                    </div>
                  </td>
                  <td style={{ padding: "12px 16px", color: "var(--ink-3)" }}>{c.last}</td>
                  <td style={{ padding: "12px 16px", textAlign: "right", whiteSpace: "nowrap" }}>
                    {c.email && (
                      <a href={`mailto:${c.email}`} className="btn btn-sm btn-ghost" aria-label="Envoyer un mail" style={{ height: 28, width: 28, padding: 0, color: "var(--ink-3)" }}>
                        <Icon name="mail" size={13}/>
                      </a>
                    )}
                    {c.phone && (
                      <a href={`tel:${c.phone.replace(/\s+/g, "")}`} className="btn btn-sm btn-ghost" aria-label="Appeler" style={{ height: 28, width: 28, padding: 0, color: "var(--ink-3)", marginLeft: 4 }}>
                        <Icon name="phone" size={13}/>
                      </a>
                    )}
                    <button onClick={() => openModal("client", { id: c.id })} className="btn btn-sm btn-ghost" style={{ height: 28, fontSize: 12, marginLeft: 4 }}>Modifier</button>
                    <button onClick={() => actions.deleteClient(c.id)} aria-label="Supprimer" className="btn btn-sm btn-ghost" style={{ height: 28, width: 28, padding: 0, fontSize: 12, color: "oklch(55% 0.18 25)", marginLeft: 4 }}>
                      <Icon name="close" size={12}/>
                    </button>
                  </td>
                </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      )}
    </>
  );
};

/* ================================================================
   MESSAGES — send works
================================================================ */
const AppMessages = ({ data, actions }) => {
  const [draft, setDraft] = React.useState("");
  const active = data.activeConv;
  const client = active ? findClient(data, active) : null;
  const msgs = active ? (data.messages[active] || []) : [];
  const scrollRef = React.useRef(null);

  React.useEffect(() => {
    if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
  }, [msgs.length, active]);

  const send = () => {
    if (!draft.trim() || !active) return;
    actions.sendMessage(active, draft);
    setDraft("");
  };

  return (
    <div className="app-messages-grid" style={{ background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 12, overflow: "hidden", display: "grid", gridTemplateColumns: "280px 1fr 260px", height: "calc(100vh - 220px)" }}>
      <div className={"app-messages-list" + (active ? " has-active" : "")} style={{ borderRight: "1px solid var(--line)", overflow: "auto" }}>
        <div style={{ padding: "14px 16px", borderBottom: "1px solid var(--line)", fontSize: 13, fontWeight: 520 }}>
          Conversations <span style={{ color: "var(--ink-4)", fontWeight: 450 }}>({data.conversations.length})</span>
        </div>
        {data.conversations.map(conv => {
          const c = findClient(data, conv.clientId);
          if (!c) return null;
          const isActive = conv.clientId === active;
          return (
            <button key={conv.clientId} onClick={() => actions.openConv(conv.clientId)} style={{
              padding: "12px 16px",
              background: isActive ? "var(--accent-soft)" : "transparent",
              cursor: "pointer", display: "flex", gap: 10, alignItems: "center",
              width: "100%", textAlign: "left",
              border: "none", borderBottom: "1px solid var(--line)",
              fontFamily: "inherit",
            }}>
              <Avatar client={c} size={36}/>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
                  <div style={{ fontWeight: 520, fontSize: 13.5, color: "var(--ink)" }}>{c.name}</div>
                  <div style={{ fontSize: 11, color: "var(--ink-4)" }}>{conv.time}</div>
                </div>
                <div style={{ fontSize: 12.5, color: "var(--ink-3)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", marginTop: 2 }}>{conv.last}</div>
              </div>
              {conv.unread > 0 && <div style={{ background: "var(--accent)", color: "white", borderRadius: 10, padding: "1px 7px", fontSize: 11, fontWeight: 600 }}>{conv.unread}</div>}
            </button>
          );
        })}
      </div>

      <div className={"app-messages-thread" + (active ? " has-active" : "")} style={{ display: "flex", flexDirection: "column", background: "var(--bg)" }}>
        {client ? (
          <>
            <div style={{ padding: "14px 20px", borderBottom: "1px solid var(--line)", background: "var(--surface)", display: "flex", alignItems: "center", gap: 10 }}>
              <button onClick={() => actions.openConv(null)} className="app-messages-back" aria-label="Retour" style={{
                display: "none", background: "transparent", border: "none", cursor: "pointer",
                color: "var(--ink-2)", padding: 4,
              }}>
                <Icon name="arrow" size={18} style={{ transform: "rotate(180deg)" }}/>
              </button>
              <Avatar client={client} size={32}/>
              <div>
                <div style={{ fontWeight: 520, fontSize: 14 }}>{client.name}</div>
                <div style={{ fontSize: 11.5, color: "var(--sage)", display: "flex", alignItems: "center", gap: 4 }}>
                  <span style={{ width: 6, height: 6, borderRadius: 50, background: "var(--sage)" }}/> En ligne
                </div>
              </div>
            </div>
            <div ref={scrollRef} style={{ flex: 1, padding: 20, display: "flex", flexDirection: "column", gap: 8, overflow: "auto" }}>
              {msgs.map(m => (
                <div key={m.id} style={{
                  alignSelf: m.from === "me" ? "flex-end" : "flex-start",
                  maxWidth: "70%",
                  display: "flex", flexDirection: "column",
                  alignItems: m.from === "me" ? "flex-end" : "flex-start",
                }}>
                  <div style={{
                    padding: "8px 12px",
                    background: m.from === "me" ? "var(--accent)" : "var(--surface)",
                    color: m.from === "me" ? "white" : "var(--ink)",
                    border: m.from === "me" ? "none" : "1px solid var(--line)",
                    borderRadius: 14, fontSize: 13.5,
                  }}>{m.text}</div>
                  <div style={{ fontSize: 10.5, color: "var(--ink-4)", marginTop: 3, padding: "0 4px" }}>{m.time}</div>
                </div>
              ))}
            </div>
            <div style={{ padding: 14, borderTop: "1px solid var(--line)", background: "var(--surface)", display: "flex", gap: 8 }}>
              <input value={draft} onChange={e => setDraft(e.target.value)}
                onKeyDown={e => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); send(); } }}
                placeholder="Écrire un message…" style={{
                  flex: 1, padding: "10px 14px",
                  background: "var(--bg)", border: "1px solid var(--line-strong)",
                  borderRadius: 10, fontSize: 13, fontFamily: "inherit",
                  color: "var(--ink)", outline: "none",
                }}/>
              <button className="btn btn-sm" onClick={send} style={{ background: "var(--accent)", color: "white" }}>
                <Icon name="arrow" size={14}/> Envoyer
              </button>
            </div>
          </>
        ) : (
          <div style={{ flex: 1, display: "flex", alignItems: "center", justifyContent: "center", color: "var(--ink-4)", fontSize: 14 }}>
            Sélectionnez une conversation.
          </div>
        )}
      </div>

      <div className="app-messages-right" style={{ borderLeft: "1px solid var(--line)", padding: 20, background: "var(--surface)" }}>
        {client ? (
          <>
            <div style={{ textAlign: "center" }}>
              <div style={{ margin: "0 auto", display: "flex", justifyContent: "center" }}>
                <Avatar client={client} size={56}/>
              </div>
              <div style={{ marginTop: 10, fontWeight: 520 }}>{client.name}</div>
              <div style={{ fontSize: 12, color: "var(--ink-4)" }}>{client.visits > 10 ? "Cliente fidèle" : "Cliente"} · {client.visits} visites</div>
            </div>
            <div style={{ marginTop: 20, fontSize: 11.5, color: "var(--ink-4)", textTransform: "uppercase", letterSpacing: "0.06em", fontWeight: 520 }}>Informations</div>
            <div style={{ marginTop: 8, display: "flex", flexDirection: "column", gap: 6, fontSize: 12.5 }}>
              {client.email && <div style={{ display: "flex", justifyContent: "space-between" }}><span style={{ color: "var(--ink-4)" }}>Email</span><span style={{ fontWeight: 520 }}>{client.email}</span></div>}
              {client.phone && <div style={{ display: "flex", justifyContent: "space-between" }}><span style={{ color: "var(--ink-4)" }}>Téléphone</span><span style={{ fontFamily: "inherit", fontWeight: 520 }}>{client.phone}</span></div>}
              {client.social && (() => {
                const net = (data.bookingSettings && data.bookingSettings.preferredSocial) || "instagram";
                const url = cbSocialUrl(net, client.social);
                return (
                  <div style={{ display: "flex", justifyContent: "space-between" }}>
                    <span style={{ color: "var(--ink-4)" }}>{CB_SOCIAL_LABEL[net] || "Réseau"}</span>
                    {url ? (
                      <a href={url} target="_blank" rel="noopener noreferrer"
                        style={{ fontWeight: 520, color: "var(--accent-ink)", textDecoration: "none" }}>
                        {client.social}
                      </a>
                    ) : (
                      <span style={{ fontWeight: 520 }}>{client.social}</span>
                    )}
                  </div>
                );
              })()}
              {client.birthday && <div style={{ display: "flex", justifyContent: "space-between" }}><span style={{ color: "var(--ink-4)" }}>Anniversaire</span><span style={{ fontFamily: "inherit", fontWeight: 520 }}>{client.birthday}</span></div>}
              <div style={{ display: "flex", justifyContent: "space-between" }}><span style={{ color: "var(--ink-4)" }}>Total dépensé</span><span style={{ fontFamily: "inherit", fontWeight: 520 }}>{fmtEUR(client.spent)}</span></div>
              <div style={{ display: "flex", justifyContent: "space-between" }}><span style={{ color: "var(--ink-4)" }}>Fidélité</span><span style={{ fontFamily: "inherit", fontWeight: 520, color: "var(--accent-ink)" }}>{client.fid}/{data.fideliteRules.visits}</span></div>
            </div>
          </>
        ) : (
          <div style={{ color: "var(--ink-4)", fontSize: 13 }}>—</div>
        )}
      </div>
    </div>
  );
};

/* ================================================================
   FIDÉLITÉ — edit rules, add visits
================================================================ */
const FideliteClientRow = ({ c, data, actions }) => {
  const [copied, setCopied] = React.useState(false);
  const total = data.fideliteRules.visits;
  const remaining = Math.max(0, total - c.fid);
  const pct = Math.min(100, (c.fid / total) * 100);

  const cardUrl = `${window.location.origin}${window.location.pathname}?card=${cbAuth.getCurrentUser()?.bookingSlug || ""}&cli=${c.id}`;

  const copyLink = async () => {
    try {
      if (navigator.clipboard) await navigator.clipboard.writeText(cardUrl);
      else {
        const ta = document.createElement("textarea");
        ta.value = cardUrl; document.body.appendChild(ta); ta.select();
        document.execCommand("copy"); document.body.removeChild(ta);
      }
      setCopied(true);
      setTimeout(() => setCopied(false), 1800);
      showToast("Lien de la carte copié");
    } catch (e) {
      showToast("Copie impossible — sélectionnez à la main", "warn");
    }
  };

  return (
    <div style={{
      padding: 14, background: "var(--surface)",
      border: "1px solid var(--line)", borderRadius: 12,
      display: "flex", flexDirection: "column", gap: 12,
    }}>
      <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
        <Avatar client={c} size={40}/>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontWeight: 520, fontSize: 14.5, color: "var(--ink)" }}>{c.name}</div>
          <div style={{ fontSize: 12.5, color: "var(--ink-4)", marginTop: 1 }}>
            {c.fid === total
              ? "🎁 Récompense prête"
              : remaining === 1
                ? "Plus qu'une visite !"
                : `Encore ${remaining} visites avant la récompense`}
          </div>
        </div>
        <div style={{
          fontFamily: "inherit", fontSize: 13, fontWeight: 600,
          color: c.fid === total ? "var(--sage)" : "var(--accent-ink)",
          padding: "4px 10px",
          background: c.fid === total ? "var(--sage-soft)" : "var(--accent-soft)",
          borderRadius: 6,
        }}>{c.fid}/{total}</div>
      </div>

      {/* Progress bar */}
      <div style={{ height: 6, background: "var(--bg-alt)", borderRadius: 999, overflow: "hidden" }}>
        <div style={{
          width: `${pct}%`, height: "100%",
          background: c.fid === total ? "var(--sage)" : "var(--accent)",
          borderRadius: 999, transition: "width 0.3s",
        }}/>
      </div>

      {/* Actions */}
      <div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
        <div style={{
          display: "flex", alignItems: "center", gap: 2,
          border: "1px solid var(--line)", borderRadius: 8, padding: 2,
          background: "var(--bg-alt)",
        }}>
          <button onClick={() => actions.adjustFidelite(c.id, -1)} disabled={c.fid <= 0} style={{
            width: 30, height: 30, background: "transparent", border: "none",
            cursor: c.fid > 0 ? "pointer" : "not-allowed",
            color: "var(--ink-3)", fontSize: 16, borderRadius: 6,
            opacity: c.fid > 0 ? 1 : 0.4,
          }}>−</button>
          <button onClick={() => actions.adjustFidelite(c.id, +1)} disabled={c.fid >= total} style={{
            width: 30, height: 30, background: "transparent", border: "none",
            cursor: c.fid < total ? "pointer" : "not-allowed",
            color: "var(--accent)", fontSize: 16, borderRadius: 6,
            opacity: c.fid < total ? 1 : 0.4,
          }}>+</button>
        </div>
        <button onClick={copyLink} className="btn btn-sm btn-ghost" style={{ height: 34, fontSize: 12.5 }}>
          <Icon name={copied ? "check" : "link"} size={12}/> {copied ? "Copié" : "Lien"}
        </button>
        {c.fid >= total && (
          <button onClick={() => {
            if (!window.confirm(`Remettre la carte de ${c.name} à zéro ? (Récompense offerte)`)) return;
            actions.resetFidelite(c.id);
          }} className="btn btn-sm" style={{ height: 34, fontSize: 12.5, background: "var(--sage)", color: "white" }}>
            Offrir & réinitialiser
          </button>
        )}
      </div>
    </div>
  );
};

const AppFidelite = ({ data, actions, openModal }) => {
  const [search, setSearch] = React.useState("");
  const filtered = data.clients.filter(c => {
    if (!search.trim()) return true;
    return c.name.toLowerCase().includes(search.trim().toLowerCase());
  });
  const sorted = [...filtered].sort((a, b) => (b.fid || 0) - (a.fid || 0));

  return (
    <div>
      {/* Rules banner */}
      <div style={{
        padding: "14px 18px", background: "var(--surface)",
        border: "1px solid var(--line)", borderRadius: 12, marginBottom: 16,
        display: "flex", alignItems: "center", gap: 14, flexWrap: "wrap",
      }}>
        <div style={{
          width: 44, height: 44, borderRadius: 10,
          background: "linear-gradient(135deg, var(--accent) 0%, oklch(45% 0.22 288) 100%)",
          color: "white", display: "flex", alignItems: "center", justifyContent: "center",
          flexShrink: 0,
        }}>
          <Icon name="gift" size={22}/>
        </div>
        <div style={{ flex: 1, minWidth: 200 }}>
          <div style={{ fontWeight: 520, fontSize: 14.5 }}>Programme de fidélité</div>
          <div style={{ fontSize: 13, color: "var(--ink-3)", marginTop: 2 }}>
            {data.fideliteRules.visits} visites = {data.fideliteRules.rewardLabel}
          </div>
        </div>
        <button className="btn btn-sm btn-ghost" onClick={() => openModal("fidelite-rules")}>
          Modifier
        </button>
      </div>

      {/* Search */}
      <input value={search} onChange={e => setSearch(e.target.value)}
        placeholder="Rechercher un client…" style={{
        padding: "9px 12px", width: "100%", marginBottom: 14,
        background: "var(--surface)", border: "1px solid var(--line)",
        borderRadius: 8, fontSize: 13.5, fontFamily: "inherit", color: "var(--ink)", outline: "none",
      }}/>

      {/* Client cards list */}
      {sorted.length === 0 ? (
        <div style={{ padding: 40, textAlign: "center", color: "var(--ink-4)", background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 12 }}>
          Aucun client trouvée.
        </div>
      ) : (
        <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
          {sorted.map(c => <FideliteClientRow key={c.id} c={c} data={data} actions={actions}/>)}
        </div>
      )}
    </div>
  );
};

/* ================================================================
   PROMOS — add, cycle status, delete
================================================================ */
const AppPromos = ({ data, actions, openModal }) => (
  <div>
    <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16 }}>
      <h3 style={{ fontSize: 16 }}>Campagnes en cours</h3>
      <button className="btn btn-sm" style={{ background: "var(--ink)", color: "var(--bg)" }} onClick={() => openModal("promo")}>
        <Icon name="plus" size={14}/> Nouvelle promotion
      </button>
    </div>
    <div className="app-col-3" style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 16, marginBottom: 20 }}>
      {data.promos.map(p => {
        const pColor = {
          "Actif":     { bg: "var(--sage-soft)", c: "oklch(38% 0.08 160)" },
          "Programmé": { bg: "var(--accent-soft)", c: "var(--accent-ink)" },
          "Brouillon": { bg: "var(--bg-alt)", c: "var(--ink-3)" },
        }[p.status];
        return (
          <div key={p.id} style={{ padding: 20, background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 12 }}>
            <div style={{ display: "flex", justifyContent: "space-between", alignItems: "start" }}>
              <Icon name="tag" size={20} style={{ color: "var(--ink-3)" }}/>
              <button onClick={() => actions.togglePromoStatus(p.id)}
                style={{ padding: "3px 8px", fontSize: 10.5, fontWeight: 600, background: pColor.bg, color: pColor.c, border: "none", borderRadius: 4, cursor: "pointer", textTransform: "uppercase", letterSpacing: "0.04em", fontFamily: "inherit" }}>
                {p.status}
              </button>
            </div>
            <h4 style={{ marginTop: 14, fontSize: 15 }}>{p.title}</h4>
            <div style={{ marginTop: 4, fontSize: 12.5, color: "var(--ink-3)" }}>{p.segment}</div>
            <div style={{ marginTop: 14, padding: "8px 10px", background: "var(--bg-alt)", borderRadius: 8, fontSize: 12, color: "var(--ink-2)", fontFamily: "inherit" }}>{p.detail}</div>
            <button onClick={() => actions.deletePromo(p.id)} className="btn btn-sm btn-ghost" style={{ marginTop: 10, width: "100%", height: 28, fontSize: 12, color: "oklch(55% 0.18 25)" }}>
              Supprimer
            </button>
          </div>
        );
      })}
    </div>
    <div style={{ background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 12, padding: 24 }}>
      <h3 style={{ fontSize: 16 }}>Performances des 30 derniers jours</h3>
      <div className="app-kpi-4" style={{ marginTop: 16, display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 16 }}>
        {[["184", "SMS envoyés"], ["43", "Conversions"], ["23%", "Taux de conversion"], ["+612 €", "CA généré"]].map(([v, l]) => (
          <div key={l}>
            <div style={{ fontSize: 12, color: "var(--ink-4)", fontWeight: 500 }}>{l}</div>
            <div style={{ fontFamily: "var(--ff-display)", fontSize: 26, fontWeight: 580, letterSpacing: "-0.025em", marginTop: 4 }}>{v}</div>
          </div>
        ))}
      </div>
    </div>
  </div>
);

/* ================================================================
   STATS — desktop: full dashboard / mobile: one simple tabbed block
================================================================ */
const AppStats = (props) => {
  const isMobile = useIsMobile();
  return isMobile ? <AppStatsMobile {...props}/> : <AppStatsDesktop {...props}/>;
};

/* ====== Helpers stats (données réelles uniquement) ====== */
const _CB_FR_M_SHORT = ["Janv","Févr","Mars","Avril","Mai","Juin","Juil","Août","Sept","Oct","Nov","Déc"];
const _statsForData = (data) => {
  const todayOff = (window.cbTodayOffset && window.cbTodayOffset()) || 0;
  const today = new Date();
  const services = data.services || [];
  const appointments = data.appointments || [];
  const priceFor = (sid) => {
    const s = services.find(x => x.id === sid);
    return s ? Number(s.price) || 0 : 0;
  };

  // Reviens en arrière sur 6 mois (mois courant inclus)
  const months = [];
  for (let k = 5; k >= 0; k--) {
    const d = new Date(today.getFullYear(), today.getMonth() - k, 1);
    months.push({
      key: `${d.getFullYear()}-${d.getMonth()}`,
      label: _CB_FR_M_SHORT[d.getMonth()],
      year: d.getFullYear(),
      month: d.getMonth(),
      v: 0,        // CA réalisé (RDV done)
      count: 0,    // nb RDV done
    });
  }

  // Agrège les RDV done par mois
  for (const a of appointments) {
    if (!a.done) continue;
    const d = window.cbDateForOffset ? window.cbDateForOffset(a.day) : new Date();
    const k = `${d.getFullYear()}-${d.getMonth()}`;
    const m = months.find(x => x.key === k);
    if (m) { m.v += priceFor(a.serviceId); m.count += 1; }
  }

  const thisMonth = months[months.length - 1];
  const lastMonth = months[months.length - 2];
  const thisMonthCA = thisMonth ? thisMonth.v : 0;
  const lastMonthCA = lastMonth ? lastMonth.v : 0;
  const rdvDoneThisMonth = thisMonth ? thisMonth.count : 0;
  const panier = rdvDoneThisMonth > 0 ? thisMonthCA / rdvDoneThisMonth : 0;
  const deltaPct = lastMonthCA > 0
    ? Math.round(((thisMonthCA - lastMonthCA) / lastMonthCA) * 100)
    : (thisMonthCA > 0 ? 100 : 0);

  // Panier moyen mois précédent — pour comparer
  const lastPanier = lastMonth && lastMonth.count > 0 ? lastMonth.v / lastMonth.count : 0;
  const deltaPanier = panier - lastPanier;

  // Top prestations — compte RDV done par service (vrais chiffres)
  const byService = {};
  for (const a of appointments) {
    if (!a.done) continue;
    const s = services.find(x => x.id === a.serviceId);
    if (!s) continue;
    const e = byService[s.id] || (byService[s.id] = { name: s.name, count: 0, rev: 0 });
    e.count += 1;
    e.rev += Number(s.price) || 0;
  }
  const topPrestations = Object.values(byService)
    .sort((a, b) => b.rev - a.rev)
    .slice(0, 5);

  // No-show : RDV passés (day < today) marqués non-done
  let noShow = 0, eligible = 0;
  for (const a of appointments) {
    if (a.day >= todayOff) continue;
    eligible += 1;
    if (!a.done) noShow += 1;
  }
  const noShowPct = eligible > 0 ? Math.round((noShow / eligible) * 1000) / 10 : 0;

  // Le compte est-il vide ?
  const isEmpty = appointments.length === 0;

  return { months, thisMonthCA, lastMonthCA, deltaPct, rdvDoneThisMonth, panier, deltaPanier, topPrestations, noShowPct, isEmpty };
};

const StatsEmptyState = () => (
  <div style={{
    padding: "40px 24px", textAlign: "center",
    background: "var(--surface)", border: "1px solid var(--line)",
    borderRadius: 14,
  }}>
    <div style={{ fontSize: 36, marginBottom: 10 }}>📊</div>
    <div style={{ fontSize: 16, fontWeight: 580, color: "var(--ink)", marginBottom: 6 }}>
      Pas encore de statistiques
    </div>
    <div style={{ fontSize: 13.5, color: "var(--ink-3)", lineHeight: 1.5, maxWidth: 380, margin: "0 auto" }}>
      Vos chiffres apparaîtront ici dès que vous aurez des rendez-vous marqués
      comme « faits » dans l'agenda.
    </div>
  </div>
);

const AppStatsMobile = ({ data }) => {
  const stats = React.useMemo(() => _statsForData(data), [data]);
  if (stats.isEmpty) return <StatsEmptyState/>;

  const series = stats.months.map(m => ({ m: m.label, v: m.v }));
  const [active, setActive] = React.useState(series.length - 1);
  const current = series[active] || series[series.length - 1];
  const maxVal = Math.max(1, ...series.map(d => d.v));
  const prev = active > 0 ? series[active - 1].v : 0;
  const delta = current.v - prev;
  const deltaPct = prev > 0 ? Math.round((delta / prev) * 100) : (current.v > 0 ? 100 : 0);
  const total = series.reduce((s, d) => s + d.v, 0);

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
      {/* Hero CA — uniquement le mois courant, données réelles */}
      <div style={{
        padding: "22px 20px",
        background: "linear-gradient(135deg, var(--accent) 0%, oklch(45% 0.22 288) 100%)",
        borderRadius: 16, color: "white",
      }}>
        <div style={{ fontSize: 12, opacity: 0.85, textTransform: "uppercase", letterSpacing: "0.06em" }}>
          CA · {current.m}
        </div>
        <div style={{
          fontFamily: "var(--ff-display)", fontSize: "clamp(32px, 7vw, 44px)",
          fontWeight: 580, letterSpacing: "-0.03em", marginTop: 4,
          fontVariantNumeric: "tabular-nums",
        }}>{fmtEUR(current.v)}</div>
        {prev > 0 && (
          <div style={{
            marginTop: 10, display: "inline-flex", alignItems: "center", gap: 5,
            fontSize: 12.5, background: "rgba(255,255,255,0.18)",
            padding: "4px 10px", borderRadius: 999,
          }}>
            <Icon name="arrowUp" size={11} stroke={2.4}
              style={{ transform: delta < 0 ? "rotate(180deg)" : "none" }}/>
            {delta >= 0 ? "+" : ""}{deltaPct}% vs mois précédent
          </div>
        )}
      </div>

      {/* Interactive chart — 6 mois réels */}
      <div style={{ padding: 18, background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 14 }}>
        <div style={{ fontSize: 12.5, color: "var(--ink-4)", fontWeight: 500, marginBottom: 14 }}>
          6 derniers mois — touchez une barre
        </div>
        <div style={{ display: "flex", alignItems: "flex-end", gap: 6, height: 140 }}>
          {series.map((d, i) => {
            const h = (d.v / maxVal) * 120;
            const isActive = active === i;
            return (
              <button key={i} onClick={() => setActive(i)}
                style={{
                  flex: 1, display: "flex", flexDirection: "column", alignItems: "center", gap: 6,
                  background: "transparent", border: "none", padding: 0, cursor: "pointer", fontFamily: "inherit",
                }}>
                <div style={{
                  width: "100%", height: Math.max(h, 2),
                  background: isActive
                    ? "linear-gradient(to top, var(--accent) 0%, oklch(65% 0.2 288) 100%)"
                    : "var(--accent-soft-2)",
                  borderRadius: 6, transition: "background .2s, height .25s cubic-bezier(0.22, 1, 0.36, 1)",
                }}/>
                <div style={{
                  fontSize: 10.5, fontWeight: isActive ? 600 : 500,
                  color: isActive ? "var(--accent-ink)" : "var(--ink-4)",
                }}>{d.m}</div>
              </button>
            );
          })}
        </div>
        <div style={{
          marginTop: 16, paddingTop: 14, borderTop: "1px solid var(--line)",
          display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12,
        }}>
          <MobileStat label="Panier moyen" value={fmtEUR(stats.panier)}/>
          <MobileStat label="RDV faits ce mois" value={stats.rdvDoneThisMonth}/>
          <MobileStat label="Total 6 mois" value={fmtEUR(total)}/>
          <MobileStat label="Taux no-show" value={stats.noShowPct + "%"}/>
        </div>
      </div>
    </div>
  );
};

const MobileStat = ({ label, value }) => (
  <div>
    <div style={{ fontSize: 11.5, color: "var(--ink-4)", fontWeight: 500 }}>{label}</div>
    <div style={{
      fontFamily: "var(--ff-display)", fontSize: 20, fontWeight: 580,
      letterSpacing: "-0.025em", marginTop: 2, fontVariantNumeric: "tabular-nums",
    }}>{value}</div>
  </div>
);

const AppStatsDesktop = ({ data }) => {
  const stats = React.useMemo(() => _statsForData(data), [data]);
  const [hoverMonth, setHoverMonth] = React.useState(null);
  const [hoverPresta, setHoverPresta] = React.useState(null);

  if (stats.isEmpty) return <StatsEmptyState/>;

  const monthlyData = stats.months.map(m => ({ m: m.label, v: m.v }));
  const maxMonth = Math.max(1, ...monthlyData.map(d => d.v));
  const topPrestations = stats.topPrestations.map(p => [p.name, p.rev, p.count]);
  const maxRev = Math.max(1, ...topPrestations.map(p => p[1]));

  return (
    <div>
      {/* Hero stat */}
      <div style={{
        padding: "24px 28px", marginBottom: 16,
        background: "linear-gradient(135deg, var(--accent) 0%, oklch(45% 0.22 288) 100%)",
        borderRadius: 16, color: "white",
        display: "flex", justifyContent: "space-between", alignItems: "center", gap: 16, flexWrap: "wrap",
      }}>
        <div>
          <div style={{ fontSize: 12, opacity: 0.85, textTransform: "uppercase", letterSpacing: "0.06em" }}>Chiffre d'affaires ce mois</div>
          <div style={{
            fontFamily: "var(--ff-display)", fontSize: "clamp(32px, 4vw, 44px)",
            fontWeight: 580, letterSpacing: "-0.03em", marginTop: 4,
          }}>{fmtEUR(stats.thisMonthCA)}</div>
          {stats.lastMonthCA > 0 && (
            <div style={{ marginTop: 8, display: "inline-flex", alignItems: "center", gap: 5, fontSize: 13, background: "rgba(255,255,255,0.18)", padding: "4px 10px", borderRadius: 999 }}>
              <Icon name="arrowUp" size={12} stroke={2.4} style={{ transform: stats.deltaPct < 0 ? "rotate(180deg)" : "none" }}/>
              {stats.deltaPct >= 0 ? "+" : ""}{stats.deltaPct}% vs mois dernier
            </div>
          )}
        </div>
        <div style={{
          display: "flex", gap: 4, alignItems: "flex-end", height: 60,
          opacity: 0.95, flexShrink: 0,
        }}>
          {monthlyData.map((d, i) => (
            <div key={i} style={{
              width: 12, height: `${(d.v / maxMonth) * 100}%`,
              background: i === 5 ? "rgba(255,255,255,0.95)" : "rgba(255,255,255,0.4)",
              borderRadius: 3,
            }}/>
          ))}
        </div>
      </div>

      {/* Secondary KPIs — données réelles */}
      <div className="app-kpi-4" style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 10, marginBottom: 18 }}>
        {[
          { l: "RDV faits ce mois", v: stats.rdvDoneThisMonth, d: null, icon: "calendar", tone: "sage" },
          { l: "Panier moyen",      v: fmtEUR(stats.panier),
            d: stats.deltaPanier !== 0 && Math.abs(stats.deltaPanier) >= 0.5
                ? `${stats.deltaPanier > 0 ? "+" : ""}${fmtEUR(stats.deltaPanier)}`
                : null,
            icon: "euro", tone: "accent" },
          { l: "Taux no-show",      v: stats.noShowPct + "%", d: null, icon: "clock", tone: "warn" },
        ].map((k, i) => {
          const tones = {
            sage:   { bg: "var(--sage-soft)",   c: "oklch(38% 0.08 160)" },
            accent: { bg: "var(--accent-soft)", c: "var(--accent-ink)" },
            warn:   { bg: "oklch(97% 0.025 30)",c: "oklch(42% 0.14 30)" },
          }[k.tone];
          return (
            <div key={i} style={{
              padding: 18, background: "var(--surface)", border: "1px solid var(--line)",
              borderRadius: 14, display: "flex", flexDirection: "column", gap: 10,
            }}>
              <div style={{
                width: 32, height: 32, borderRadius: 9,
                background: tones.bg, color: tones.c,
                display: "flex", alignItems: "center", justifyContent: "center",
              }}>
                <Icon name={k.icon} size={15}/>
              </div>
              <div>
                <div style={{ fontFamily: "var(--ff-display)", fontSize: 26, fontWeight: 580, letterSpacing: "-0.03em" }}>{k.v}</div>
                <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginTop: 2 }}>{k.l}</div>
                {k.d && (
                  <div style={{ marginTop: 6, display: "inline-flex", alignItems: "center", gap: 4, fontSize: 11.5, color: "var(--sage)", background: "var(--sage-soft)", padding: "2px 8px", borderRadius: 999 }}>
                    <Icon name="arrowUp" size={10} stroke={2.4} style={{ transform: k.d.startsWith("-") ? "rotate(180deg)" : "none" }}/> {k.d}
                  </div>
                )}
              </div>
            </div>
          );
        })}
      </div>

      {/* Chart + top services */}
      <div className="app-split" style={{ display: "grid", gridTemplateColumns: "1.4fr 1fr", gap: 16 }}>
        <div style={{ padding: 22, background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 14, position: "relative" }}>
          <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16, gap: 8, flexWrap: "wrap" }}>
            <h3 style={{ fontSize: 15 }}>Chiffre d'affaires</h3>
            <div style={{ fontSize: 12, color: "var(--ink-4)", fontWeight: 500 }}>6 derniers mois</div>
          </div>

          {/* Tooltip */}
          {hoverMonth != null && (
            <div style={{
              position: "absolute", top: 56,
              left: `${((40 + hoverMonth * 76 + 24) / 500) * 100}%`,
              transform: "translateX(-50%)",
              background: "var(--ink)", color: "var(--bg)",
              padding: "6px 10px", borderRadius: 6,
              fontSize: 12, fontWeight: 500,
              pointerEvents: "none",
              whiteSpace: "nowrap",
              zIndex: 2,
            }}>
              {monthlyData[hoverMonth].m} · <strong>{fmtEUR(monthlyData[hoverMonth].v)}</strong>
            </div>
          )}

          <svg viewBox="0 0 500 220" style={{ width: "100%", height: 200 }}>
            <defs>
              <linearGradient id="bar" x1="0" x2="0" y1="0" y2="1">
                <stop offset="0%" stopColor="var(--accent)" stopOpacity="1"/>
                <stop offset="100%" stopColor="var(--accent)" stopOpacity="0.3"/>
              </linearGradient>
            </defs>
            {/* Gridlines basés sur le max réel */}
            {[0, 0.33, 0.66, 1].map((p) => {
              const y = 190 - p * 160;
              return <line key={p} x1="30" x2="490" y1={y} y2={y} stroke="var(--line)" strokeDasharray="2,4"/>;
            })}
            {monthlyData.map((d, i) => {
              const h = (d.v / maxMonth) * 160;
              const x = 40 + i * 76;
              const isLast = i === monthlyData.length - 1;
              const isHover = hoverMonth === i;
              return (
                <g key={i} onMouseEnter={() => setHoverMonth(i)} onMouseLeave={() => setHoverMonth(null)}
                   style={{ cursor: "pointer" }}>
                  {/* Invisible wider hit-area */}
                  <rect x={x - 14} y={30} width={76} height={170} fill="transparent"/>
                  <rect x={x} y={190 - h} width={48} height={h} rx={6}
                    fill={isLast ? "url(#bar)" : (isHover ? "var(--accent)" : "var(--accent-soft-2)")}
                    style={{ transition: "fill 0.15s" }}/>
                  <text x={x + 24} y={208} fontSize="11" fill={isHover ? "var(--accent-ink)" : "var(--ink-3)"} textAnchor="middle" fontWeight={isLast || isHover ? "600" : "500"}>
                    {d.m}
                  </text>
                </g>
              );
            })}
          </svg>
        </div>

        <div style={{ padding: 22, background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 14 }}>
          <h3 style={{ fontSize: 15, marginBottom: 16 }}>Top prestations</h3>
          <div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
            {topPrestations.map(([name, rev, count], i) => {
              const isHover = hoverPresta === i;
              const pct = (rev / maxRev) * 100;
              return (
                <div key={name}
                  onMouseEnter={() => setHoverPresta(i)}
                  onMouseLeave={() => setHoverPresta(null)}
                  style={{
                    padding: "8px 10px",
                    background: isHover ? "var(--bg-alt)" : "transparent",
                    borderRadius: 8, cursor: "pointer",
                    transition: "background 0.15s",
                  }}>
                  <div style={{ display: "flex", justifyContent: "space-between", fontSize: 13, marginBottom: 5, gap: 8 }}>
                    <span style={{ color: "var(--ink)", fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
                      <span style={{ color: "var(--ink-4)", marginRight: 6, fontWeight: 500 }}>#{i + 1}</span>
                      {name}
                    </span>
                    <span style={{ fontFamily: "inherit", fontWeight: 600, flexShrink: 0, color: isHover ? "var(--accent-ink)" : "var(--ink)" }}>{fmtEUR(rev)}</span>
                  </div>
                  <div style={{ height: 6, background: "var(--bg-alt)", borderRadius: 10, overflow: "hidden" }}>
                    <div style={{
                      width: `${pct}%`, height: "100%",
                      background: (isHover || i === 0) ? "var(--accent)" : "var(--accent-soft-2)",
                      borderRadius: 10,
                      transition: "background 0.15s",
                    }}/>
                  </div>
                  {isHover && (
                    <div style={{ fontSize: 11.5, color: "var(--ink-4)", marginTop: 4 }}>
                      {count} prestation{count > 1 ? "s" : ""} · moy. {fmtEUR(rev / Math.max(count, 1))}
                    </div>
                  )}
                </div>
              );
            })}
          </div>
        </div>
      </div>
    </div>
  );
};

/* ================================================================
   FACTURES — toggle paid, add, delete, French-compliant detail view
================================================================ */

// Full French-compliant invoice detail (printable)
const InvoiceDetailModal = ({ invoice, data, onClose }) => {
  if (!invoice) return null;
  const b = data.business || {};
  const c = findClient(data, invoice.clientId);
  const isCredit = (invoice.amount < 0) || !!invoice.creditOf;
  // TVA — utilise le taux figé sur la facture si présent (snapshot à l'émission),
  // sinon retombe sur le statut courant du business.
  const vatRate = invoice.vatRate != null && invoice.vatRate > 0
    ? Number(invoice.vatRate)
    : (b.vatStatus === "applicable" ? Number(b.vatRate || 0) : 0);
  const vatActive = vatRate > 0;
  const amountTTC = invoice.amount;
  const amountHT = vatActive ? +(amountTTC / (1 + vatRate / 100)).toFixed(2) : amountTTC;
  const vatAmount = vatActive ? +(amountTTC - amountHT).toFixed(2) : 0;
  const dueLabel = invoice.dueDate || (b.paymentTermsDays > 0 ? `Sous ${b.paymentTermsDays} jours` : "À réception");

  const missingFields = [];
  if (!b.address) missingFields.push("adresse");
  if (!b.siret)   missingFields.push("SIRET");

  const handlePrint = () => window.print();

  return (
    <Modal open={!!invoice} onClose={onClose} wide
      title={`Facture ${invoice.number || invoice.id}`}
      footer={
        <>
          <button className="btn btn-ghost" onClick={onClose}>Fermer</button>
          <button className="btn btn-primary" onClick={handlePrint}>
            <Icon name="invoice" size={14}/> Imprimer / PDF
          </button>
        </>
      }>
      {missingFields.length > 0 && (
        <div style={{
          marginBottom: 16, padding: "10px 14px",
          background: "var(--warn-soft)", border: "1px solid var(--warn-soft-2)",
          borderRadius: 8, fontSize: 13, color: "var(--warn-ink)", lineHeight: 1.5,
        }}>
          <strong>Champs manquants pour conformité&nbsp;:</strong> {missingFields.join(", ")}. Ajoutez-les dans <em>Paramètres → Informations légales</em>.
        </div>
      )}

      {/* Printable invoice body */}
      <div id="cb-invoice-print" style={{
        padding: "28px 32px", background: "white",
        border: "1px solid var(--line)", borderRadius: 10,
        fontFamily: "var(--ff-text)", color: "#111",
        lineHeight: 1.5, fontSize: 13.5,
      }}>
        {/* Header: emitter + invoice meta */}
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 24, marginBottom: 24 }}>
          <div>
            <div style={{ fontFamily: "var(--ff-display)", fontSize: 18, fontWeight: 600, letterSpacing: "-0.02em" }}>
              {b.name || "Votre activité"}
            </div>
            <div style={{ fontSize: 12.5, marginTop: 6, color: "#555", whiteSpace: "pre-line" }}>
              {b.owner}{b.legalForm ? ` · ${b.legalForm}` : ""}{"\n"}
              {b.address || "— adresse à renseigner —"}{"\n"}
              {[b.postalCode, b.city].filter(Boolean).join(" ")}{"\n"}
              {b.phone ? `Tél : ${b.phone}` : ""}{b.contactEmail ? `${b.phone ? " · " : ""}${b.contactEmail}` : ""}
            </div>
            <div style={{ fontSize: 11.5, marginTop: 8, color: "#777", fontFamily: "inherit" }}>
              SIRET&nbsp;: {b.siret || "— à renseigner —"}{b.vatNumber ? ` · TVA : ${b.vatNumber}` : ""}
              {b.rcsRm ? <><br/>{b.rcsRm}</> : null}
            </div>
          </div>
          <div style={{ textAlign: "right" }}>
            <div style={{ fontFamily: "var(--ff-display)", fontSize: 28, fontWeight: 700, letterSpacing: "-0.025em", textTransform: "uppercase" }}>
              {isCredit ? "Avoir" : "Facture"}
            </div>
            <div style={{ fontSize: 13, marginTop: 8, fontFamily: "inherit" }}>
              N° {invoice.number || invoice.id}
            </div>
            {invoice.creditOf && (
              <div style={{ fontSize: 12, color: "#777", marginTop: 2, fontFamily: "inherit" }}>
                Annule la facture {invoice.creditOf}
              </div>
            )}
            <div style={{ fontSize: 12.5, color: "#555", marginTop: 4 }}>
              Date d'émission&nbsp;: {invoice.date}
            </div>
            <div style={{ fontSize: 12.5, color: "#555" }}>
              Date de prestation&nbsp;: {invoice.prestationDate || invoice.date}
            </div>
            <div style={{ fontSize: 12.5, color: "#555" }}>
              Date d'échéance&nbsp;: {dueLabel}
            </div>
          </div>
        </div>

        {/* Recipient */}
        <div style={{ padding: "12px 14px", background: "#f7f7f9", borderRadius: 8, marginBottom: 20, fontSize: 12.5 }}>
          <div style={{ fontSize: 11, color: "#777", textTransform: "uppercase", letterSpacing: "0.06em", marginBottom: 4 }}>
            Facturé à
          </div>
          <div style={{ fontWeight: 600 }}>{c ? c.name : "—"}</div>
          {c && c.email && <div style={{ color: "#555" }}>{c.email}</div>}
          {c && c.phone && <div style={{ color: "#555" }}>{c.phone}</div>}
        </div>

        {/* Line items */}
        <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 12.5, marginBottom: 16 }}>
          <thead>
            <tr style={{ borderBottom: "2px solid #333", textAlign: "left" }}>
              <th style={{ padding: "8px 4px", fontWeight: 600 }}>Désignation</th>
              <th style={{ padding: "8px 4px", fontWeight: 600, textAlign: "center" }}>Qté</th>
              <th style={{ padding: "8px 4px", fontWeight: 600, textAlign: "right" }}>Prix unit. HT</th>
              {vatActive && <th style={{ padding: "8px 4px", fontWeight: 600, textAlign: "right" }}>TVA</th>}
              <th style={{ padding: "8px 4px", fontWeight: 600, textAlign: "right" }}>Total HT</th>
            </tr>
          </thead>
          <tbody>
            <tr style={{ borderBottom: "1px solid #ddd" }}>
              <td style={{ padding: "10px 4px" }}>
                {invoice.designation || "Prestation de service"}
                <div style={{ fontSize: 11.5, color: "#777", marginTop: 2 }}>
                  {invoice.prestationDate ? `Réalisée le ${invoice.prestationDate}` : ""}
                </div>
              </td>
              <td style={{ padding: "10px 4px", textAlign: "center", fontFamily: "inherit" }}>1</td>
              <td style={{ padding: "10px 4px", textAlign: "right", fontFamily: "inherit" }}>{amountHT.toFixed(2)} €</td>
              {vatActive && <td style={{ padding: "10px 4px", textAlign: "right", fontFamily: "inherit" }}>{vatRate}%</td>}
              <td style={{ padding: "10px 4px", textAlign: "right", fontFamily: "inherit", fontWeight: 600 }}>{amountHT.toFixed(2)} €</td>
            </tr>
          </tbody>
        </table>

        {/* Totals */}
        <div style={{ display: "flex", justifyContent: "flex-end", marginBottom: 24 }}>
          <div style={{ minWidth: 260, fontSize: 12.5 }}>
            <div style={{ display: "flex", justifyContent: "space-between", padding: "4px 0" }}>
              <span style={{ color: "#555" }}>Total HT</span>
              <span style={{ fontFamily: "inherit" }}>{amountHT.toFixed(2)} €</span>
            </div>
            {vatActive ? (
              <div style={{ display: "flex", justifyContent: "space-between", padding: "4px 0" }}>
                <span style={{ color: "#555" }}>TVA ({vatRate}%)</span>
                <span style={{ fontFamily: "inherit" }}>{vatAmount.toFixed(2)} €</span>
              </div>
            ) : (
              <div style={{ fontSize: 11, color: "#777", padding: "4px 0", fontStyle: "italic" }}>
                TVA non applicable — art. 293 B du CGI
              </div>
            )}
            <div style={{ display: "flex", justifyContent: "space-between", padding: "10px 0", borderTop: "2px solid #333", marginTop: 4, fontSize: 15, fontWeight: 700 }}>
              <span>Total TTC</span>
              <span style={{ fontFamily: "inherit" }}>{amountTTC.toFixed(2)} €</span>
            </div>
          </div>
        </div>

        {/* Payment terms + legal mentions */}
        <div style={{ paddingTop: 16, borderTop: "1px solid #ddd", fontSize: 11.5, color: "#555", lineHeight: 1.6 }}>
          <div style={{ fontWeight: 600, color: "#333", marginBottom: 4 }}>Modalités de paiement</div>
          <div>Échéance&nbsp;: {dueLabel}. Moyens acceptés&nbsp;: {b.paymentMethods || "—"}.</div>
          {b.iban && <div style={{ fontFamily: "inherit", fontSize: 11 }}>IBAN&nbsp;: {b.iban}</div>}
          <div style={{ marginTop: 4, fontSize: 11, color: "#777" }}>
            Aucun escompte n'est accordé en cas de paiement anticipé.
          </div>

          <div style={{ marginTop: 12, fontSize: 11, color: "#777" }}>
            En cas de retard de paiement, des pénalités sont applicables au taux de la Banque centrale européenne + 10 points.
            Indemnité forfaitaire pour frais de recouvrement&nbsp;: 40 € (art. L441-10 du Code de commerce).
          </div>

          {(b.mediatorName || b.mediatorUrl) && (
            <div style={{ marginTop: 10, fontSize: 11, color: "#777" }}>
              Médiateur de la consommation&nbsp;: {b.mediatorName || "—"}{b.mediatorUrl ? ` · ${b.mediatorUrl}` : ""} (art. L612-1 du Code de la consommation).
            </div>
          )}

          {invoice.paid && !isCredit && (
            <div style={{
              marginTop: 14, display: "inline-block",
              padding: "4px 12px", background: "oklch(88% 0.08 160)",
              color: "oklch(30% 0.1 160)", borderRadius: 999,
              fontSize: 12, fontWeight: 600,
            }}>✓ PAYÉE</div>
          )}
        </div>
      </div>
    </Modal>
  );
};

const InvoiceCardMobile = ({ f, data, actions, onView }) => {
  const c = findClient(data, f.clientId);
  return (
    <div style={{
      background: "var(--surface)", border: "1px solid var(--line)",
      borderRadius: 12, padding: 14, display: "flex", flexDirection: "column", gap: 10,
    }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", gap: 8 }}>
        <div style={{ fontFamily: "inherit", fontSize: 11.5, color: "var(--ink-4)", letterSpacing: "0.04em" }}>
          {f.number || f.id}
        </div>
        <div style={{ fontFamily: "var(--ff-display)", fontSize: 20, fontWeight: 580, letterSpacing: "-0.02em" }}>
          {fmtEUR(f.amount)}
        </div>
      </div>
      <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
        {c && <Avatar client={c} size={32}/>}
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontWeight: 520, fontSize: 14, color: "var(--ink)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
            {c ? c.name : "—"}
          </div>
          <div style={{ fontSize: 12, color: "var(--ink-4)", marginTop: 1 }}>{f.date}</div>
        </div>
      </div>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 8, paddingTop: 10, borderTop: "1px solid var(--line)" }}>
        <button onClick={() => actions.toggleInvoicePaid(f.id)} style={{
          padding: "5px 10px", fontSize: 11.5, fontWeight: 600, borderRadius: 6,
          background: f.paid ? "var(--sage-soft)" : "var(--warn-soft)",
          color: f.paid ? "oklch(38% 0.08 160)" : "var(--warn-ink)",
          border: "none", cursor: "pointer", fontFamily: "inherit",
          textTransform: "uppercase", letterSpacing: "0.04em",
        }}>{f.paid ? "Payée" : "En attente"}</button>
        <div style={{ display: "flex", gap: 6 }}>
          <button onClick={onView} className="btn btn-sm btn-ghost" style={{ height: 32, fontSize: 12.5 }}>Voir</button>
          {!(f.creditOf || f.amount < 0) && (
            <button onClick={() => actions.deleteInvoice(f.id)} aria-label="Annuler par avoir" title="Annuler par avoir (la facture ne peut pas être supprimée)" className="btn btn-sm btn-ghost" style={{ height: 32, width: 32, padding: 0, color: "oklch(55% 0.18 25)" }}>
              <Icon name="close" size={14}/>
            </button>
          )}
        </div>
      </div>
    </div>
  );
};

const AppFactures = ({ data, actions, openModal }) => {
  const isMobile = useIsMobile();
  const [viewId, setViewId] = React.useState(null);
  const b = data.business || {};
  const legalReady = b.address && b.siret;
  const viewInvoice = viewId ? data.invoices.find(i => i.id === viewId) : null;

  return (
    <div>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16, gap: 10 }}>
        <h3 style={{ fontSize: 16 }}>Vos factures</h3>
        <button className="btn btn-sm" style={{ background: "var(--ink)", color: "var(--bg)" }} onClick={() => openModal("invoice")}>
          <Icon name="plus" size={14}/>{isMobile ? "" : " Nouvelle facture"}
        </button>
      </div>

      {!legalReady && (
        <div style={{
          padding: "12px 14px", marginBottom: 14,
          background: "var(--warn-soft)", border: "1px solid var(--warn-soft-2)",
          borderRadius: 10, fontSize: 13, color: "var(--warn-ink)", lineHeight: 1.5,
          display: "flex", gap: 10, alignItems: "center", flexWrap: "wrap",
        }}>
          <Icon name="shield" size={15} style={{ flexShrink: 0 }}/>
          <div style={{ flex: 1, minWidth: 200 }}>
            <strong>Factures non conformes&nbsp;:</strong> ajoutez votre adresse et SIRET dans les paramètres pour qu'elles respectent la loi française.
          </div>
        </div>
      )}
      {data.invoices.length === 0 ? (
        <div style={{ padding: 40, textAlign: "center", color: "var(--ink-4)", background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 12 }}>
          Aucune facture pour le moment.
        </div>
      ) : isMobile ? (
        <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
          {data.invoices.map(f => (
            <InvoiceCardMobile key={f.id} f={f} data={data} actions={actions} onView={() => setViewId(f.id)}/>
          ))}
        </div>
      ) : (
        <div className="app-scroll-x" style={{ background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 12, overflow: "auto" }}>
          <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 13, minWidth: 640 }}>
            <thead>
              <tr style={{ background: "var(--bg-alt)", textAlign: "left" }}>
                {["Numéro", "Cliente", "Date", "Montant", "Statut", ""].map(h => (
                  <th key={h} style={{ padding: "12px 16px", fontSize: 12.5, fontWeight: 520, color: "var(--ink-3)", borderBottom: "1px solid var(--line)" }}>{h}</th>
                ))}
              </tr>
            </thead>
            <tbody>
              {data.invoices.map((f, i) => {
                const c = findClient(data, f.clientId);
                return (
                  <tr key={f.id} style={{ borderBottom: i < data.invoices.length - 1 ? "1px solid var(--line)" : "none" }}>
                    <td style={{ padding: "12px 16px", fontFamily: "inherit", fontSize: 12, color: "var(--ink-3)" }}>{f.number || f.id}</td>
                    <td style={{ padding: "12px 16px", fontWeight: 520 }}>{c ? c.name : "—"}</td>
                    <td style={{ padding: "12px 16px", color: "var(--ink-3)" }}>{f.date}</td>
                    <td style={{ padding: "12px 16px", fontFamily: "inherit", fontWeight: 520 }}>{fmtEUR(f.amount)}</td>
                    <td style={{ padding: "12px 16px" }}>
                      <button onClick={() => actions.toggleInvoicePaid(f.id)} style={{
                        padding: "3px 8px", fontSize: 11, fontWeight: 520, borderRadius: 4,
                        background: f.paid ? "var(--sage-soft)" : "var(--warn-soft)",
                        color: f.paid ? "oklch(38% 0.08 160)" : "var(--warn-ink)",
                        border: "none", cursor: "pointer", fontFamily: "inherit",
                      }}>{f.paid ? "Payée" : "En attente"}</button>
                    </td>
                    <td style={{ padding: "12px 16px", textAlign: "right", whiteSpace: "nowrap" }}>
                      <button onClick={() => setViewId(f.id)} className="btn btn-sm btn-ghost" style={{ height: 28, fontSize: 12 }}>Voir</button>
                      {f.creditOf || (f.amount < 0) ? null : (
                        <button onClick={() => actions.deleteInvoice(f.id)} className="btn btn-sm btn-ghost" style={{ height: 28, fontSize: 12, color: "oklch(55% 0.18 25)", marginLeft: 4 }} title="Annuler par avoir (la facture ne peut pas être supprimée)">Annuler</button>
                      )}
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      )}

      <InvoiceDetailModal invoice={viewInvoice} data={data} onClose={() => setViewId(null)}/>
    </div>
  );
};

/* ================================================================
   STOCK — adjust quantities, add, delete
================================================================ */
const STOCK_STATUS_COLOR = {
  ok:       ["var(--sage-soft)", "oklch(38% 0.08 160)", "Suffisant"],
  low:      ["var(--warn-soft)", "var(--warn-ink)", "Bas"],
  critical: ["oklch(96% 0.04 30)", "oklch(48% 0.18 30)", "Critique"],
};
const stockStatusOf = (it) => it.qty >= it.min ? "ok" : it.qty < Math.ceil(it.min / 2) ? "critical" : "low";

const StockCardMobile = ({ it, actions }) => {
  const stColor = STOCK_STATUS_COLOR[stockStatusOf(it)];
  const isLow = it.qty < it.min;
  return (
    <div style={{
      background: "var(--surface)",
      border: "1px solid " + (isLow ? stColor[1] + "33" : "var(--line)"),
      borderRadius: 12, padding: 14,
      display: "flex", flexDirection: "column", gap: 12,
    }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "start", gap: 10 }}>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontWeight: 520, fontSize: 14.5, color: "var(--ink)" }}>{it.name}</div>
          <div style={{ fontSize: 12.5, color: "var(--ink-4)", marginTop: 3 }}>
            {fmtEUR(it.price)} · seuil {it.min}
          </div>
        </div>
        <span style={{
          padding: "3px 8px", fontSize: 10.5, fontWeight: 600, borderRadius: 4,
          background: stColor[0], color: stColor[1], flexShrink: 0,
          textTransform: "uppercase", letterSpacing: "0.04em",
        }}>{stColor[2]}</span>
      </div>

      <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 10 }}>
        <div style={{
          display: "flex", alignItems: "center",
          border: "1px solid var(--line)", borderRadius: 10, padding: 3,
          background: "var(--bg-alt)",
        }}>
          <button onClick={() => actions.adjustStock(it.id, -1)} aria-label="Diminuer" style={{
            width: 40, height: 40, background: "transparent", border: "none",
            cursor: "pointer", color: "var(--ink-2)", fontSize: 20,
            borderRadius: 7, fontFamily: "inherit",
          }}>−</button>
          <span style={{
            fontFamily: "inherit", fontSize: 16, fontWeight: 600,
            minWidth: 48, textAlign: "center", color: "var(--ink)",
          }}>{it.qty}</span>
          <button onClick={() => actions.adjustStock(it.id, +1)} aria-label="Augmenter" style={{
            width: 40, height: 40, background: "transparent", border: "none",
            cursor: "pointer", color: "var(--ink-2)", fontSize: 20,
            borderRadius: 7, fontFamily: "inherit",
          }}>+</button>
        </div>
        <div style={{ display: "flex", gap: 6, flexShrink: 0 }}>
          <button onClick={() => actions.adjustStock(it.id, +5)} className="btn btn-sm btn-ghost" style={{ height: 36, fontSize: 12.5, padding: "0 10px" }}>+5</button>
          <button onClick={() => actions.deleteStockItem(it.id)} aria-label="Supprimer" className="btn btn-sm btn-ghost" style={{ height: 36, width: 36, padding: 0, color: "oklch(55% 0.18 25)" }}>
            <Icon name="close" size={14}/>
          </button>
        </div>
      </div>
    </div>
  );
};

const AppStock = ({ data, actions, openModal }) => {
  const isMobile = useIsMobile();
  const low = data.stock.filter(s => s.qty < s.min);
  return (
    <div>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16, gap: 10 }}>
        <h3 style={{ fontSize: 16 }}>Votre stock</h3>
        <button className="btn btn-sm" style={{ background: "var(--ink)", color: "var(--bg)" }} onClick={() => openModal("stock")}>
          <Icon name="plus" size={14}/>{isMobile ? "" : " Nouveau produit"}
        </button>
      </div>
      {low.length > 0 && (
        <div style={{
          padding: 14, background: "oklch(97% 0.025 30)",
          border: "1px solid oklch(90% 0.04 30)", borderRadius: 10,
          marginBottom: 16, display: "flex", gap: 12, alignItems: "center",
        }}>
          <div style={{
            width: 34, height: 34, borderRadius: 8,
            background: "oklch(70% 0.14 30)", color: "white",
            display: "flex", alignItems: "center", justifyContent: "center",
            flexShrink: 0,
          }}>
            <Icon name="bell" size={16}/>
          </div>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontWeight: 520, fontSize: 13.5, color: "oklch(32% 0.14 30)" }}>
              {low.length} produit{low.length > 1 ? "s" : ""} à commander
            </div>
            <div style={{
              fontSize: 12, color: "oklch(42% 0.14 30)", marginTop: 2,
              lineHeight: 1.5,
            }}>
              {low.map(s => `${s.name} (${s.qty})`).join(", ")}
            </div>
          </div>
        </div>
      )}

      {isMobile ? (
        data.stock.length === 0 ? (
          <div style={{ padding: 36, textAlign: "center", color: "var(--ink-4)", fontSize: 14, background: "var(--surface)", border: "1px dashed var(--line-strong)", borderRadius: 12 }}>
            Aucun produit. Ajoutez le premier pour commencer.
          </div>
        ) : (
          <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
            {data.stock.map(it => (
              <StockCardMobile key={it.id} it={it} actions={actions}/>
            ))}
          </div>
        )
      ) : (
        <div className="app-scroll-x" style={{ background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 12, overflow: "auto" }}>
          <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 13, minWidth: 640 }}>
            <thead>
              <tr style={{ background: "var(--bg-alt)", textAlign: "left" }}>
                {["Produit", "Stock", "Seuil mini", "Prix unitaire", "Statut", ""].map(h => (
                  <th key={h} style={{ padding: "12px 16px", fontSize: 12.5, fontWeight: 520, color: "var(--ink-3)", borderBottom: "1px solid var(--line)" }}>{h}</th>
                ))}
              </tr>
            </thead>
            <tbody>
              {data.stock.map((it, i) => {
                const stColor = STOCK_STATUS_COLOR[stockStatusOf(it)];
                return (
                  <tr key={it.id} style={{ borderBottom: i < data.stock.length - 1 ? "1px solid var(--line)" : "none" }}>
                    <td style={{ padding: "12px 16px", fontWeight: 520 }}>{it.name}</td>
                    <td style={{ padding: "12px 16px" }}>
                      <div style={{ display: "inline-flex", alignItems: "center", gap: 6, border: "1px solid var(--line)", borderRadius: 7, padding: 2 }}>
                        <button onClick={() => actions.adjustStock(it.id, -1)} style={{ width: 22, height: 22, background: "transparent", border: "none", cursor: "pointer", color: "var(--ink-3)", borderRadius: 4, fontSize: 14 }}>−</button>
                        <span style={{ fontFamily: "inherit", fontSize: 13, minWidth: 22, textAlign: "center" }}>{it.qty}</span>
                        <button onClick={() => actions.adjustStock(it.id, +1)} style={{ width: 22, height: 22, background: "transparent", border: "none", cursor: "pointer", color: "var(--ink-3)", borderRadius: 4, fontSize: 14 }}>+</button>
                      </div>
                    </td>
                    <td style={{ padding: "12px 16px", fontFamily: "inherit", color: "var(--ink-4)" }}>{it.min}</td>
                    <td style={{ padding: "12px 16px", fontFamily: "inherit" }}>{fmtEUR(it.price)}</td>
                    <td style={{ padding: "12px 16px" }}>
                      <span style={{ padding: "3px 8px", fontSize: 11, fontWeight: 520, borderRadius: 4, background: stColor[0], color: stColor[1] }}>{stColor[2]}</span>
                    </td>
                    <td style={{ padding: "12px 16px", textAlign: "right" }}>
                      <button onClick={() => actions.adjustStock(it.id, +5)} className="btn btn-sm btn-ghost" style={{ height: 28, fontSize: 12 }}>+5 réappro</button>
                      <button onClick={() => actions.deleteStockItem(it.id)} className="btn btn-sm btn-ghost" style={{ height: 28, fontSize: 12, color: "oklch(55% 0.18 25)", marginLeft: 4 }}>Retirer</button>
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      )}
    </div>
  );
};

/* ================================================================
   PRISE DE RDV — public booking link + settings
================================================================ */
const DAY_FULL_LABELS = ["Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche"];

// Migrate legacy bookingSettings → schedule if missing
const resolveSchedule = (bs) => {
  if (bs.schedule) return bs.schedule;
  const daysOpen = bs.daysOpen || [1, 2, 3, 4, 5, 6];
  const h = bs.hours || { start: 9, end: 18 };
  const s = {};
  for (let i = 1; i <= 7; i++) {
    s[i] = { open: daysOpen.includes(i), start: h.start, end: h.end };
  }
  return s;
};

/* ============ SERVICES — gestion des prestations proposées ============ */
const AppServices = ({ data, actions }) => {
  const services = data.services || [];
  const [editing, setEditing] = React.useState(null); // null | "new" | service.id
  const [draft, setDraft] = React.useState({ name: "", price: "", duration: "1", description: "", active: true });

  const startNew = () => {
    setDraft({ name: "", price: "", duration: "1", description: "", active: true });
    setEditing("new");
  };
  const startEdit = (s) => {
    setDraft({
      name: s.name, price: String(s.price ?? ""), duration: String(s.duration ?? "1"),
      description: s.description || "", active: s.active !== false,
    });
    setEditing(s.id);
  };
  const cancel = () => setEditing(null);
  const save = () => {
    if (!draft.name.trim()) { showToast("Donnez un nom à votre prestation", "warn"); return; }
    const payload = {
      name: draft.name.trim(),
      price: Number(draft.price) || 0,
      duration: Number(draft.duration) || 1,
      description: draft.description.trim(),
      active: draft.active,
    };
    if (editing === "new") actions.addService(payload);
    else actions.updateService(editing, payload);
    setEditing(null);
  };

  const fmtDuration = (h) => {
    if (!h && h !== 0) return "—";
    const hh = Math.floor(h);
    const mm = Math.round((h - hh) * 60);
    if (hh === 0) return `${mm} min`;
    if (mm === 0) return `${hh} h`;
    return `${hh} h ${String(mm).padStart(2, "0")}`;
  };

  return (
    <div style={{ maxWidth: 760 }}>
      <div style={{ marginBottom: 18, display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: 12, flexWrap: "wrap" }}>
        <div>
          <h3 style={{ fontSize: 20 }}>Vos prestations</h3>
          <p style={{ fontSize: 14, color: "var(--ink-3)", marginTop: 6, lineHeight: 1.5 }}>
            Ajoutez ici tout ce que vous proposez. Les prestations <strong>actives</strong> apparaîtront sur votre page de réservation.
          </p>
        </div>
        {editing === null && (
          <button onClick={startNew} className="btn btn-accent btn-sm">
            <Icon name="plus" size={13}/> Nouvelle prestation
          </button>
        )}
      </div>

      {/* Form add/edit */}
      {editing !== null && (
        <div style={{
          marginBottom: 18, padding: 18,
          background: "var(--surface)", border: `1px solid var(--accent-soft-2)`,
          borderRadius: 14, boxShadow: "var(--sh-2)",
        }}>
          <div style={{ fontSize: 14, fontWeight: 580, marginBottom: 14 }}>
            {editing === "new" ? "Nouvelle prestation" : "Modifier"}
          </div>
          <div style={{ display: "grid", gap: 12 }}>
            <ServiceField label="Nom de la prestation *" value={draft.name}
              onChange={v => setDraft({ ...draft, name: v })}
              placeholder="ex. Coupe + brushing"/>
            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }} className="app-split">
              <ServiceField label="Prix (€)" type="number" value={draft.price}
                onChange={v => setDraft({ ...draft, price: v })}
                placeholder="45"/>
              <ServiceField label="Durée (heures)" type="number" value={draft.duration}
                onChange={v => setDraft({ ...draft, duration: v })}
                placeholder="1" step="0.25"/>
            </div>
            <div>
              <label style={{ fontSize: 12.5, color: "var(--ink-3)", display: "block", marginBottom: 4 }}>
                Description (optionnel — visible par vos clients)
              </label>
              <textarea value={draft.description} onChange={e => setDraft({ ...draft, description: e.target.value })}
                placeholder="Ce qui est inclus, conditions particulières…"
                rows={2}
                style={{ ...fieldInputStyle, resize: "vertical", minHeight: 60 }}/>
            </div>
            <label style={{ display: "flex", alignItems: "center", gap: 8, cursor: "pointer", fontSize: 14 }}>
              <input type="checkbox" checked={draft.active} onChange={e => setDraft({ ...draft, active: e.target.checked })}
                style={{ width: 16, height: 16, accentColor: "var(--accent)", cursor: "pointer" }}/>
              <span>Visible sur la page de réservation</span>
            </label>
          </div>
          <div style={{ display: "flex", gap: 8, marginTop: 14, flexWrap: "wrap" }}>
            <button onClick={save} className="btn btn-accent btn-sm">
              {editing === "new" ? "Ajouter" : "Enregistrer"}
            </button>
            <button onClick={cancel} className="btn btn-ghost btn-sm">Annuler</button>
          </div>
        </div>
      )}

      {/* List */}
      {services.length === 0 ? (
        <div style={{
          padding: "40px 20px", textAlign: "center",
          background: "var(--bg-alt)", border: "1px dashed var(--line-strong)",
          borderRadius: 12, color: "var(--ink-3)",
        }}>
          <div style={{ fontSize: 32, marginBottom: 8 }}>✦</div>
          <div style={{ fontSize: 14.5, fontWeight: 520, color: "var(--ink-2)" }}>Aucune prestation pour le moment</div>
          <div style={{ fontSize: 13, marginTop: 6 }}>Cliquez sur « Nouvelle prestation » pour commencer.</div>
        </div>
      ) : (
        <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
          {services.map(s => {
            const active = s.active !== false;
            return (
              <div key={s.id} style={{
                padding: "14px 16px",
                background: "var(--surface)",
                border: `1px solid ${active ? "var(--line)" : "var(--line)"}`,
                borderLeft: active ? `3px solid var(--accent)` : `3px solid var(--ink-4)`,
                borderRadius: 10,
                opacity: active ? 1 : 0.62,
                display: "flex", gap: 12, alignItems: "center", flexWrap: "wrap",
              }}>
                <div style={{ flex: 1, minWidth: 180 }}>
                  <div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
                    <span style={{ fontSize: 14.5, fontWeight: 580, color: "var(--ink)" }}>{s.name}</span>
                    {!active && (
                      <span style={{
                        padding: "2px 8px", background: "var(--bg-alt)",
                        border: "1px solid var(--line)", borderRadius: 999,
                        fontSize: 10.5, fontWeight: 600, color: "var(--ink-3)",
                      }}>Masquée</span>
                    )}
                  </div>
                  <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginTop: 2, display: "flex", gap: 12, flexWrap: "wrap" }}>
                    <span>{fmtDuration(s.duration)}</span>
                    <span style={{ fontWeight: 580, color: "var(--accent-ink)" }}>
                      {s.price ? `${s.price} €` : "Gratuit"}
                    </span>
                  </div>
                  {s.description && (
                    <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginTop: 4, lineHeight: 1.45 }}>
                      {s.description}
                    </div>
                  )}
                </div>
                <div style={{ display: "flex", gap: 6, alignItems: "center" }}>
                  <button onClick={() => actions.toggleServiceActive(s.id)} title={active ? "Masquer" : "Afficher"}
                    className="btn btn-sm btn-ghost" style={{ height: 30, fontSize: 12 }}>
                    {active ? "Masquer" : "Afficher"}
                  </button>
                  <button onClick={() => startEdit(s)} title="Modifier"
                    className="btn btn-sm btn-ghost" style={{ height: 30, width: 30, padding: 0 }}>
                    <Icon name="settings" size={13}/>
                  </button>
                  <button onClick={() => actions.deleteService(s.id)} title="Supprimer"
                    className="btn btn-sm btn-ghost" style={{ height: 30, width: 30, padding: 0, color: "oklch(55% 0.18 25)" }}>
                    <Icon name="trash" size={13}/>
                  </button>
                </div>
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
};

const ServiceField = ({ label, value, onChange, placeholder, type = "text", step }) => (
  <div>
    <label style={{ fontSize: 12.5, color: "var(--ink-3)", display: "block", marginBottom: 4 }}>{label}</label>
    <input type={type} step={step} value={value} onChange={e => onChange(e.target.value)} placeholder={placeholder}
      style={fieldInputStyle}/>
  </div>
);

const AppBookings = ({ data, actions }) => {
  const [slug, setSlug] = React.useState(() => cbAuth.ensureBookingSlug() || "");
  const [editingSlug, setEditingSlug] = React.useState(false);
  const [draftSlug, setDraftSlug] = React.useState(slug);
  const [copied, setCopied] = React.useState(false);
  const [newVac, setNewVac] = React.useState({ start: "", end: "", reason: "" });
  const bs = data.bookingSettings || {};
  const schedule = resolveSchedule(bs);
  const vacations = bs.vacations || [];
  const url = `${window.location.origin}${window.location.pathname}?book=${slug}`;

  const saveSlug = () => {
    const newSlug = cbAuth.updateBookingSlug(draftSlug);
    setSlug(newSlug);
    setEditingSlug(false);
    showToast("Lien mis à jour");
  };

  const copy = async () => {
    try {
      if (navigator.clipboard) await navigator.clipboard.writeText(url);
      else {
        const ta = document.createElement("textarea");
        ta.value = url; document.body.appendChild(ta); ta.select();
        document.execCommand("copy"); document.body.removeChild(ta);
      }
      setCopied(true);
      setTimeout(() => setCopied(false), 1800);
      showToast("Lien copié");
    } catch (e) {
      showToast("Impossible de copier — sélectionnez à la main", "warn");
    }
  };

  const updateDay = (isoIdx, patch) => {
    const next = { ...schedule, [isoIdx]: { ...schedule[isoIdx], ...patch } };
    actions.updateBookingSettings({ schedule: next });
  };

  const addVacation = () => {
    if (!newVac.start || !newVac.end) {
      showToast("Date de début et fin requises", "warn");
      return;
    }
    if (newVac.end < newVac.start) {
      showToast("La date de fin doit être après la date de début", "warn");
      return;
    }
    const v = {
      id: "v_" + Math.random().toString(36).slice(2, 10),
      start: newVac.start, end: newVac.end,
      reason: newVac.reason.trim() || "Fermeture",
    };
    actions.updateBookingSettings({ vacations: [...vacations, v] });
    setNewVac({ start: "", end: "", reason: "" });
    showToast("Période ajoutée");
  };

  const removeVacation = (id) => {
    actions.updateBookingSettings({ vacations: vacations.filter(v => v.id !== id) });
  };

  return (
    <div style={{ maxWidth: 760 }}>
      <div style={{ marginBottom: 24 }}>
        <h3 style={{ fontSize: 20 }}>Votre lien de réservation</h3>
        <p style={{ fontSize: 14, color: "var(--ink-3)", marginTop: 6 }}>
          Partagez-le sur Instagram, WhatsApp, votre carte de visite. Vos clients choisissent un créneau elles-mêmes.
        </p>
      </div>

      {/* The URL card — non-mono label, readable URL */}
      <div style={{
        padding: 20, background: "var(--surface)",
        border: "1px solid var(--line)", borderRadius: 14,
        marginBottom: 18,
      }}>
        <div style={{ fontSize: 13, color: "var(--ink-3)", fontWeight: 500, marginBottom: 10 }}>
          Votre lien public
        </div>
        <div style={{
          display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap",
          padding: "10px 14px", background: "var(--bg-alt)",
          border: "1px solid var(--line)", borderRadius: 10,
        }}>
          <Icon name="link" size={15} style={{ color: "var(--accent-ink)", flexShrink: 0 }}/>
          <span style={{
            flex: 1, fontSize: 13.5,
            color: "var(--ink-2)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
            minWidth: 0,
          }}>{url}</span>
          <button onClick={copy} className="btn btn-sm" style={{
            background: copied ? "var(--sage)" : "var(--ink)",
            color: "var(--bg)", flexShrink: 0,
          }}>
            <Icon name={copied ? "check" : "copy"} size={13}/> {copied ? "Copié" : "Copier"}
          </button>
          <a href={url} target="_blank" rel="noopener noreferrer" className="btn btn-sm btn-ghost" style={{ flexShrink: 0 }}>
            <Icon name="external" size={13}/> Aperçu
          </a>
        </div>

        {/* Slug editor */}
        <div style={{ marginTop: 14, display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
          <div style={{ fontSize: 12.5, color: "var(--ink-4)" }}>Identifiant personnalisé&nbsp;:</div>
          {editingSlug ? (
            <>
              <input value={draftSlug} onChange={e => setDraftSlug(e.target.value)} style={{
                padding: "4px 10px", fontSize: 13,
                background: "var(--bg)", border: "1px solid var(--line-strong)",
                borderRadius: 6, color: "var(--ink)", outline: "none", minWidth: 200,
                fontFamily: "inherit",
              }}/>
              <button onClick={saveSlug} className="btn btn-sm btn-primary" style={{ height: 28, fontSize: 12 }}>Enregistrer</button>
              <button onClick={() => { setEditingSlug(false); setDraftSlug(slug); }} className="btn btn-sm btn-ghost" style={{ height: 28, fontSize: 12 }}>Annuler</button>
            </>
          ) : (
            <>
              <span style={{
                fontSize: 13, padding: "2px 10px",
                background: "var(--bg-alt)", borderRadius: 6,
                color: "var(--ink-2)", fontWeight: 500,
              }}>{slug}</span>
              <button onClick={() => { setDraftSlug(slug); setEditingSlug(true); }} className="btn btn-sm btn-ghost" style={{ height: 26, fontSize: 11.5 }}>
                Modifier
              </button>
            </>
          )}
        </div>
      </div>

      {/* Réseau social préféré pour le contact client */}
      <div style={{
        padding: 20, background: "var(--surface)",
        border: "1px solid var(--line)", borderRadius: 14, marginBottom: 18,
      }}>
        <div style={{ fontSize: 15, fontWeight: 520, marginBottom: 6 }}>Réseau social préféré</div>
        <div style={{ fontSize: 13, color: "var(--ink-3)", marginBottom: 14, lineHeight: 1.5 }}>
          À la réservation, vos clients pourront indiquer leur pseudo sur ce réseau pour faciliter les échanges.
          Choisissez « Aucun » pour ne pas demander cette info.
        </div>
        <div style={{ display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap" }}>
          <select value={bs.preferredSocial || "instagram"}
            onChange={e => actions.updateBookingSettings({ preferredSocial: e.target.value })}
            style={{
              padding: "8px 12px", fontSize: 13.5, fontFamily: "inherit",
              background: "var(--bg)", border: "1px solid var(--line-strong)",
              borderRadius: 8, color: "var(--ink)", outline: "none", minWidth: 200,
            }}>
            <option value="instagram">Instagram</option>
            <option value="facebook">Facebook</option>
            <option value="tiktok">TikTok</option>
            <option value="snapchat">Snapchat</option>
            <option value="whatsapp">WhatsApp</option>
            <option value="none">Aucun — ne pas demander</option>
          </select>
        </div>
      </div>

      {/* Per-day schedule */}
      <div style={{
        padding: 20, background: "var(--surface)",
        border: "1px solid var(--line)", borderRadius: 14, marginBottom: 18,
      }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", marginBottom: 14, flexWrap: "wrap", gap: 8 }}>
          <div style={{ fontSize: 15, fontWeight: 520 }}>Horaires par jour</div>
          <div>
            <label style={{ fontSize: 12.5, color: "var(--ink-3)", marginRight: 8 }}>Pas des créneaux&nbsp;:</label>
            <select value={bs.slotDuration || 30} onChange={e => actions.updateBookingSettings({ slotDuration: +e.target.value })}
              style={{
                padding: "4px 8px", fontSize: 13, fontFamily: "inherit",
                background: "var(--bg)", border: "1px solid var(--line-strong)",
                borderRadius: 6, color: "var(--ink)", outline: "none",
              }}>
              {[15, 30, 45, 60].map(m => <option key={m} value={m}>{m} min</option>)}
            </select>
          </div>
        </div>

        <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
          {[1, 2, 3, 4, 5, 6, 7].map(isoIdx => {
            const day = schedule[isoIdx] || { open: false, start: 9, end: 18, break: false, breakStart: 12.5, breakEnd: 13.5 };
            const hourSelectStyle = {
              padding: "4px 8px", fontSize: 13, fontFamily: "inherit",
              background: "var(--bg)", border: "1px solid var(--line-strong)",
              borderRadius: 6, color: "var(--ink)", outline: "none",
            };
            return (
              <div key={isoIdx} style={{
                padding: "10px 14px",
                background: day.open ? "var(--bg-alt)" : "transparent",
                border: "1px solid var(--line)", borderRadius: 10,
                opacity: day.open ? 1 : 0.6,
              }}>
                <div style={{ display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap" }}>
                  <div style={{ minWidth: 90, fontSize: 13.5, fontWeight: 520, color: "var(--ink)" }}>
                    {DAY_FULL_LABELS[isoIdx - 1]}
                  </div>
                  <ToggleSwitch checked={day.open} onChange={(v) => updateDay(isoIdx, { open: v })}/>
                  {day.open ? (
                    <div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
                      <select value={day.start} onChange={e => updateDay(isoIdx, { start: +e.target.value })} style={hourSelectStyle}>
                        {Array.from({ length: 28 }, (_, i) => 6 + i * 0.5).map(h => (
                          <option key={h} value={h}>{fmtH(h)}</option>
                        ))}
                      </select>
                      <span style={{ fontSize: 12, color: "var(--ink-4)" }}>→</span>
                      <select value={day.end} onChange={e => updateDay(isoIdx, { end: +e.target.value })} style={hourSelectStyle}>
                        {Array.from({ length: 28 }, (_, i) => 7 + i * 0.5).map(h => (
                          <option key={h} value={h}>{fmtH(h)}</option>
                        ))}
                      </select>
                    </div>
                  ) : (
                    <span style={{ fontSize: 12.5, color: "var(--ink-4)" }}>Fermé</span>
                  )}
                </div>
                {/* Lunch break row */}
                {day.open && (
                  <div style={{
                    marginTop: 8, paddingTop: 10, borderTop: "1px dashed var(--line)",
                    display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap",
                    fontSize: 12.5,
                  }}>
                    <label style={{
                      display: "flex", alignItems: "center", gap: 8,
                      cursor: "pointer", color: "var(--ink-2)",
                    }}>
                      <input type="checkbox" checked={!!day.break}
                        onChange={e => updateDay(isoIdx, { break: e.target.checked })}
                        style={{ accentColor: "var(--accent)", width: 15, height: 15 }}/>
                      <Icon name="clock" size={12} style={{ color: "var(--ink-3)" }}/>
                      Pause déjeuner
                    </label>
                    {day.break && (
                      <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
                        <select value={day.breakStart || 12.5} onChange={e => updateDay(isoIdx, { breakStart: +e.target.value })} style={{ ...hourSelectStyle, fontSize: 12.5 }}>
                          {Array.from({ length: 16 }, (_, i) => 10 + i * 0.5).map(h => (
                            <option key={h} value={h}>{fmtH(h)}</option>
                          ))}
                        </select>
                        <span style={{ fontSize: 12, color: "var(--ink-4)" }}>→</span>
                        <select value={day.breakEnd || 13.5} onChange={e => updateDay(isoIdx, { breakEnd: +e.target.value })} style={{ ...hourSelectStyle, fontSize: 12.5 }}>
                          {Array.from({ length: 16 }, (_, i) => 11 + i * 0.5).map(h => (
                            <option key={h} value={h}>{fmtH(h)}</option>
                          ))}
                        </select>
                      </div>
                    )}
                  </div>
                )}
              </div>
            );
          })}
        </div>
      </div>

      {/* Vacations / closures */}
      <div style={{
        padding: 20, background: "var(--surface)",
        border: "1px solid var(--line)", borderRadius: 14, marginBottom: 18,
      }}>
        <div style={{ fontSize: 15, fontWeight: 520, marginBottom: 6 }}>Périodes de fermeture</div>
        <div style={{ fontSize: 13, color: "var(--ink-3)", marginBottom: 14, lineHeight: 1.5 }}>
          Vacances, congés, événements exceptionnels. Ces périodes sont visibles par vos clients sur la page de réservation — elles ne pourront pas réserver dessus.
        </div>

        {vacations.length > 0 && (
          <div style={{ display: "flex", flexDirection: "column", gap: 8, marginBottom: 14 }}>
            {vacations.map(v => (
              <div key={v.id} style={{
                display: "flex", alignItems: "center", gap: 12,
                padding: "10px 14px", background: "var(--bg-alt)",
                border: "1px solid var(--line)", borderRadius: 10,
              }}>
                <div style={{
                  width: 32, height: 32, borderRadius: 8,
                  background: "var(--warn-soft)", color: "var(--warn-ink)",
                  display: "flex", alignItems: "center", justifyContent: "center",
                  flexShrink: 0,
                }}>
                  <Icon name="calendar" size={15}/>
                </div>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontSize: 13.5, fontWeight: 520, color: "var(--ink)" }}>{v.reason}</div>
                  <div style={{ fontSize: 12, color: "var(--ink-4)", marginTop: 1 }}>
                    Du {new Date(v.start).toLocaleDateString("fr-FR")} au {new Date(v.end).toLocaleDateString("fr-FR")}
                  </div>
                </div>
                <button onClick={() => removeVacation(v.id)} aria-label="Supprimer" className="btn btn-sm btn-ghost" style={{ height: 30, width: 30, padding: 0, color: "oklch(55% 0.18 25)" }}>
                  <Icon name="close" size={13}/>
                </button>
              </div>
            ))}
          </div>
        )}

        <div style={{
          padding: 14, background: "var(--bg-alt)",
          border: "1px dashed var(--line-strong)", borderRadius: 10,
          display: "grid", gap: 10,
          gridTemplateColumns: "1fr 1fr",
          minWidth: 0, overflow: "hidden",
        }} className="app-split">
          <div style={{ minWidth: 0 }}>
            <label style={{ fontSize: 12.5, color: "var(--ink-3)", display: "block", marginBottom: 4 }}>Du</label>
            <input type="date" value={newVac.start} onChange={e => setNewVac({ ...newVac, start: e.target.value })} style={fieldInputStyle}/>
          </div>
          <div style={{ minWidth: 0 }}>
            <label style={{ fontSize: 12.5, color: "var(--ink-3)", display: "block", marginBottom: 4 }}>Au</label>
            <input type="date" value={newVac.end} onChange={e => setNewVac({ ...newVac, end: e.target.value })} style={fieldInputStyle}/>
          </div>
          <div style={{ gridColumn: "1 / -1", minWidth: 0 }}>
            <label style={{ fontSize: 12.5, color: "var(--ink-3)", display: "block", marginBottom: 4 }}>Motif (optionnel)</label>
            <input type="text" value={newVac.reason} onChange={e => setNewVac({ ...newVac, reason: e.target.value })}
              placeholder="Vacances d'été, formation…"
              style={fieldInputStyle}/>
          </div>
          <div style={{ gridColumn: "1 / -1" }}>
            <button onClick={addVacation} className="btn btn-accent btn-sm" style={{ width: "100%" }}>
              <Icon name="plus" size={13}/> Ajouter cette période
            </button>
          </div>
        </div>
      </div>

      {/* Beta disclaimer */}
      <div style={{
        padding: "10px 14px", background: "var(--bg-alt)",
        border: "1px dashed var(--line-strong)", borderRadius: 10,
        fontSize: 12, color: "var(--ink-3)", lineHeight: 1.5,
        overflowWrap: "anywhere", wordBreak: "break-word",
      }}>
        <strong style={{ color: "var(--ink-2)" }}>Note bêta&nbsp;:</strong> pendant la bêta, les réservations fonctionnent sur le même navigateur que vous. La synchro serveur arrive au lancement officiel.
      </div>
    </div>
  );
};

const ToggleSwitch = ({ checked, onChange }) => (
  <button onClick={() => onChange(!checked)} aria-pressed={checked} style={{
    width: 44, height: 24, borderRadius: 999, border: "none",
    background: checked ? "var(--accent)" : "var(--line-strong)",
    cursor: "pointer", padding: 2, flexShrink: 0,
    transition: "background 0.15s",
  }}>
    <div style={{
      width: 20, height: 20, borderRadius: "50%", background: "white",
      boxShadow: "var(--sh-1)",
      transform: checked ? "translateX(20px)" : "translateX(0)",
      transition: "transform 0.15s",
    }}/>
  </button>
);

/* ================================================================
   SETTINGS
================================================================ */
// Tons des sections de paramètres — alignés sur CB_TONE pour la cohérence
// avec la sidebar. Activité réutilise l'indigo de "Mon activité" sidebar ;
// Compte réutilise la lavande de "Mon compte".
const SETTINGS_TONES = {
  business: { ...CB_TONE.activity, icon: "sparkle" }, // indigo (= sidebar Mon activité)
  legal:    { ...CB_TONE.legal,    icon: "shield"  }, // bleu
  account:  { ...CB_TONE.account,  icon: "users"   }, // lavande (= sidebar Mon compte)
  contact:  { ...CB_TONE.contact,  icon: "mail"    }, // teal
  data:     { ...CB_TONE.data,     icon: "box"     }, // peach
};


const SettingsSection = ({ title, subtitle, tone, children, style, accordion, open, onToggle }) => {
  const t = SETTINGS_TONES[tone] || SETTINGS_TONES.business;

  // Mode accordéon (mobile) : carte blanche neutre, accent uniquement quand ouvert.
  if (accordion) {
    return (
      <div style={{
        marginBottom: 10,
        background: "var(--surface)",
        border: `1px solid ${open ? "var(--accent-soft-2)" : "var(--line)"}`,
        borderRadius: 14, overflow: "hidden",
        transition: "border-color .25s ease, box-shadow .25s ease",
        boxShadow: open ? "0 6px 18px -10px rgba(15,18,30,0.10)" : "none",
      }}>
        <button onClick={onToggle} style={{
          width: "100%", padding: "14px 14px",
          display: "flex", alignItems: "center", gap: 12,
          background: "transparent",
          border: "none", cursor: "pointer", fontFamily: "inherit", textAlign: "left",
        }}>
          <div style={{
            width: 38, height: 38, borderRadius: 11, flexShrink: 0,
            background: open ? "var(--accent)" : "var(--bg-alt)",
            color: open ? "white" : (t.dot || "var(--ink-3)"),
            display: "flex", alignItems: "center", justifyContent: "center",
            transition: "background .25s, color .25s",
          }}>
            <Icon name={t.icon} size={16}/>
          </div>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontSize: 14.5, fontWeight: 600, color: "var(--ink)", letterSpacing: "-0.01em" }}>
              {title}
            </div>
            {subtitle && (
              <div style={{ fontSize: 12, color: "var(--ink-4)", marginTop: 2 }}>
                {subtitle}
              </div>
            )}
          </div>
          <div style={{
            width: 28, height: 28, borderRadius: 8, flexShrink: 0,
            color: "var(--ink-4)",
            display: "flex", alignItems: "center", justifyContent: "center",
            transition: "transform .3s cubic-bezier(0.22, 1, 0.36, 1)",
            transform: open ? "rotate(180deg)" : "rotate(0)",
          }}>
            <Icon name="arrow" size={14} style={{ transform: "rotate(90deg)" }}/>
          </div>
        </button>
        <div style={{
          display: "grid",
          gridTemplateRows: open ? "1fr" : "0fr",
          opacity: open ? 1 : 0,
          transition: "grid-template-rows .32s cubic-bezier(0.22, 1, 0.36, 1), opacity .25s ease",
        }}>
          <div style={{ overflow: "hidden" }}>
            <div style={{
              padding: "4px 16px 18px",
              borderTop: "1px solid var(--line)",
              marginTop: 4,
              display: "flex", flexDirection: "column", gap: 14,
              ...style,
            }}>{children}</div>
          </div>
        </div>
      </div>
    );
  }

  // Mode desktop classique : titre + carte surface.
  return (
    <div style={{ marginBottom: 20 }}>
      <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 10 }}>
        <div style={{
          width: 28, height: 28, borderRadius: 8,
          background: t.bg, color: t.ink,
          display: "flex", alignItems: "center", justifyContent: "center",
        }}>
          <Icon name={t.icon} size={13}/>
        </div>
        <div>
          <h3 style={{ fontSize: 15, fontWeight: 580, letterSpacing: "-0.01em", lineHeight: 1.2 }}>{title}</h3>
          {subtitle && <div style={{ fontSize: 12, color: "var(--ink-4)", marginTop: 1 }}>{subtitle}</div>}
        </div>
      </div>
      <div style={{
        background: "var(--surface)", border: "1px solid var(--line)",
        borderRadius: 12, padding: 20,
        display: "flex", flexDirection: "column", gap: 14,
        ...style,
      }}>{children}</div>
    </div>
  );
};

/* Avatar partagé — affiche la photo si présente, sinon les initiales avec gradient coloré */
const ProAvatar = ({ url, initials, hue = 30, size = 32, ring = false }) => {
  const ringStyle = ring ? {
    boxShadow: `0 0 0 2px var(--surface), 0 0 0 4px color-mix(in oklab, oklch(70% 0.14 ${hue}) 50%, transparent)`,
  } : {};
  if (url) {
    return (
      <img src={url} alt={initials || ""} style={{
        width: size, height: size, borderRadius: size / 2,
        objectFit: "cover", flexShrink: 0, display: "block",
        ...ringStyle,
      }}/>
    );
  }
  return (
    <div style={{
      width: size, height: size, borderRadius: size / 2,
      background: `linear-gradient(135deg, oklch(80% 0.12 ${hue}), oklch(68% 0.16 ${(hue + 40) % 360}))`,
      color: "white", display: "flex", alignItems: "center", justifyContent: "center",
      fontSize: size * 0.38, fontWeight: 600, flexShrink: 0,
      letterSpacing: "-0.01em",
      boxShadow: `0 4px 10px -4px oklch(70% 0.14 ${hue})`,
      ...ringStyle,
    }}>
      {initials || "?"}
    </div>
  );
};

/* Uploader : lit un fichier, le redimensionne à 256 px, stocke en data URL */
const AvatarUploader = ({ currentUrl, initials, hue, onChange }) => {
  const inputRef = React.useRef(null);
  const [busy, setBusy] = React.useState(false);

  const handleFile = async (file) => {
    if (!file) return;
    if (!file.type.startsWith("image/")) { showToast("Choisissez une image", "warn"); return; }
    if (file.size > 5 * 1024 * 1024) { showToast("Image trop lourde (max 5 Mo)", "warn"); return; }
    setBusy(true);
    try {
      const dataUrl = await resizeImage(file, 256);
      onChange(dataUrl);
      showToast("Photo mise à jour");
    } catch (e) {
      console.error(e);
      showToast("Impossible de lire l'image", "warn");
    } finally {
      setBusy(false);
    }
  };

  return (
    <div style={{
      display: "flex", alignItems: "center", gap: 16,
      padding: "10px 12px", background: "var(--bg-alt)",
      border: "1px solid var(--line)", borderRadius: 12,
    }}>
      <ProAvatar url={currentUrl} initials={initials} hue={hue} size={64} ring/>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontSize: 13.5, fontWeight: 520, color: "var(--ink)" }}>
          Photo de profil
        </div>
        <div style={{ fontSize: 12, color: "var(--ink-4)", marginTop: 2, lineHeight: 1.4 }}>
          Apparaît sur votre page publique de réservation et sur vos factures.
        </div>
        <div style={{ display: "flex", gap: 6, marginTop: 8, flexWrap: "wrap" }}>
          <input
            ref={inputRef} type="file" accept="image/*"
            onChange={e => handleFile(e.target.files && e.target.files[0])}
            style={{ display: "none" }}
          />
          <button type="button" className="btn btn-sm btn-ghost"
            disabled={busy} onClick={() => inputRef.current && inputRef.current.click()}>
            <Icon name="sparkle" size={12}/> {busy ? "Envoi…" : (currentUrl ? "Changer" : "Ajouter une photo")}
          </button>
          {currentUrl && (
            <button type="button" className="btn btn-sm btn-ghost"
              onClick={() => { onChange(""); showToast("Photo retirée", "warn"); }}
              style={{ color: "oklch(55% 0.18 25)" }}>
              Retirer
            </button>
          )}
        </div>
      </div>
    </div>
  );
};

/* Resize + encode as base64 data URL. Returns a square ~maxSize portrait. */
const resizeImage = (file, maxSize) => new Promise((resolve, reject) => {
  const reader = new FileReader();
  reader.onerror = reject;
  reader.onload = () => {
    const img = new Image();
    img.onerror = reject;
    img.onload = () => {
      const size = maxSize;
      const s = Math.min(img.width, img.height);
      const sx = (img.width - s) / 2;
      const sy = (img.height - s) / 2;
      const canvas = document.createElement("canvas");
      canvas.width = size; canvas.height = size;
      const ctx = canvas.getContext("2d");
      ctx.drawImage(img, sx, sy, s, s, 0, 0, size, size);
      resolve(canvas.toDataURL("image/jpeg", 0.85));
    };
    img.src = reader.result;
  };
  reader.readAsDataURL(file);
});

const AppSettings = ({ data, actions, go }) => {
  const b = data.business || {};
  const [form, setForm] = React.useState({
    name: b.name || "",
    owner: b.owner || "",
    phone: b.phone || "",
    contactEmail: b.contactEmail || "",
    address: b.address || "",
    postalCode: b.postalCode || "",
    city: b.city || "",
    siret: b.siret || "",
    legalForm: b.legalForm || "Auto-entrepreneur",
    vatStatus: b.vatStatus || "franchise",
    vatNumber: b.vatNumber || "",
    vatRate: b.vatRate || 20,
    iban: b.iban || "",
    paymentTermsDays: b.paymentTermsDays || 0,
    paymentMethods: b.paymentMethods || "Virement, espèces, carte",
    rcsRm: b.rcsRm || "",
    mediatorName: b.mediatorName || "",
    mediatorUrl: b.mediatorUrl || "",
  });

  // Contact form (to ClientBase team)
  const [contact, setContact] = React.useState({ subject: "Question générale", message: "" });
  const [contactSent, setContactSent] = React.useState(false);

  // Modale de confirmation pour les actions dangereuses (reset / delete)
  const [dangerModal, setDangerModal] = React.useState(null); // null | "reset" | "delete"

  const user = cbAuth.getCurrentUser();
  const emailVerified = !!(user && user.emailVerified);

  // États locaux pour les 3 actions sécurité
  const [verifBusy, setVerifBusy]   = React.useState(false);
  const [verifSent, setVerifSent]   = React.useState(false);
  const [emailOpen, setEmailOpen]   = React.useState(false);
  const [newEmail, setNewEmail]     = React.useState("");
  const [emailBusy, setEmailBusy]   = React.useState(false);
  const [emailMsg,  setEmailMsg]    = React.useState(null); // {ok|err, text}
  const [pwBusy,    setPwBusy]      = React.useState(false);
  const [pwSent,    setPwSent]      = React.useState(false);

  const set = (patch) => setForm(f => ({ ...f, ...patch }));

  const saveSection = (keys) => {
    const patch = {};
    keys.forEach(k => { patch[k] = form[k]; });
    if (keys.includes("owner")) {
      patch.initials = (form.owner || "").split(" ").map(x => x[0]).slice(0, 2).join("").toUpperCase();
    }
    actions.updateBusiness(patch);
  };

  const [contactBusy, setContactBusy] = React.useState(false);
  const sendContact = async (e) => {
    e.preventDefault();
    if (!contact.message.trim() || contact.message.trim().length < 10) {
      showToast("Message trop court", "warn"); return;
    }
    const u = user || {};
    setContactBusy(true);
    const res = await window.sendContactMail({
      subject: `[ClientBase In-app — ${contact.subject}] ${u.email || "Utilisateur"}`,
      from: u.ownerName || u.email || "Utilisateur ClientBase",
      replyTo: u.email || "no-reply@clientbase.fr",
      message: contact.message,
      meta: {
        Activité: (data.business && data.business.name) || "—",
        Source: "App pro / Paramètres → Nous contacter",
      },
    });
    setContactBusy(false);
    if (res.error) { showToast(res.error, "warn"); return; }
    setContactSent(true);
    setTimeout(() => setContactSent(false), 4500);
    showToast("Message envoyé à l'équipe");
    setContact({ subject: "Question générale", message: "" });
  };

  const resendVerification = async () => {
    setVerifBusy(true);
    const res = await cbAuth.resendVerification();
    setVerifBusy(false);
    if (res.error) { showToast(res.error, "warn"); return; }
    setVerifSent(true);
    setTimeout(() => setVerifSent(false), 8000);
    showToast("Email de vérification envoyé ✉");
  };

  const submitChangeEmail = async () => {
    setEmailMsg(null);
    if (!cbAuth.isValidEmail(newEmail)) {
      setEmailMsg({ kind: "err", text: "Email invalide. Exemple : prenom@exemple.fr" });
      return;
    }
    setEmailBusy(true);
    const res = await cbAuth.changeEmail(newEmail);
    setEmailBusy(false);
    if (res.error) { setEmailMsg({ kind: "err", text: res.error }); return; }
    setEmailMsg({ kind: "ok", text: `Un email de confirmation a été envoyé à ${newEmail}. Cliquez sur le lien qu'il contient pour valider le changement.` });
    setNewEmail("");
  };

  const sendPasswordReset = async () => {
    if (!user || !user.email) { showToast("Aucun email associé", "warn"); return; }
    setPwBusy(true);
    const res = await cbAuth.resetPassword(user.email);
    setPwBusy(false);
    if (res.error) { showToast(res.error, "warn"); return; }
    setPwSent(true);
    setTimeout(() => setPwSent(false), 8000);
    showToast("Email de réinitialisation envoyé 🔒");
  };

  const doLogout = () => {
    if (!window.confirm("Vous déconnecter de ClientBase ?")) return;
    cbAuth.logout();
    showToast("Déconnecté", "warn");
  };

  const isMobile = useIsMobile(720);
  // Desktop : onglet par défaut "business". Mobile : tout fermé.
  const [tab, setTab] = React.useState(isMobile ? null : "business");
  const tabs = [
    { id: "business", label: "Votre activité", icon: "sparkle", tone: "business", sub: "Photo, nom, contact" },
    { id: "legal",    label: "Factures",       icon: "invoice", tone: "legal",    sub: "SIRET, TVA, mentions" },
    { id: "data",     label: "Vos données",    icon: "shield",  tone: "data",     sub: "RGPD, réinitialisation" },
    { id: "account",  label: "Compte",         icon: "users",   tone: "account",  sub: "Email, sécurité" },
    { id: "contact",  label: "Nous contacter", icon: "mail",    tone: "contact",  sub: "Bug, question, idée" },
  ];

  // Accordéon : sur mobile, chaque section est pliable. Helper qui fait
  // basculer la section cliquée (fermer si même id, ouvrir sinon).
  const toggle = (id) => setTab(tab === id ? null : id);
  const accord = (id) => isMobile ? {
    accordion: true,
    open: tab === id,
    onToggle: () => toggle(id),
  } : {};

  return (
    <div style={{ maxWidth: 680, paddingBottom: 48 }}>
      {!isMobile && (
        /* Desktop — barre d'onglets horizontale */
        <div style={{
          marginBottom: 20,
          background: "var(--surface)", border: "1px solid var(--line)",
          borderRadius: 12, padding: 4,
          display: "flex", gap: 2,
        }}>
          {tabs.map(t => {
            const isActive = tab === t.id;
            const tone = SETTINGS_TONES[t.tone] || SETTINGS_TONES.business;
            return (
              <button key={t.id} onClick={() => setTab(t.id)} style={{
                flex: 1,
                display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 7,
                padding: "9px 12px",
                fontSize: 12.5, fontWeight: 520,
                background: isActive ? tone.bg : "transparent",
                color: isActive ? tone.ink : "var(--ink-3)",
                border: "none", borderRadius: 9, cursor: "pointer", fontFamily: "inherit",
                transition: "background .15s, color .15s",
                whiteSpace: "nowrap",
              }}>
                <Icon name={t.icon} size={13}/>
                {t.label}
              </button>
            );
          })}
        </div>
      )}

      {/* Identité */}
      {(isMobile || tab === "business") && (
      <SettingsSection title="Votre activité" subtitle="Photo, nom, contact, identité" tone="business" {...accord("business")}>
        <AvatarUploader
          currentUrl={data.business.avatar}
          initials={data.business.initials || (data.business.owner || "?").split(" ").map(x => x[0]).slice(0,2).join("").toUpperCase()}
          hue={data.business.hue}
          onChange={(dataUrl) => actions.updateBusiness({ avatar: dataUrl || "" })}
        />
        <FormField label="Nom de l'activité" value={form.name} onChange={v => set({ name: v })}/>
        <FormField label="Votre nom" value={form.owner} onChange={v => set({ owner: v })}/>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10 }} className="app-split">
          <FormField label="Téléphone" value={form.phone} onChange={v => set({ phone: v })} placeholder="06 12 34 56 78"/>
          <FormField label="Email de contact" type="email" value={form.contactEmail} onChange={v => set({ contactEmail: v })} placeholder="contact@votre-activité.fr"/>
        </div>
        <div>
          <button className="btn btn-primary" onClick={() => saveSection(["name", "owner", "phone", "contactEmail"])}>
            Enregistrer
          </button>
        </div>
      </SettingsSection>
      )}

      {/* Infos légales pour factures */}
      {(isMobile || tab === "legal") && (
      <SettingsSection title="Informations légales" subtitle="Obligatoires pour vos factures" tone="legal" {...accord("legal")}>
        <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginTop: -4, marginBottom: 6, lineHeight: 1.5 }}>
          Ces informations apparaissent sur vos factures. Elles sont <strong>obligatoires</strong> en France (art. L441-9 du Code de commerce).
        </div>
        <FormSelect label="Forme juridique" value={form.legalForm} onChange={v => set({ legalForm: v })}
          options={["Auto-entrepreneur", "Entreprise individuelle (EI)", "EURL", "SASU", "SARL", "SAS", "Autre"]}/>
        <FormField label="SIRET" value={form.siret} onChange={v => set({ siret: v })} placeholder="123 456 789 00012"/>
        <FormField label="Adresse" value={form.address} onChange={v => set({ address: v })} placeholder="12 rue du Commerce"/>
        <div style={{ display: "grid", gridTemplateColumns: "120px 1fr", gap: 10 }}>
          <FormField label="Code postal" value={form.postalCode} onChange={v => set({ postalCode: v })} placeholder="75015"/>
          <FormField label="Ville" value={form.city} onChange={v => set({ city: v })} placeholder="Paris"/>
        </div>
        <FormSelect label="Statut TVA" value={form.vatStatus} onChange={v => set({ vatStatus: v })}
          options={[
            ["franchise",  "Franchise en base (auto-ent. / petite entreprise) — sans TVA"],
            ["applicable", "Assujetti·e à la TVA"],
          ]}/>
        {form.vatStatus === "applicable" && (
          <div style={{ display: "grid", gridTemplateColumns: "1fr 120px", gap: 10 }}>
            <FormField label="N° TVA intracommunautaire" value={form.vatNumber} onChange={v => set({ vatNumber: v })} placeholder="FR12345678901"/>
            <FormField label="Taux (%)" value={String(form.vatRate)} onChange={v => set({ vatRate: parseFloat(v) || 0 })}/>
          </div>
        )}
        <FormField label="IBAN (facultatif)" value={form.iban} onChange={v => set({ iban: v })} placeholder="FR76 XXXX XXXX XXXX"/>
        <div style={{ display: "grid", gridTemplateColumns: "140px 1fr", gap: 10 }}>
          <div>
            <label style={fieldLabelStyle}>Délai paiement (jours)</label>
            <input type="number" min="0" max="60" value={form.paymentTermsDays}
              onChange={e => set({ paymentTermsDays: parseInt(e.target.value, 10) || 0 })}
              style={fieldInputStyle}/>
          </div>
          <FormField label="Moyens de paiement acceptés" value={form.paymentMethods} onChange={v => set({ paymentMethods: v })}/>
        </div>
        <FormField label="Immatriculation RCS / RM (facultatif)" value={form.rcsRm} onChange={v => set({ rcsRm: v })}
          placeholder="ex : RCS Paris 123 456 789 — ou — RM 91 123 456 789"/>
        <div style={{ fontSize: 12, color: "var(--ink-4)", marginTop: -8, lineHeight: 1.5 }}>
          Commerçant&nbsp;: numéro RCS + ville du greffe. Artisan&nbsp;: numéro Répertoire des Métiers + département.
        </div>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10 }}>
          <FormField label="Médiateur de la consommation" value={form.mediatorName} onChange={v => set({ mediatorName: v })}
            placeholder="ex : MEDICYS"/>
          <FormField label="Site / contact du médiateur" value={form.mediatorUrl} onChange={v => set({ mediatorUrl: v })}
            placeholder="ex : medicys.fr"/>
        </div>
        <div style={{ fontSize: 12, color: "var(--ink-4)", marginTop: -8, lineHeight: 1.5 }}>
          Obligatoire pour tout pro vendant à des particuliers (art. L612-1 Code de la consommation).
          Trouver un médiateur référencé&nbsp;: <a href="https://www.economie.gouv.fr/mediation-conso" target="_blank" rel="noopener noreferrer" style={{ color: "var(--accent-ink)", fontWeight: 520 }}>economie.gouv.fr/mediation-conso</a>.
        </div>
        <div>
          <button className="btn btn-primary" onClick={() => saveSection([
            "address", "postalCode", "city", "siret", "legalForm",
            "vatStatus", "vatNumber", "vatRate", "iban", "paymentTermsDays", "paymentMethods",
            "rcsRm", "mediatorName", "mediatorUrl",
          ])}>
            Enregistrer
          </button>
        </div>
      </SettingsSection>
      )}

      {/* Contact ClientBase */}
      {(isMobile || tab === "contact") && (
      <SettingsSection title="Nous contacter" subtitle="Un bug, une question, une idée" tone="contact" {...accord("contact")}>
        <div style={{ fontSize: 13, color: "var(--ink-3)", marginTop: -4, lineHeight: 1.5 }}>
          Un bug, une suggestion, une question&nbsp;? On répond en moins de 24&nbsp;h ouvrées.
        </div>
        {contactSent ? (
          <div style={{
            padding: "16px 18px", background: "var(--sage-soft)",
            border: "1px solid oklch(88% 0.03 160)", borderRadius: 10,
            display: "flex", gap: 10, alignItems: "center",
            color: "oklch(38% 0.08 160)",
          }}>
            <Icon name="check" size={18} stroke={2.6}/>
            <div>
              <div style={{ fontWeight: 520 }}>Message envoyé</div>
              <div style={{ fontSize: 12.5, marginTop: 2 }}>On vous répond à {user?.email || "votre email"}.</div>
            </div>
          </div>
        ) : (
          <form onSubmit={sendContact} style={{ display: "flex", flexDirection: "column", gap: 12 }}>
            <FormSelect label="Sujet" value={contact.subject} onChange={v => setContact({ ...contact, subject: v })}
              options={["Question générale", "Bug à signaler", "Suggestion de fonctionnalité", "Demande commerciale", "Autre"]}/>
            <FormTextarea label="Votre message" value={contact.message} onChange={v => setContact({ ...contact, message: v })} rows={5} placeholder="Décrivez votre demande…"/>
            <div>
              <button type="submit" disabled={contactBusy} className="btn btn-primary" style={{ opacity: contactBusy ? 0.7 : 1 }}>
                <Icon name="mail" size={13}/> {contactBusy ? "Envoi…" : "Envoyer à l'équipe"}
              </button>
            </div>
          </form>
        )}
      </SettingsSection>

      )}

      {/* RGPD / Reset / Delete account */}
      {(isMobile || tab === "data") && (
      <SettingsSection title="Vos données" subtitle="RGPD, export, suppression" tone="data" {...accord("data")}>
        <div style={{ fontSize: 13, color: "var(--ink-2)", lineHeight: 1.6 }}>
          Vos données sont hébergées en France (Paris), dans l'Union européenne, et chiffrées en transit.{" "}
          {go && (
            <a href="#" onClick={(e) => { e.preventDefault(); go("legal"); }} style={{ color: "var(--accent-ink)", fontWeight: 520 }}>
              En savoir plus sur vos droits
            </a>
          )}.
        </div>

        {/* Export RGPD */}
        <div className="app-split-row" style={{
          display: "flex", justifyContent: "space-between", alignItems: "center",
          gap: 14, flexWrap: "wrap", paddingTop: 14, borderTop: "1px solid var(--line)",
        }}>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontWeight: 520, fontSize: 14 }}>Exporter mes données</div>
            <div style={{ fontSize: 13, color: "var(--ink-3)", marginTop: 4, lineHeight: 1.5 }}>
              Télécharge une copie complète de vos données au format JSON (compte, clients, RDV, factures, prestations, stock).
              Conforme au droit à la portabilité (RGPD&nbsp;art.&nbsp;20).
            </div>
          </div>
          <button className="btn btn-ghost" onClick={() => {
            try {
              const u = cbAuth.getCurrentUser() || {};
              const payload = {
                exportedAt: new Date().toISOString(),
                format: "ClientBase RGPD export v1",
                account: { email: u.email || "", name: u.name || "", emailVerified: !!u.emailVerified },
                data: data,
              };
              const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json" });
              const url = URL.createObjectURL(blob);
              const a = document.createElement("a");
              const stamp = new Date().toISOString().slice(0, 10);
              a.href = url;
              a.download = `clientbase-export-${stamp}.json`;
              document.body.appendChild(a);
              a.click();
              document.body.removeChild(a);
              setTimeout(() => URL.revokeObjectURL(url), 1000);
              showToast("Export téléchargé", "ok");
            } catch (e) {
              showToast("Échec de l'export — réessayez", "warn");
            }
          }}>
            <Icon name="arrow" size={12} style={{ transform: "rotate(90deg)" }}/> Télécharger (JSON)
          </button>
        </div>

        {/* Réinitialiser */}
        <div className="app-split-row" style={{
          display: "flex", justifyContent: "space-between", alignItems: "center",
          gap: 14, flexWrap: "wrap", paddingTop: 14, borderTop: "1px solid var(--line)",
        }}>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontWeight: 520, fontSize: 14 }}>Réinitialiser les données</div>
            <div style={{ fontSize: 13, color: "var(--ink-3)", marginTop: 4, lineHeight: 1.5 }}>
              Supprime <strong>définitivement</strong> toutes vos clients, RDV, factures, prestations et statistiques.
              Votre compte reste actif — vous pouvez recommencer de zéro.
            </div>
          </div>
          <button className="btn btn-ghost" onClick={() => setDangerModal("reset")}
            style={{ color: "oklch(55% 0.18 25)", borderColor: "oklch(85% 0.05 25)" }}>
            Réinitialiser
          </button>
        </div>

        {/* Supprimer le compte */}
        <div className="app-split-row" style={{
          display: "flex", justifyContent: "space-between", alignItems: "center",
          gap: 14, flexWrap: "wrap", paddingTop: 14, borderTop: "1px solid var(--line)",
        }}>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontWeight: 520, fontSize: 14, color: "oklch(45% 0.2 25)" }}>Supprimer mon compte</div>
            <div style={{ fontSize: 13, color: "var(--ink-3)", marginTop: 4, lineHeight: 1.5 }}>
              Supprime votre compte ClientBase <strong>et toutes vos données</strong>.
              Action <strong>irréversible</strong> — vous ne pourrez plus vous reconnecter avec cet email.
            </div>
          </div>
          <button onClick={() => setDangerModal("delete")} style={{
            padding: "8px 14px", fontSize: 13, fontWeight: 580,
            background: "oklch(55% 0.2 25)", color: "white",
            border: "none", borderRadius: 999, cursor: "pointer", fontFamily: "inherit",
          }}>
            Supprimer le compte
          </button>
        </div>
      </SettingsSection>

      )}

      {/* Compte — email + sécurité */}
      {((isMobile || tab === "account") && user) && (
        <SettingsSection title="Compte" subtitle="Email, sécurité, session" tone="account" {...accord("account")}>
          {/* Bloc identité avec badge vérifié/non vérifié */}
          <div style={{
            padding: "14px 16px",
            background: "var(--bg-alt)", border: "1px solid var(--line)",
            borderRadius: 12,
            display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap",
          }}>
            <div style={{
              width: 40, height: 40, borderRadius: 11, flexShrink: 0,
              background: emailVerified ? "var(--sage-soft)" : "var(--warn-soft)",
              color: emailVerified ? "var(--sage)" : "var(--warn-ink)",
              display: "flex", alignItems: "center", justifyContent: "center",
            }}>
              <Icon name={emailVerified ? "check" : "mail"} size={18} stroke={emailVerified ? 2.6 : 1.8}/>
            </div>
            <div style={{ flex: 1, minWidth: 180 }}>
              <div style={{ fontSize: 14, fontWeight: 560, color: "var(--ink)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
                {user.email}
              </div>
              <div style={{ marginTop: 4, display: "flex", alignItems: "center", gap: 6, flexWrap: "wrap" }}>
                {emailVerified ? (
                  <span style={{
                    display: "inline-flex", alignItems: "center", gap: 4,
                    padding: "2px 8px", fontSize: 11, fontWeight: 600,
                    background: "var(--sage-soft)", color: "oklch(38% 0.08 160)",
                    borderRadius: 999,
                  }}>
                    <Icon name="check" size={10} stroke={2.8}/> Vérifié
                  </span>
                ) : (
                  <span style={{
                    display: "inline-flex", alignItems: "center", gap: 4,
                    padding: "2px 8px", fontSize: 11, fontWeight: 600,
                    background: "var(--warn-soft)", color: "var(--warn-ink)",
                    borderRadius: 999,
                  }}>
                    <Icon name="clock" size={10} stroke={2.6}/> Non vérifié
                  </span>
                )}
                <span style={{ fontSize: 12, color: "var(--ink-4)" }}>
                  · Compte créé le {new Date(user.createdAt).toLocaleDateString("fr-FR")}
                </span>
              </div>
            </div>
          </div>

          {/* Action : re-envoyer l'email de vérification (si non vérifié) */}
          {!emailVerified && (
            <div style={{
              padding: "14px 16px",
              background: "var(--warn-soft)", border: "1px solid var(--warn-soft-2)",
              borderRadius: 12, color: "var(--warn-ink)",
              display: "flex", gap: 12, alignItems: "center", flexWrap: "wrap",
            }}>
              <div style={{ flex: 1, minWidth: 180 }}>
                <div style={{ fontSize: 13.5, fontWeight: 560 }}>Confirmez votre email</div>
                <div style={{ fontSize: 12.5, marginTop: 2, lineHeight: 1.5 }}>
                  Un email de confirmation a été envoyé à {user.email}. Cliquez sur le lien qu'il contient pour activer toutes les fonctionnalités.
                </div>
              </div>
              <button onClick={resendVerification} disabled={verifBusy || verifSent} className="btn btn-sm"
                style={{ background: "var(--warn-ink)", color: "white", opacity: (verifBusy || verifSent) ? 0.7 : 1 }}>
                {verifBusy ? "Envoi…" : verifSent ? "Envoyé ✓" : "Renvoyer l'email"}
              </button>
            </div>
          )}

          {/* Action : changer l'email */}
          <div style={{
            padding: "14px 16px",
            background: "var(--surface)", border: "1px solid var(--line)",
            borderRadius: 12,
          }}>
            <div style={{
              display: "flex", gap: 12, alignItems: "center", flexWrap: "wrap",
            }}>
              <div style={{
                width: 36, height: 36, borderRadius: 10, flexShrink: 0,
                background: "var(--accent-soft)", color: "var(--accent-ink)",
                display: "flex", alignItems: "center", justifyContent: "center",
              }}>
                <Icon name="mail" size={16}/>
              </div>
              <div style={{ flex: 1, minWidth: 160 }}>
                <div style={{ fontSize: 13.5, fontWeight: 560 }}>Changer l'adresse email</div>
                <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginTop: 2 }}>
                  On enverra un lien de confirmation à la nouvelle adresse.
                </div>
              </div>
              {!emailOpen && (
                <button onClick={() => setEmailOpen(true)} className="btn btn-sm btn-ghost">
                  Modifier
                </button>
              )}
            </div>
            {emailOpen && (
              <div style={{ marginTop: 12, display: "flex", flexDirection: "column", gap: 10 }}>
                <input type="email" value={newEmail} onChange={e => setNewEmail(e.target.value)}
                  placeholder="nouvelle@adresse.fr"
                  style={fieldInputStyle}/>
                {emailMsg && (
                  <div style={{
                    padding: "9px 12px", borderRadius: 9, fontSize: 13,
                    background: emailMsg.kind === "ok" ? "var(--sage-soft)" : "oklch(96% 0.04 25)",
                    border: `1px solid ${emailMsg.kind === "ok" ? "oklch(88% 0.03 160)" : "oklch(88% 0.06 25)"}`,
                    color:      emailMsg.kind === "ok" ? "oklch(38% 0.08 160)" : "oklch(42% 0.15 25)",
                  }}>{emailMsg.text}</div>
                )}
                <div style={{ display: "flex", gap: 8, justifyContent: "flex-end", flexWrap: "wrap" }}>
                  <button onClick={() => { setEmailOpen(false); setNewEmail(""); setEmailMsg(null); }}
                    className="btn btn-sm btn-ghost">Annuler</button>
                  <button onClick={submitChangeEmail} disabled={emailBusy} className="btn btn-sm btn-accent"
                    style={{ opacity: emailBusy ? 0.7 : 1 }}>
                    {emailBusy ? "Envoi…" : "Envoyer le lien"}
                  </button>
                </div>
              </div>
            )}
          </div>

          {/* Action : reset password */}
          <div style={{
            padding: "14px 16px",
            background: "var(--surface)", border: "1px solid var(--line)",
            borderRadius: 12,
            display: "flex", gap: 12, alignItems: "center", flexWrap: "wrap",
          }}>
            <div style={{
              width: 36, height: 36, borderRadius: 10, flexShrink: 0,
              background: "var(--accent-soft)", color: "var(--accent-ink)",
              display: "flex", alignItems: "center", justifyContent: "center",
            }}>
              <Icon name="lock" size={16}/>
            </div>
            <div style={{ flex: 1, minWidth: 160 }}>
              <div style={{ fontSize: 13.5, fontWeight: 560 }}>Réinitialiser le mot de passe</div>
              <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginTop: 2 }}>
                On envoie un lien sécurisé à {user.email}.
              </div>
            </div>
            <button onClick={sendPasswordReset} disabled={pwBusy || pwSent} className="btn btn-sm btn-ghost"
              style={{ opacity: (pwBusy || pwSent) ? 0.7 : 1 }}>
              {pwBusy ? "Envoi…" : pwSent ? "Envoyé ✓" : "Envoyer le lien"}
            </button>
          </div>

          {/* Logout — bouton rouge plein pour bien le distinguer */}
          <div style={{
            paddingTop: 12, borderTop: "1px solid var(--line)",
            display: "flex", justifyContent: "flex-end",
          }}>
            <button onClick={doLogout} style={{
              padding: "9px 16px", fontSize: 13.5, fontWeight: 580,
              background: "oklch(55% 0.2 25)", color: "white",
              border: "none", borderRadius: 999, cursor: "pointer", fontFamily: "inherit",
              display: "inline-flex", alignItems: "center", gap: 7,
            }}>
              <Icon name="arrow" size={13} style={{ transform: "rotate(180deg)" }}/>
              Se déconnecter
            </button>
          </div>
        </SettingsSection>
      )}

      {/* Modale de confirmation pour reset / delete account */}
      <DangerConfirmModal
        kind={dangerModal}
        onClose={() => setDangerModal(null)}
        onConfirm={async () => {
          const k = dangerModal;
          setDangerModal(null);
          if (k === "reset") await actions.reset();
          if (k === "delete") await actions.deleteAccount();
        }}
      />
    </div>
  );
};

/* Modale de confirmation forte — exige de TAPER un mot pour valider une
   action irréversible (réinitialiser ou supprimer le compte). */
const DangerConfirmModal = ({ kind, onClose, onConfirm }) => {
  const [typed, setTyped] = React.useState("");
  React.useEffect(() => { setTyped(""); }, [kind]);

  if (!kind) return null;
  const isDelete = kind === "delete";
  const requiredWord = isDelete ? "SUPPRIMER" : "RESET";
  const valid = typed.trim().toUpperCase() === requiredWord;

  const title = isDelete ? "Supprimer définitivement votre compte ?" : "Réinitialiser toutes vos données ?";

  return (
    <Modal open={true} onClose={onClose}
      title={title}
      footer={
        <>
          <button className="btn btn-ghost" onClick={onClose}>Annuler</button>
          <button onClick={onConfirm} disabled={!valid} style={{
            padding: "9px 16px", fontSize: 13.5, fontWeight: 580,
            background: valid ? "oklch(55% 0.2 25)" : "var(--bg-alt)",
            color: valid ? "white" : "var(--ink-4)",
            border: "none", borderRadius: 999,
            cursor: valid ? "pointer" : "not-allowed", fontFamily: "inherit",
          }}>
            {isDelete ? "Supprimer mon compte" : "Tout réinitialiser"}
          </button>
        </>
      }>
      <div style={{ display: "grid", gap: 14 }}>
        <div style={{
          padding: "14px 16px",
          background: "oklch(96% 0.04 25)", border: "1px solid oklch(88% 0.06 25)",
          borderRadius: 10, color: "oklch(35% 0.15 25)", fontSize: 13.5, lineHeight: 1.55,
        }}>
          <strong>⚠️ Cette action est irréversible.</strong>
          <ul style={{ margin: "8px 0 0", paddingLeft: 18, lineHeight: 1.55 }}>
            <li>Toutes vos clients seront supprimées</li>
            <li>Tous vos rendez-vous (passés et à venir) seront supprimés</li>
            <li>Toutes vos factures, prestations et statistiques seront perdues</li>
            <li>Aucune sauvegarde ne sera conservée — <strong>impossible de récupérer</strong></li>
            {isDelete && (
              <li style={{ marginTop: 6, fontWeight: 580 }}>
                Votre compte ClientBase ({(window.cbAuth && cbAuth.getCurrentUser() && cbAuth.getCurrentUser().email) || "vous"}) sera supprimé
              </li>
            )}
          </ul>
        </div>
        <div>
          <label style={{ fontSize: 13, color: "var(--ink-2)", display: "block", marginBottom: 6 }}>
            Pour confirmer, tapez <strong style={{ color: "oklch(55% 0.2 25)" }}>{requiredWord}</strong> ci-dessous&nbsp;:
          </label>
          <input value={typed} onChange={e => setTyped(e.target.value)} autoFocus
            placeholder={requiredWord}
            style={{ ...fieldInputStyle, letterSpacing: "0.04em", fontWeight: 500 }}/>
        </div>
      </div>
    </Modal>
  );
};

/* ================================================================
   SUBSCRIPTION — trial status, plan comparison, upgrade CTAs
================================================================ */
const SUBSCRIPTION_PLANS = [
  {
    id: "free",
    name: "Découverte",
    price: 0,
    tagline: "Pour tester tranquillement",
    cta: "Revenir en gratuit",
    features: [
      { kind: "check", t: "Agenda personnel" },
      { kind: "check", t: "Fiches clients (jusqu'à 20)" },
      { kind: "check", t: "Suivi du stock" },
      { kind: "check", t: "Support client" },
      { kind: "cross", t: "Page publique de réservation" },
      { kind: "cross", t: "Cartes de fidélité" },
      { kind: "cross", t: "Facturation" },
      { kind: "cross", t: "Statistiques détaillées" },
    ],
    disabled: true,
  },
  {
    id: "pro",
    name: "Pro",
    price: 9.99,
    tagline: "Le cœur de l'activité",
    cta: "Passer en Pro",
    highlight: true,
    disabled: true,
    features: [
      { kind: "check", t: "Agenda personnel" },
      { kind: "check", t: "Fiches clients (jusqu'à 50)" },
      { kind: "check", t: "Page publique de réservation" },
      { kind: "check", t: "Facturation conforme (jusqu'à 100 / mois)" },
      { kind: "check", t: "Statistiques détaillées" },
      { kind: "check", t: "Support client" },
      { kind: "cross", t: "Suivi du stock" },
      { kind: "cross", t: "Cartes de fidélité" },
    ],
  },
  {
    id: "business",
    name: "Business",
    price: 19.99,
    tagline: "Pour les équipes ambitieuses",
    cta: "Choisir Business",
    betaFree: true,
    features: [
      { kind: "check", t: "Clientes illimitées" },
      { kind: "check", t: "Factures illimitées, conformes françaises" },
      { kind: "check", t: "Page publique de réservation" },
      { kind: "check", t: "Cartes de fidélité partageables" },
      { kind: "check", t: "Suivi du stock + alertes" },
      { kind: "check", t: "Statistiques détaillées (jour/semaine/mois)" },
      { kind: "check", t: "Support client prioritaire" },
    ],
  },
];

const AppSubscription = ({ data, go, setMod }) => {
  // Bêta ouverte pour 3 mois à partir d'aujourd'hui. Durée susceptible d'être allongée.
  const betaDurationDays = 90;
  const betaStart = React.useMemo(() => {
    const saved = localStorage.getItem("cb_beta_start_v1");
    if (saved) return +saved;
    const now = Date.now();
    localStorage.setItem("cb_beta_start_v1", String(now));
    return now;
  }, []);
  const betaEnd = betaStart + betaDurationDays * 24 * 60 * 60 * 1000;

  // Countdown qui tick toutes les secondes
  const [now, setNow] = React.useState(() => Date.now());
  React.useEffect(() => {
    const t = setInterval(() => setNow(Date.now()), 1000);
    return () => clearInterval(t);
  }, []);

  const remaining = Math.max(0, betaEnd - now);
  const days    = Math.floor(remaining / (1000 * 60 * 60 * 24));
  const hours   = Math.floor((remaining / (1000 * 60 * 60)) % 24);
  const minutes = Math.floor((remaining / (1000 * 60)) % 60);
  const seconds = Math.floor((remaining / 1000) % 60);
  const trialPct = Math.max(0, Math.min(100, ((betaDurationDays * 24 * 60 * 60 * 1000 - remaining) / (betaDurationDays * 24 * 60 * 60 * 1000)) * 100));
  const currentPlan = "free";

  return (
    <div style={{ maxWidth: 880 }}>
      {/* Trial status banner */}
      <div style={{
        padding: "22px 24px", marginBottom: 22,
        background: "linear-gradient(135deg, var(--accent) 0%, oklch(45% 0.22 300) 100%)",
        borderRadius: 16, color: "white",
        position: "relative", overflow: "hidden",
      }}>
        <div aria-hidden style={{
          position: "absolute", top: -40, right: -40,
          width: 160, height: 160, borderRadius: "50%",
          background: "rgba(255,255,255,0.12)", pointerEvents: "none",
        }}/>
        <div style={{ position: "relative", display: "flex", justifyContent: "space-between", alignItems: "center", gap: 16, flexWrap: "wrap" }}>
          <div>
            <style>{`
              @keyframes cbHeartbeat {
                0%, 100% { transform: scale(1); }
                50%      { transform: scale(1.08); }
              }
              @keyframes cbBlink {
                0%, 100% { opacity: 1; }
                50%      { opacity: 0.35; }
              }
            `}</style>
            <div style={{ display: "inline-flex", alignItems: "center", gap: 8 }}>
              <span style={{
                width: 8, height: 8, borderRadius: 50, background: "white",
                animation: "cbHeartbeat 1.4s ease-in-out infinite",
              }}/>
              <div style={{ fontSize: 12, opacity: 0.85, textTransform: "uppercase", letterSpacing: "0.06em", fontWeight: 520 }}>
                Bêta en cours — accès Business offert
              </div>
            </div>
            {/* Countdown tiles — tick every second */}
            <div style={{ display: "flex", gap: 8, marginTop: 10, flexWrap: "wrap" }}>
              {[
                { v: days,    l: days > 1 ? "jours" : "jour" },
                { v: hours,   l: "h" },
                { v: minutes, l: "min" },
                { v: seconds, l: "s", blink: true },
              ].map((x, i) => (
                <div key={i} style={{
                  background: "rgba(255,255,255,0.14)",
                  border: "1px solid rgba(255,255,255,0.18)",
                  borderRadius: 10, padding: "8px 10px",
                  minWidth: 56, textAlign: "center",
                  backdropFilter: "blur(4px)",
                  WebkitBackdropFilter: "blur(4px)",
                }}>
                  <div style={{
                    fontFamily: "var(--ff-display)", fontSize: 22, fontWeight: 600,
                    letterSpacing: "-0.02em", lineHeight: 1, fontVariantNumeric: "tabular-nums",
                    animation: x.blink ? "cbBlink 1s ease-in-out infinite" : "none",
                  }}>
                    {String(x.v).padStart(2, "0")}
                  </div>
                  <div style={{ fontSize: 10, opacity: 0.82, marginTop: 3, letterSpacing: "0.04em" }}>
                    {x.l}
                  </div>
                </div>
              ))}
            </div>
            <div style={{ marginTop: 10, width: 260, maxWidth: "100%", height: 5, background: "rgba(255,255,255,0.2)", borderRadius: 999, overflow: "hidden" }}>
              <div style={{
                width: `${trialPct}%`, height: "100%",
                background: "linear-gradient(90deg, white 0%, oklch(90% 0.12 300) 100%)",
                borderRadius: 999, transition: "width 1s linear",
              }}/>
            </div>
          </div>
          <div style={{ fontSize: 13, lineHeight: 1.5, maxWidth: 300, opacity: 0.92 }}>
            Durée prévue : <strong>3 mois</strong> — mais <strong>ce délai peut être allongé</strong>,
            rien n'est encore figé. Pendant toute la bêta, toutes les fonctionnalités <strong>Business</strong> sont
            débloquées gratuitement.
          </div>
        </div>
      </div>

      {/* Plan comparison */}
      <div style={{
        display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 14,
        marginBottom: 22,
      }} className="app-col-3">
        {SUBSCRIPTION_PLANS.map(p => (
          <div key={p.id} style={{
            padding: "22px 20px", borderRadius: 16,
            background: p.betaFree
              ? "linear-gradient(180deg, var(--sage-soft) 0%, var(--surface) 70%)"
              : p.highlight ? "linear-gradient(180deg, var(--accent-soft) 0%, var(--surface) 70%)"
              : "var(--surface)",
            border: p.betaFree ? "1px solid oklch(88% 0.03 160)"
              : p.highlight ? "1px solid var(--accent-soft-2)"
              : "1px solid var(--line)",
            position: "relative", display: "flex", flexDirection: "column", gap: 14,
            opacity: p.disabled ? 0.6 : 1,
            filter: p.disabled ? "grayscale(0.35)" : "none",
            transition: "opacity .2s, filter .2s, transform .2s",
          }}>
            {p.highlight && !p.disabled && (
              <div style={{
                position: "absolute", top: -10, left: 20,
                padding: "3px 10px", background: "var(--accent)",
                color: "white", borderRadius: 999, fontSize: 10.5, fontWeight: 600,
                letterSpacing: "0.04em", textTransform: "uppercase",
              }}>Recommandé</div>
            )}
            {p.betaFree && (
              <div style={{
                position: "absolute", top: -10, left: 20,
                padding: "3px 10px", background: "var(--sage)",
                color: "white", borderRadius: 999, fontSize: 10.5, fontWeight: 600,
                letterSpacing: "0.04em", textTransform: "uppercase",
              }}>Gratuit pendant la bêta</div>
            )}
            <div>
              <div style={{ fontSize: 13, color: "var(--ink-4)", fontWeight: 500 }}>{p.tagline}</div>
              <div style={{ fontSize: 18, fontWeight: 580, marginTop: 2, letterSpacing: "-0.01em" }}>{p.name}</div>
            </div>
            <div>
              <span style={{
                fontFamily: "var(--ff-display)", fontSize: 36, fontWeight: 580,
                letterSpacing: "-0.03em",
              }}>{p.price === 0 ? "0 €" : `${p.price.toString().replace('.', ',')} €`}</span>
              <span style={{ fontSize: 13, color: "var(--ink-4)", marginLeft: 6 }}>/ mois</span>
            </div>
            <ul style={{ listStyle: "none", padding: 0, margin: 0, display: "flex", flexDirection: "column", gap: 8, fontSize: 13, color: "var(--ink-2)" }}>
              {p.features.map(f => {
                const isCross = f.kind === "cross";
                const isSoon  = f.kind === "soon";
                return (
                  <li key={f.t} style={{ display: "flex", gap: 8, alignItems: "flex-start", lineHeight: 1.4 }}>
                    <span style={{
                      width: 18, height: 18, borderRadius: 50, flexShrink: 0, marginTop: 1,
                      background: isCross ? "var(--bg-alt)" : isSoon ? "var(--accent-soft)" : "var(--sage-soft)",
                      color: isCross ? "var(--ink-4)" : isSoon ? "var(--accent-ink)" : "var(--sage)",
                      display: "inline-flex", alignItems: "center", justifyContent: "center",
                    }}>
                      <Icon name={isCross ? "close" : isSoon ? "clock" : "check"} size={11} stroke={2.6}/>
                    </span>
                    <span style={{
                      color: isCross ? "var(--ink-4)" : isSoon ? "var(--ink-3)" : "var(--ink-2)",
                      textDecoration: isCross ? "line-through" : "none",
                    }}>
                      {f.t}
                      {isSoon && (
                        <span style={{
                          marginLeft: 6, padding: "1px 7px",
                          background: "var(--accent-soft)", color: "var(--accent-ink)",
                          borderRadius: 6, fontSize: 10, fontWeight: 600,
                        }}>Bientôt</span>
                      )}
                    </span>
                  </li>
                );
              })}
            </ul>
            <button
              disabled={p.disabled || currentPlan === p.id}
              onClick={() => showToast("Bientôt disponible — on vous préviendra !")}
              className={`btn ${p.betaFree ? "btn-accent" : p.highlight ? "btn-accent" : "btn-ghost"}`}
              style={{
                marginTop: "auto",
                opacity: p.disabled ? 0.5 : 1,
                cursor: p.disabled ? "not-allowed" : "pointer",
              }}>
              {currentPlan === p.id ? "Plan actuel" : p.cta}
            </button>
          </div>
        ))}
      </div>

      {/* Mini-pills de réassurance */}
      <div style={{ display: "flex", flexWrap: "wrap", gap: 6, marginTop: 4 }}>
        {[
          "🇫🇷 Hébergé en France",
          "🔒 RGPD & chiffré",
          "🤝 Sans engagement",
        ].map(t => (
          <span key={t} style={{
            display: "inline-flex", alignItems: "center", gap: 5,
            padding: "5px 11px",
            background: "var(--surface)", border: "1px solid var(--line)",
            borderRadius: 999, fontSize: 12, fontWeight: 500, color: "var(--ink-2)",
          }}>{t}</span>
        ))}
      </div>

      {/* Nouveautés intégrées à l'abonnement */}
      <SubscriptionChangelog data={data}/>
    </div>
  );
};

/* Nouveautés affichées sous les plans — rappelle ce qui arrive et dans quel plan.
   Dépliable pour rester compact par défaut. */
const SubscriptionChangelog = ({ data }) => {
  const [open, setOpen] = React.useState(false);
  const [msg, setMsg] = React.useState("");
  const [busy, setBusy] = React.useState(false);
  const [sent, setSent] = React.useState(false);
  const changelog = (typeof window !== "undefined" && window.CB_CHANGELOG) || [];

  const sendIdea = async () => {
    if (!msg.trim() || msg.trim().length < 6) { showToast("Dites-nous un peu plus 🙂", "warn"); return; }
    if (!window.sendContactMail) { showToast("Envoi indisponible", "warn"); return; }
    setBusy(true);
    const u = (window.cbAuth && window.cbAuth.getCurrentUser()) || {};
    const res = await window.sendContactMail({
      subject: `[ClientBase — Idée feature] ${u.email || "Utilisateur"}`,
      from:    u.ownerName || u.email || "Utilisateur",
      replyTo: u.email || "no-reply@clientbase.fr",
      message: msg,
      meta: {
        Activité: (data.business && data.business.name) || "—",
        Source:  "App pro / Abonnement → Suggérer une feature",
      },
    });
    setBusy(false);
    if (res.error) { showToast(res.error, "warn"); return; }
    setSent(true); setMsg("");
    setTimeout(() => setSent(false), 4500);
    showToast("Merci pour votre idée ! ✨");
  };

  return (
    <div style={{
      marginTop: 20,
      background: "var(--bg-alt)", border: "1px solid var(--line)",
      borderRadius: 16, overflow: "hidden",
    }}>
      <button onClick={() => setOpen(!open)} style={{
        width: "100%", padding: "18px 22px",
        display: "flex", alignItems: "center", gap: 14,
        background: "transparent", border: "none", cursor: "pointer",
        fontFamily: "inherit", textAlign: "left",
      }}>
        <div style={{
          width: 40, height: 40, borderRadius: 11, flexShrink: 0,
          background: "var(--accent-soft)", color: "var(--accent-ink)",
          display: "flex", alignItems: "center", justifyContent: "center",
          border: "1px solid var(--accent-soft-2)",
        }}>
          <Icon name="sparkle" size={18}/>
        </div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 11, fontWeight: 600, color: "var(--accent-ink)", textTransform: "uppercase", letterSpacing: "0.06em" }}>
            Ce qui arrive bientôt
          </div>
          <div style={{ fontSize: 15, fontWeight: 580, letterSpacing: "-0.015em", color: "var(--ink)", marginTop: 2 }}>
            Roadmap ClientBase
          </div>
          <div style={{ fontSize: 12, color: "var(--ink-4)", marginTop: 2 }}>
            {open ? "Fermer" : "Voir ce qui est prévu + suggérer une idée"}
          </div>
        </div>
        <div style={{
          width: 30, height: 30, borderRadius: 9, flexShrink: 0,
          background: "var(--surface)", border: "1px solid var(--line)",
          display: "flex", alignItems: "center", justifyContent: "center",
          color: "var(--ink-3)",
          transition: "transform .3s cubic-bezier(0.22, 1, 0.36, 1)",
          transform: open ? "rotate(45deg)" : "rotate(0)",
        }}>
          <Icon name="plus" size={14}/>
        </div>
      </button>

      <div style={{
        display: "grid",
        gridTemplateRows: open ? "1fr" : "0fr",
        opacity: open ? 1 : 0,
        transition: "grid-template-rows .4s cubic-bezier(0.22, 1, 0.36, 1), opacity .3s ease",
      }}>
        <div style={{ overflow: "hidden" }}>
          <div style={{ padding: "0 22px 20px" }}>
            {/* Timeline courte */}
            <div style={{ position: "relative" }}>
              <div aria-hidden style={{
                position: "absolute", left: 11, top: 6, bottom: 6, width: 2,
                background: "linear-gradient(180deg, var(--accent-soft-2), var(--line))",
                borderRadius: 2,
              }}/>
              {changelog.map((r, i) => (
                <div key={r.version} style={{
                  position: "relative", paddingLeft: 34,
                  paddingBottom: i < changelog.length - 1 ? 16 : 0,
                }}>
                  <div style={{
                    position: "absolute", left: 2, top: 4,
                    width: 18, height: 18, borderRadius: 50,
                    background: r.upcoming ? "var(--surface)" : "var(--accent)",
                    border: `2px solid ${r.upcoming ? "var(--accent-soft-2)" : "var(--accent)"}`,
                    display: "flex", alignItems: "center", justifyContent: "center",
                    color: "white",
                  }}>
                    {!r.upcoming && <Icon name="check" size={9} stroke={3}/>}
                  </div>
                  <div style={{
                    background: "var(--surface)", border: "1px solid var(--line)",
                    borderRadius: 12, padding: "12px 14px",
                  }}>
                    <div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
                      <span style={{
                        fontSize: 11, padding: "2px 7px",
                        background: r.upcoming ? "var(--bg-alt)" : "var(--accent-soft)",
                        color: r.upcoming ? "var(--ink-3)" : "var(--accent-ink)",
                        borderRadius: 6, fontWeight: 600,
                      }}>v{r.version}</span>
                      <span style={{ fontSize: 11.5, color: "var(--ink-4)" }}>{r.date}</span>
                      <span style={{
                        padding: "2px 7px", fontSize: 10, fontWeight: 600,
                        background: r.upcoming ? "oklch(95% 0.05 300)" : "var(--sage-soft)",
                        color:      r.upcoming ? "oklch(42% 0.18 300)" : "oklch(38% 0.08 160)",
                        borderRadius: 999,
                      }}>{r.tag}</span>
                    </div>
                    <div style={{ marginTop: 6, fontSize: 14, fontWeight: 560, letterSpacing: "-0.01em" }}>{r.title}</div>
                    <ul style={{ listStyle: "none", padding: 0, margin: "8px 0 0", display: "flex", flexDirection: "column", gap: 5 }}>
                      {r.items.slice(0, 3).map(it => (
                        <li key={it} style={{ display: "flex", gap: 8, alignItems: "flex-start", fontSize: 12.5, color: "var(--ink-2)", lineHeight: 1.45 }}>
                          <span style={{
                            width: 14, height: 14, borderRadius: 50, flexShrink: 0, marginTop: 2,
                            background: r.upcoming ? "var(--bg-alt)" : "var(--sage-soft)",
                            color: r.upcoming ? "var(--ink-4)" : "var(--sage)",
                            display: "inline-flex", alignItems: "center", justifyContent: "center",
                          }}>
                            <Icon name={r.upcoming ? "clock" : "check"} size={8} stroke={2.6}/>
                          </span>
                          {it}
                        </li>
                      ))}
                      {r.items.length > 3 && (
                        <li style={{ fontSize: 11.5, color: "var(--ink-4)", paddingLeft: 22 }}>
                          + {r.items.length - 3} autre{r.items.length - 3 > 1 ? "s" : ""}…
                        </li>
                      )}
                    </ul>
                  </div>
                </div>
              ))}
            </div>

            {/* Suggérer une idée */}
            <div style={{
              marginTop: 16, padding: "16px 18px",
              background: "var(--surface)", border: "1px solid var(--accent-soft-2)",
              borderRadius: 12,
            }}>
              <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
                <div style={{
                  width: 32, height: 32, borderRadius: 9, flexShrink: 0,
                  background: "var(--accent)", color: "white",
                  display: "flex", alignItems: "center", justifyContent: "center",
                }}>
                  <Icon name="heart" size={14}/>
                </div>
                <div>
                  <div style={{ fontSize: 11, fontWeight: 600, color: "var(--accent-ink)", textTransform: "uppercase", letterSpacing: "0.04em" }}>
                    Votre idée
                  </div>
                  <div style={{ fontSize: 14, fontWeight: 560, color: "var(--ink)", letterSpacing: "-0.01em", marginTop: 1 }}>
                    Qu'est-ce qu'on devrait ajouter ?
                  </div>
                </div>
              </div>
              {sent ? (
                <div style={{
                  marginTop: 12, padding: "10px 12px",
                  background: "var(--sage-soft)", border: "1px solid oklch(88% 0.03 160)",
                  borderRadius: 10, fontSize: 13, color: "oklch(38% 0.08 160)",
                  display: "flex", gap: 8, alignItems: "center",
                }}>
                  <Icon name="check" size={15} stroke={2.6}/>
                  Merci ! Votre idée est bien arrivée.
                </div>
              ) : (
                <>
                  <textarea value={msg} onChange={e => setMsg(e.target.value)}
                    rows={2}
                    placeholder="Ex. « J'aimerais envoyer une carte cadeau pour l'anniversaire d'un client. »"
                    style={{
                      marginTop: 12, width: "100%", padding: "10px 12px",
                      fontSize: 16, fontFamily: "inherit", background: "var(--bg)",
                      border: "1px solid var(--line-strong)", borderRadius: 9,
                      color: "var(--ink)", outline: "none", resize: "vertical", minHeight: 64,
                      boxSizing: "border-box",
                    }}/>
                  <div style={{ marginTop: 10, display: "flex", justifyContent: "flex-end" }}>
                    <button onClick={sendIdea} disabled={busy} className="btn btn-sm btn-accent"
                      style={{ opacity: busy ? 0.7 : 1 }}>
                      {busy ? "Envoi…" : "Envoyer l'idée"} <Icon name="arrow" size={12}/>
                    </button>
                  </div>
                </>
              )}
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

/* ================================================================
   MODAL FORMS
================================================================ */
const ClientFormModal = ({ modal, onClose, data, actions }) => {
  const open = modal && modal.type === "client";
  const editing = open && modal.ctx && modal.ctx.id ? findClient(data, modal.ctx.id) : null;
  const [form, setForm] = React.useState({ name: "", email: "", phone: "", birthday: "", social: "", notes: "" });

  React.useEffect(() => {
    if (!open) return;
    if (editing) {
      setForm({
        name: editing.name, email: editing.email || "",
        phone: editing.phone || "", birthday: editing.birthday || "",
        social: editing.social || "", notes: editing.notes || "",
      });
    } else {
      setForm({ name: "", email: "", phone: "", birthday: "", social: "", notes: "" });
    }
  }, [open, editing && editing.id]);

  const submit = () => {
    if (!form.name.trim()) { showToast("Le nom est requis", "warn"); return; }
    if (editing) actions.updateClient(editing.id, form);
    else actions.addClient(form);
    onClose();
  };

  return (
    <Modal open={!!open} onClose={onClose}
      title={editing ? `Modifier — ${editing.name}` : "Nouveau client"}
      footer={
        <>
          {editing && (
            <button className="btn btn-ghost cb-btn-destructive"
              onClick={() => { actions.deleteClient(editing.id); onClose(); }}>Supprimer</button>
          )}
          <button className="btn btn-ghost" onClick={onClose}>Annuler</button>
          <button className="btn btn-primary" onClick={submit}>{editing ? "Enregistrer" : "Créer la fiche"}</button>
        </>
      }>
      <div style={{ display: "grid", gap: 14 }}>
        <FormField label="Nom complet" value={form.name} onChange={v => setForm({ ...form, name: v })} placeholder="Léa Morel" required autoFocus/>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14 }}>
          <FormField label="Email" type="email" value={form.email} onChange={v => setForm({ ...form, email: v })} placeholder="lea@exemple.fr"/>
          <FormField label="Téléphone" value={form.phone} onChange={v => setForm({ ...form, phone: v })} placeholder="06 12 34 56 78"/>
        </div>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14 }}>
          <FormField label="Anniversaire (optionnel)" value={form.birthday} onChange={v => setForm({ ...form, birthday: v })} placeholder="JJ/MM — ex. 14/03"/>
          <FormField label={`${CB_SOCIAL_LABEL[(data.bookingSettings && data.bookingSettings.preferredSocial) || "instagram"] || "Réseau social"} (optionnel)`}
            value={form.social} onChange={v => setForm({ ...form, social: v })}
            placeholder={CB_SOCIAL_PLACEHOLDER[(data.bookingSettings && data.bookingSettings.preferredSocial) || "instagram"] || "@pseudo"}/>
        </div>
        <FormTextarea label="Notes (allergies, préférences…)" value={form.notes} onChange={v => setForm({ ...form, notes: v })} placeholder="Préfère les couleurs nude." rows={3}/>
      </div>
    </Modal>
  );
};

const AppointmentFormModal = ({ modal, onClose, data, actions }) => {
  const open = modal && modal.type === "appointment";
  const ctx = (modal && modal.ctx) || {};
  const [form, setForm] = React.useState({
    clientId: "", serviceId: "", day: cbTodayOffset(), h: 9, d: 1, color: "accent",
  });

  React.useEffect(() => {
    if (!open) return;
    const initialSvc = ctx.serviceId
      ? data.services.find(s => s.id === ctx.serviceId)
      : data.services[0];
    setForm({
      clientId: ctx.clientId || (data.clients[0] ? data.clients[0].id : ""),
      serviceId: ctx.serviceId || (data.services[0] ? data.services[0].id : ""),
      day: ctx.day != null ? ctx.day : cbTodayOffset(),
      h:   ctx.h   != null ? ctx.h   : 9,
      d: initialSvc ? Number(initialSvc.duration) || 1 : 1,
      color: "accent",
    });
  }, [open, ctx.day, ctx.h, ctx.clientId, ctx.serviceId]);

  // Quand on change la prestation, on récupère sa durée par défaut.
  // L'utilisateur peut ensuite modifier librement via le picker minutes.
  const onServiceChange = (newId) => {
    const svc = data.services.find(s => s.id === newId);
    setForm(f => ({
      ...f,
      serviceId: newId,
      d: svc ? Number(svc.duration) || 1 : f.d,
    }));
  };

  const submit = () => {
    if (!form.clientId || !form.serviceId) { showToast("Cliente et prestation sont requises", "warn"); return; }
    actions.addAppointment({ ...form, d: Number(form.d) || 1 });
    onClose();
  };

  const hourOptions = [];
  for (let h = 8; h < 19; h += 0.25) {
    hourOptions.push([h.toString(), fmtH(h)]);
  }

  // Options de durée en MINUTES, par tranches de 15 min jusqu'à 4 h.
  // value = durée en HEURES (cohérent avec le stockage de a.d).
  const durationOptions = [];
  for (let m = 15; m <= 240; m += 15) {
    const hours = m / 60;
    const label = m < 60
      ? `${m} min`
      : m % 60 === 0
        ? `${m / 60} h`
        : `${Math.floor(m / 60)} h ${String(m % 60).padStart(2, "0")}`;
    durationOptions.push([String(hours), label]);
  }

  return (
    <Modal open={!!open} onClose={onClose}
      title="Nouveau rendez-vous"
      footer={
        <>
          <button className="btn btn-ghost" onClick={onClose}>Annuler</button>
          <button className="btn btn-primary" onClick={submit}>Ajouter au planning</button>
        </>
      }>
      <div style={{ display: "grid", gap: 14 }}>
        <FormSelect label="Cliente" value={form.clientId} onChange={v => setForm({ ...form, clientId: v })} required
          options={data.clients.length === 0 ? [["", "— Aucun client. Ajoutez-en d'abord —"]] : data.clients.map(c => [c.id, c.name])}/>
        <FormSelect label="Prestation" value={form.serviceId} onChange={onServiceChange} required
          options={data.services.map(s => [s.id, `${s.name} — ${fmtEUR(s.price)}`])}/>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14 }}>
          <FormSelect label="Jour" value={String(form.day)} onChange={v => setForm({ ...form, day: parseInt(v, 10) })}
            options={DAY_LABELS.map((d, i) => [String(_AGENDA_MONDAY_OFFSET + i), d])}/>
          <FormSelect label="Heure" value={String(form.h)} onChange={v => setForm({ ...form, h: parseFloat(v) })}
            options={hourOptions}/>
        </div>
        <FormSelect label="Durée" value={String(form.d)} onChange={v => setForm({ ...form, d: parseFloat(v) })}
          options={durationOptions}/>
        <FormSelect label="Couleur" value={form.color} onChange={v => setForm({ ...form, color: v })} options={COLOR_CHOICES}/>
      </div>
    </Modal>
  );
};

const InvoiceFormModal = ({ modal, onClose, data, actions }) => {
  const open = modal && modal.type === "invoice";
  const [form, setForm] = React.useState({ clientId: "", designation: "", amount: "", paid: false });

  React.useEffect(() => {
    if (!open) return;
    setForm({
      clientId: data.clients[0] ? data.clients[0].id : "",
      designation: "", amount: "", paid: false,
    });
  }, [open]);

  const submit = () => {
    const amt = parseFloat((form.amount || "").replace(",", "."));
    if (!form.clientId) { showToast("Choisissez un client", "warn"); return; }
    if (!form.designation.trim()) { showToast("Désignation requise (description de la prestation)", "warn"); return; }
    if (!amt || amt <= 0) { showToast("Montant invalide", "warn"); return; }
    actions.addInvoice({
      clientId: form.clientId,
      designation: form.designation.trim(),
      amount: amt,
      paid: form.paid,
    });
    onClose();
  };

  return (
    <Modal open={!!open} onClose={onClose} title="Nouvelle facture"
      footer={
        <>
          <button className="btn btn-ghost" onClick={onClose}>Annuler</button>
          <button className="btn btn-primary" onClick={submit}>Créer</button>
        </>
      }>
      <div style={{ display: "grid", gap: 14 }}>
        <FormSelect label="Cliente" value={form.clientId} onChange={v => setForm({ ...form, clientId: v })} required
          options={data.clients.map(c => [c.id, c.name])}/>
        <FormField label="Désignation de la prestation" value={form.designation} onChange={v => setForm({ ...form, designation: v })}
          placeholder="ex : Coupe + brushing femme" required/>
        <FormField label="Montant TTC (€)" value={form.amount} onChange={v => setForm({ ...form, amount: v })} placeholder="45,00" required/>
        <label style={{ display: "flex", alignItems: "center", gap: 10, fontSize: 13.5, cursor: "pointer" }}>
          <input type="checkbox" checked={form.paid} onChange={e => setForm({ ...form, paid: e.target.checked })}
            style={{ accentColor: "var(--accent)", width: 16, height: 16 }}/>
          Marquer comme payée
        </label>
      </div>
    </Modal>
  );
};

const StockFormModal = ({ modal, onClose, actions }) => {
  const open = modal && modal.type === "stock";
  const [form, setForm] = React.useState({ name: "", qty: "", min: "", price: "" });

  React.useEffect(() => {
    if (!open) return;
    setForm({ name: "", qty: "", min: "", price: "" });
  }, [open]);

  const submit = () => {
    if (!form.name.trim()) { showToast("Nom du produit requis", "warn"); return; }
    const qty = parseInt(form.qty, 10) || 0;
    const min = parseInt(form.min, 10) || 0;
    const price = parseFloat((form.price || "").replace(",", ".")) || 0;
    actions.addStockItem({ name: form.name.trim(), qty, min, price });
    onClose();
  };

  return (
    <Modal open={!!open} onClose={onClose} title="Nouveau produit"
      footer={
        <>
          <button className="btn btn-ghost" onClick={onClose}>Annuler</button>
          <button className="btn btn-primary" onClick={submit}>Ajouter</button>
        </>
      }>
      <div style={{ display: "grid", gap: 14 }}>
        <FormField label="Nom du produit" value={form.name} onChange={v => setForm({ ...form, name: v })} placeholder="Base coat OPI" required autoFocus/>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 10 }}>
          <FormField label="Stock actuel" value={form.qty} onChange={v => setForm({ ...form, qty: v })} placeholder="10"/>
          <FormField label="Seuil mini" value={form.min} onChange={v => setForm({ ...form, min: v })} placeholder="3"/>
          <FormField label="Prix (€)" value={form.price} onChange={v => setForm({ ...form, price: v })} placeholder="12,90"/>
        </div>
      </div>
    </Modal>
  );
};

const PromoFormModal = ({ modal, onClose, actions }) => {
  const open = modal && modal.type === "promo";
  const [form, setForm] = React.useState({ title: "", segment: "", detail: "", status: "Brouillon" });

  React.useEffect(() => {
    if (!open) return;
    setForm({ title: "", segment: "", detail: "", status: "Brouillon" });
  }, [open]);

  const submit = () => {
    if (!form.title.trim()) { showToast("Titre requis", "warn"); return; }
    actions.addPromo({
      title: form.title.trim(),
      segment: form.segment.trim() || "Toutes les clients",
      detail: form.detail.trim() || "À configurer",
      status: form.status,
    });
    onClose();
  };

  return (
    <Modal open={!!open} onClose={onClose} title="Nouvelle promotion"
      footer={
        <>
          <button className="btn btn-ghost" onClick={onClose}>Annuler</button>
          <button className="btn btn-primary" onClick={submit}>Créer la campagne</button>
        </>
      }>
      <div style={{ display: "grid", gap: 14 }}>
        <FormField label="Titre" value={form.title} onChange={v => setForm({ ...form, title: v })} placeholder="Anniversaires avril" required autoFocus/>
        <FormField label="Cible / segment" value={form.segment} onChange={v => setForm({ ...form, segment: v })} placeholder="Clientes fidèles, VIP…"/>
        <FormField label="Détail (ex : -15%, SMS samedi 10h…)" value={form.detail} onChange={v => setForm({ ...form, detail: v })}/>
        <FormSelect label="Statut" value={form.status} onChange={v => setForm({ ...form, status: v })}
          options={[["Brouillon", "Brouillon"], ["Programmé", "Programmé"], ["Actif", "Actif"]]}/>
      </div>
    </Modal>
  );
};

const FideliteRulesModal = ({ modal, onClose, data, actions }) => {
  const open = modal && modal.type === "fidelite-rules";
  const [visits, setVisits] = React.useState(data.fideliteRules.visits);
  const [rewardLabel, setRewardLabel] = React.useState(data.fideliteRules.rewardLabel);

  React.useEffect(() => {
    if (!open) return;
    setVisits(data.fideliteRules.visits);
    setRewardLabel(data.fideliteRules.rewardLabel);
  }, [open]);

  const submit = () => {
    const v = parseInt(visits, 10);
    if (!v || v < 2) { showToast("Nombre de visites invalide", "warn"); return; }
    actions.updateFideliteRules({ visits: v, rewardLabel });
    onClose();
  };

  return (
    <Modal open={!!open} onClose={onClose} title="Règles de fidélité"
      footer={
        <>
          <button className="btn btn-ghost" onClick={onClose}>Annuler</button>
          <button className="btn btn-primary" onClick={submit}>Enregistrer</button>
        </>
      }>
      <div style={{ display: "grid", gap: 14 }}>
        <FormField label="Nombre de visites pour obtenir la récompense" value={String(visits)} onChange={v => setVisits(v)}/>
        <FormField label="Récompense" value={rewardLabel} onChange={setRewardLabel} placeholder="1 prestation offerte"/>
      </div>
    </Modal>
  );
};

Object.assign(window, { AppShell });
