/* ClientBase V2, Espace client (portail cliente)
   Activé via ?espace=<token> dans l'URL.
   - token "demo" : charge un jeu de données démo
   - Sinon : récupère via Supabase RPC get_client_portal(token) (à câbler côté backend)
   - Le frontend supporte déjà toute l'UX : RDV à venir, historique, annulation,
     fidélité, profil + préférences.
*/

const _espaceFmtDate = (ymd) => {
  if (!ymd) return "";
  try {
    const d = new Date(ymd + "T00:00:00");
    const wd = ["dim","lun","mar","mer","jeu","ven","sam"][d.getDay()];
    const m = ["janv.","févr.","mars","avril","mai","juin","juil.","août","sept.","oct.","nov.","déc."][d.getMonth()];
    return `${wd}. ${d.getDate()} ${m}`;
  } catch { return ymd; }
};
const _espaceFmtHour = (h) => {
  if (h == null) return "";
  const hh = Math.floor(h);
  const mm = Math.round((h - hh) * 60);
  return `${hh}h${mm.toString().padStart(2, "0")}`;
};

const _DEMO_CLIENT = {
  id: "demo-cli",
  firstName: "Marie",
  lastName: "Dupont",
  email: "marie.dupont@gmail.com",
  phone: "06 12 34 56 78",
  createdAt: "2025-08-10",
  preferences: {
    smsReminders: true,
    emailReminders: true,
    marketing: false,
  },
  // Compte centralisé : une cliente, plusieurs pros
  pros: [
    {
      proId: "pro_lea",
      name: "Studio Léa",
      owner: "Léa Bernard",
      hue: 18,
      address: "12 rue de la Paix, 75002 Paris",
      type: "Ongles",
      fidelite: { visits: 7, target: 10, rewardLabel: "1 prestation offerte (jusqu'à 45 €)" },
      upcoming: [
        { id: "r_u1", date: "2026-05-22", h: 11,   d: 1.5,  service: "Pose complète gel",   price: 55, status: "confirmed" },
        { id: "r_u2", date: "2026-06-10", h: 14,   d: 1,    service: "Remplissage",         price: 35, status: "confirmed" },
      ],
      history: [
        { id: "r_h1",  date: "2026-04-28", h: 10.5, d: 1,    service: "Remplissage",         price: 35 },
        { id: "r_h2",  date: "2026-04-03", h: 11,   d: 1.5,  service: "Pose complète gel",   price: 55 },
        { id: "r_h3",  date: "2026-03-12", h: 14.5, d: 0.75, service: "Manucure russe",      price: 30 },
      ],
    },
    {
      proId: "pro_sarah",
      name: "Sarah Coiffure",
      owner: "Sarah Dupont",
      hue: 280,
      address: "8 avenue Foch, 75116 Paris",
      type: "Coiffure",
      fidelite: { visits: 3, target: 8, rewardLabel: "20 € de réduction" },
      upcoming: [
        { id: "r_u3", date: "2026-05-28", h: 15,   d: 2,    service: "Coupe + couleur",     price: 95, status: "confirmed" },
      ],
      history: [
        { id: "r_h4",  date: "2026-03-04", h: 14,   d: 1,    service: "Brushing",             price: 40 },
        { id: "r_h5",  date: "2026-01-22", h: 11,   d: 1.5,  service: "Coupe + brushing",    price: 60 },
      ],
    },
  ],
};

const EspacePage = ({ token }) => {
  // Sans token, ou ?espace=login / ?espace=signup : page d'auth client.
  // Le paramètre URL peut spécifier le mode d'arrivée (sélecteur Pro/Client
  // de la home envoie sur l'un ou l'autre).
  if (!token || token === "login" || token === "signup") {
    return <EspaceLogin initialMode={token === "signup" ? "signup" : "login"}/>;
  }

  const [client, setClient] = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [view, setView] = React.useState("dash"); // dash | detail | profile | history | contact
  const [activeRdv, setActiveRdv] = React.useState(null);
  const [cancelOpenId, setCancelOpenId] = React.useState(null);
  // IMPORTANT : tous les useState DOIVENT être déclarés en haut du
  // composant, AVANT tout early return (if loading return / if !client
  // return). Sinon : "Rendered more hooks than during the previous render"
  // qui crash toute l'app.
  const [modifyOpenId, setModifyOpenId] = React.useState(null);
  // Popup d'erreur policy (cancel/modify refusés à cause du délai pro) :
  // { action: "cancel"|"modify", proName, ownerName, phone, hoursRequired,
  //   hoursLeft } ou null.
  const [policyError, setPolicyError] = React.useState(null);

  React.useEffect(() => {
    let cancelled = false;
    (async () => {
      if (token === "demo") {
        // Laisse le splash de démo (CbDemoSplash) s'afficher confortablement.
        await new Promise(r => setTimeout(r, 1100));
        if (!cancelled) { setClient(_DEMO_CLIENT); setLoading(false); }
        return;
      }
      if (token === "me") {
        // Token "me" : user vraiment connecté côté client. Le backend
        // (RPC get_client_portal) n'est pas encore câblé, donc on
        // construit une fiche VIDE personnalisée avec uniquement les
        // infos personnelles du localStorage de session. Aucun RDV ni
        // pro tant que de vrais salons n'auront pas connecté la cliente
        // (via leur app pro). Les sections vides afficheront un onboarding.

        // Marque l'audience client pour le routing auto (au cas où
        // l'utilisateur arrive ici via lien email sans être passé par
        // EspaceLogin qui pose normalement ce flag).
        try { localStorage.setItem("cb_last_audience", "client"); } catch {}

        // Rattachement rétroactif : si l'utilisateur arrive ici depuis le
        // lien de confirmation email (premier login après signup), on
        // attache à son compte les RDV pris avant la confirmation. Appelé
        // en silencieux, idempotent — pas de toast si 0 rattaché.
        if (window.cbCloud && window.cbCloud.linkMyOrphanAppointments) {
          window.cbCloud.linkMyOrphanAppointments().then(n => {
            if (n > 0) {
              try { showToast(`${n} rendez-vous rattaché${n > 1 ? "s" : ""} à votre compte`); } catch {}
            }
          }).catch(() => {});
        }

        await new Promise(r => setTimeout(r, 250));

        // 1) Priorité : session Supabase active (cas du lien email confirm
        //    qui vient de poser un session via ?code=). Supabase gère
        //    automatiquement l'échange code → session, on attend juste un
        //    peu pour laisser le temps à detectSessionInUrl de s'exécuter.
        let supaUser = null;
        if (window.cbSupabase) {
          try {
            // Premier check rapide : getSession() est synchrone côté JS
            // (lit le localStorage Supabase) — pas besoin de retry pour ça.
            const { data: sess0 } = await window.cbSupabase.auth.getSession();
            window.cbDebug && window.cbDebug.log("[espace me] getSession initial:", sess0 && sess0.session ? "OK" : "null");
            // Si déjà une session : on récupère l'user direct (1 round-trip)
            if (sess0 && sess0.session) {
              const { data, error } = await window.cbSupabase.auth.getUser();
              window.cbDebug && window.cbDebug.log("[espace me] getUser:", data && data.user ? data.user.email : "null", "err:", error && error.message);
              if (data && data.user) supaUser = data.user;
            } else {
              // Pas de session immédiate → on attend un peu au cas où
              // l'échange ?code= soit encore en cours (detectSessionInUrl).
              for (let i = 0; i < 6; i++) {
                await new Promise(r => setTimeout(r, 200));
                const { data } = await window.cbSupabase.auth.getSession();
                if (data && data.session) {
                  const u = await window.cbSupabase.auth.getUser();
                  if (u && u.data && u.data.user) { supaUser = u.data.user; break; }
                }
              }
              window.cbDebug && window.cbDebug.log("[espace me] après retry:", supaUser ? supaUser.email : "toujours null");
            }
          } catch (e) { window.cbDebug && window.cbDebug.warn("[espace me] supabase exception:", e); }
        }

        // 2) Fallback : localStorage (legacy / signup démo)
        let session = null;
        try { session = JSON.parse(localStorage.getItem("cb_client_v1") || "null"); } catch {}
        window.cbDebug && window.cbDebug.log("[espace me] localStorage cb_client_v1:", session ? session.email : "null");

        // Aucune source de session → page "introuvable"
        if (!supaUser && !session) {
          window.cbDebug && window.cbDebug.warn("[espace me] aucune session → EspaceNotFound");
          if (!cancelled) { setClient(null); setLoading(false); }
          return;
        }

        // Construit la fiche client à partir de la session disponible
        const md = (supaUser && supaUser.user_metadata) || {};
        const firstName = (session && session.firstName) || md.first_name || (md.full_name || "").split(" ")[0] || "";
        const lastName  = (session && session.lastName)  || md.last_name  || (md.full_name || "").split(" ").slice(1).join(" ") || "";
        const email     = (supaUser && supaUser.email)   || (session && session.email) || "";
        const phone     = md.phone || (session && session.phone) || "";

        // Si on a un user Supabase mais pas le cache localStorage, on le
        // pose pour les prochains chargements (évite le délai retry).
        if (supaUser && !session) {
          try {
            localStorage.setItem("cb_client_v1", JSON.stringify({
              email, firstName, lastName, phone,
              loginAt: Date.now(),
            }));
          } catch {}
        }

        // Fetch les RDV du client depuis Supabase (RPC get_my_appointments)
        // qui retourne déjà la structure pros[] attendue par EspaceDash.
        // Fait en parallèle pour ne pas ralentir l'affichage : on pose
        // d'abord le client avec pros=[] puis on met à jour quand les
        // données arrivent.
        let proList = [];
        if (supaUser && window.cbCloud && window.cbCloud.getMyAppointments) {
          try {
            proList = await window.cbCloud.getMyAppointments();
            window.cbDebug && window.cbDebug.log("[espace me] getMyAppointments:", proList.length, "pros");
          } catch (e) { window.cbDebug && window.cbDebug.warn("[espace me] getMyAppointments error:", e); }
        }

        const personalized = {
          id: (supaUser && supaUser.id) || (session && session.id) || "me",
          firstName, lastName, email, phone,
          createdAt: (session && session.createdAt) || new Date().toISOString().slice(0, 10),
          preferences: { smsReminders: true, emailReminders: true, marketing: false },
          pros: proList,
        };
        if (!cancelled) { setClient(personalized); setLoading(false); }
        return;
      }
      // Vrai backend (phase 2) : await window.cbSupabase.rpc("get_client_portal", { p_token: token })
      // Pour l'instant, fallback démo.
      await new Promise(r => setTimeout(r, 350));
      if (!cancelled) { setClient(_DEMO_CLIENT); setLoading(false); }
    })();
    return () => { cancelled = true; };
  }, [token]);

  if (loading) {
    // Démo client : splash dédié (badge DÉMO, violet). Sinon loader sobre.
    if (token === "demo" && window.CbDemoSplash) {
      return React.createElement(window.CbDemoSplash, { variant: "client" });
    }
    return (
      <div style={{
        minHeight: "100vh", display: "flex", alignItems: "center", justifyContent: "center",
        background: "var(--bg)", color: "var(--ink-3)", fontSize: 13.5,
      }}>Chargement…</div>
    );
  }
  if (!client) {
    return <EspaceNotFound/>;
  }

  // Helper : retrouve le pro d'un RDV via client.pros + son ID.
  const findProForRdv = (rdvId) => {
    if (!client || !client.pros) return null;
    for (const p of client.pros) {
      if ((p.upcoming || []).some(r => r.id === rdvId)) return p;
      if ((p.history || []).some(r => r.id === rdvId)) return p;
    }
    return null;
  };
  // Helper : parse l'erreur policy de Supabase (format
  // "Délai d'annulation : Nh. Restant : Mh.") pour extraire les nombres.
  const parsePolicyDetail = (msg) => {
    const m = (msg || "").match(/:\s*(\d+(?:\.\d+)?)\s*h.*?:\s*(-?\d+(?:\.\d+)?)\s*h/);
    if (!m) return { hoursRequired: null, hoursLeft: null };
    return { hoursRequired: Number(m[1]), hoursLeft: Number(m[2]) };
  };

  const cancelRdv = async (id) => {
    // Cloud path : RPC client_cancel_appointment (vérifie délai + notifie le pro)
    if (token === "me" && window.cbSupabase) {
      try {
        const { data: out, error } = await window.cbSupabase.rpc("client_cancel_appointment", {
          p_appointment_id: id,
        });
        if (error) {
          const m = error.message || "";
          const details = error.details || "";
          setCancelOpenId(null);
          if (m.includes("POLICY_DEADLINE_PASSED")) {
            // Popup riche au lieu d'un toast : on explique le délai + on
            // propose un bouton "Contacter le pro" pour qu'il fasse le geste.
            const pro = findProForRdv(id);
            const { hoursRequired, hoursLeft } = parsePolicyDetail(details);
            setPolicyError({
              action: "cancel",
              proName: (pro && pro.name) || "votre pro",
              ownerName: pro && pro.owner,
              phone: pro && pro.phone,
              hoursRequired, hoursLeft,
            });
            return;
          }
          if (m.includes("APPT_NOT_YOURS")) {
            showToast("Ce RDV ne vous appartient pas", "warn"); return;
          }
          showToast("Erreur lors de l'annulation", "warn");
          return;
        }
      } catch (e) {
        showToast("Erreur réseau · réessayez", "warn");
        setCancelOpenId(null);
        return;
      }
    }
    // Optimistic update local (démo + cloud)
    setClient(c => ({
      ...c,
      pros: c.pros.map(p => ({ ...p, upcoming: p.upcoming.filter(r => r.id !== id) })),
    }));
    setCancelOpenId(null);
    setView("dash");
    setActiveRdv(null);
    showToast("Rendez-vous annulé · votre pro a été prévenue");
  };

  // Modification d'un RDV : ouvre un modal pour saisir nouveau jour/heure,
  // appelle client_modify_appointment côté Supabase qui vérifie :
  //   - délai pro (policy_modify_hours)
  //   - pas de double-réservation sur le nouveau créneau
  // (state modifyOpenId déclaré tout en haut avec les autres useState)
  const modifyRdv = async (id, newDate, newHour) => {
    if (token === "me" && window.cbSupabase) {
      try {
        const dayOff = window.cbDayOffsetFrom ? window.cbDayOffsetFrom(new Date(newDate)) : 0;
        const { error } = await window.cbSupabase.rpc("client_modify_appointment", {
          p_appointment_id: id,
          p_new_day: dayOff,
          p_new_h: Number(newHour),
        });
        if (error) {
          const m = error.message || "";
          const details = error.details || "";
          if (m.includes("POLICY_DEADLINE_PASSED")) {
            // Même popup riche que pour le cancel, action différente
            setModifyOpenId(null);
            const pro = findProForRdv(id);
            const { hoursRequired, hoursLeft } = parsePolicyDetail(details);
            setPolicyError({
              action: "modify",
              proName: (pro && pro.name) || "votre pro",
              ownerName: pro && pro.owner,
              phone: pro && pro.phone,
              hoursRequired, hoursLeft,
            });
            return;
          }
          if (m.includes("SLOT_TAKEN"))     { showToast("Créneau déjà pris · choisissez un autre", "warn"); return; }
          if (m.includes("APPT_NOT_YOURS")) { showToast("Ce RDV ne vous appartient pas", "warn"); return; }
          showToast("Erreur lors de la modification", "warn");
          return;
        }
      } catch (e) {
        showToast("Erreur réseau · réessayez", "warn");
        return;
      }
    }
    // Optimistic update : déplace le RDV dans le state local
    setClient(c => ({
      ...c,
      pros: c.pros.map(p => ({
        ...p,
        upcoming: p.upcoming.map(r => r.id === id
          ? { ...r, date: newDate, h: Number(newHour) }
          : r),
      })),
    }));
    setModifyOpenId(null);
    showToast("Rendez-vous déplacé · votre pro a été prévenue");
  };

  return (
    <div style={{
      minHeight: "100vh",
      background: "linear-gradient(180deg, var(--cli-soft) 0%, var(--bg) 240px)",
      fontFamily: "var(--ff-text)",
    }}>
      <style>{`
        .esp-card { background: var(--surface); border: 1px solid var(--line); border-radius: 14px; }
        .esp-btn-row { display: flex; gap: 8px; flex-wrap: wrap; }
        .esp-tab { padding: 8px 14px; border-radius: 999px; font-size: 13px; font-weight: 540; cursor: pointer; border: 1px solid var(--line); background: var(--surface); color: var(--ink-2); transition: all .15s; }
        .esp-tab.on { background: var(--ink); color: var(--bg); border-color: var(--ink); }
        @media (max-width: 600px) {
          .esp-stack-mobile { grid-template-columns: 1fr !important; }
        }
      `}</style>

      {/* Bandeau démo — même structure que le pro (DemoBanner) avec CTA
          conversion fort. Quitter = retour home (pas de session à clear côté
          client démo, c'est juste un token URL). */}
      {(token === "demo" || !token) && (
        <div style={{
          padding: "10px 22px",
          background: "linear-gradient(135deg, var(--cli-soft), color-mix(in oklab, var(--cli-accent) 8%, var(--surface)))",
          borderBottom: "1px solid var(--cli-soft-2)",
          display: "flex", alignItems: "center", justifyContent: "space-between", gap: 14,
          fontSize: 13,
        }}>
          <div style={{ display: "flex", alignItems: "center", gap: 10, minWidth: 0 }}>
            <span style={{
              display: "inline-block", width: 8, height: 8, borderRadius: "50%",
              background: "var(--cli-accent)",
              boxShadow: "0 0 0 4px color-mix(in oklab, var(--cli-accent) 20%, transparent)",
              flexShrink: 0,
            }}/>
            <strong style={{ color: "var(--cli-ink)", fontWeight: 580 }}>Mode démo</strong>
            <span className="cb-demo-msg" style={{ color: "var(--ink-2)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
              Vous explorez un espace client pré-rempli. Aucune donnée n'est sauvegardée.
            </span>
          </div>
          <div style={{ display: "flex", gap: 8, flexShrink: 0 }}>
            <button onClick={() => { window.location.href = "/inscription?role=client"; }}
              style={{
                padding: "6px 14px",
                background: `linear-gradient(135deg, var(--cli-accent) 0%, color-mix(in oklab, var(--cli-accent) 70%, var(--accent)) 100%)`,
                color: "#fff",
                border: "none",
                borderRadius: 999,
                fontSize: 12.5, fontWeight: 600, letterSpacing: "-0.005em",
                cursor: "pointer", whiteSpace: "nowrap",
                boxShadow: "0 4px 12px -4px var(--cli-accent)",
                fontFamily: "inherit",
              }}>
              ✨ Créer mon vrai compte →
            </button>
            <button onClick={() => { window.location.href = "/"; }}
              style={{
                padding: "5px 11px",
                background: "rgba(255,255,255,0.7)",
                border: "1px solid var(--cli-soft-2)",
                borderRadius: 999,
                fontSize: 12, fontWeight: 540, color: "var(--cli-ink)",
                cursor: "pointer", whiteSpace: "nowrap",
                fontFamily: "inherit",
              }}>
              Quitter
            </button>
          </div>
        </div>
      )}

      {/* Header */}
      <EspaceHeader client={client} view={view} setView={setView}
        onOpenSettings={() => setView("profile")}
        onLogout={async () => {
          // Logout COMPLET : sinon le routing root re-redirige vers
          // /espace au prochain reload (cbAuth.getCurrentUser() true
          // + cb_last_audience === "client" → "espacePath").
          try { if (window.cbSupabase) await window.cbSupabase.auth.signOut(); } catch {}
          try { localStorage.removeItem(CLIENT_SESSION_KEY); } catch {}
          try { localStorage.removeItem("cb_last_audience"); } catch {}
          // Hard reload vers home pour vider tout state React résiduel
          window.location.href = "/";
        }}/>

      {/* paddingBottom 100 pour laisser de la place à la BottomNav fixed */}
      <div style={{ maxWidth: 880, margin: "0 auto", padding: "16px 18px 100px" }}>
        {view === "dash" && (
          <EspaceDash client={client}
            onOpenRdv={(r, pro) => { setActiveRdv({ ...r, _pro: pro }); setView("detail"); }}
            onSeeHistory={() => setView("history")}
            onSeeProfile={() => setView("profile")}/>
        )}
        {view === "detail" && activeRdv && (
          <EspaceRdvDetail rdv={activeRdv} pro={activeRdv._pro}
            onBack={() => { setView("dash"); setActiveRdv(null); }}
            onCancelRequest={(id) => setCancelOpenId(id)}
            onModifyRequest={(id) => setModifyOpenId(id)}/>
        )}
        {view === "history" && (
          <EspaceHistory client={client} onBack={() => setView("dash")}/>
        )}
        {view === "contact" && (
          <EspaceContact client={client} onBack={() => setView("dash")}/>
        )}
        {view === "profile" && (
          <EspaceProfile client={client} setClient={setClient}
            onBack={() => setView("dash")}
            onLogout={async () => {
              try { if (window.cbSupabase) await window.cbSupabase.auth.signOut(); } catch {}
              try { localStorage.removeItem(CLIENT_SESSION_KEY); } catch {}
              try { localStorage.removeItem("cb_last_audience"); } catch {}
              window.location.href = "/";
            }}/>
        )}
      </div>

      {/* Bottom nav app-style : visible sur toutes les vues SAUF le détail
          RDV (où on a un back button propre). Pin en bas, glassmorphism,
          4 onglets adaptatifs. */}
      {view !== "detail" && (
        <EspaceBottomNav view={view} setView={setView}/>
      )}

      {/* Annulation modal */}
      {cancelOpenId && (
        <EspaceCancelModal
          rdv={client.pros.flatMap(p => p.upcoming.map(r => ({ ...r, _pro: p }))).find(r => r.id === cancelOpenId)}
          onClose={() => setCancelOpenId(null)}
          onConfirm={() => cancelRdv(cancelOpenId)}/>
      )}

      {/* Déplacement (modification) modal */}
      {modifyOpenId && (
        <EspaceModifyModal
          rdv={client.pros.flatMap(p => p.upcoming.map(r => ({ ...r, _pro: p }))).find(r => r.id === modifyOpenId)}
          onClose={() => setModifyOpenId(null)}
          onConfirm={modifyRdv}/>
      )}

      {/* Popup d'erreur policy (cancel/modify refusés par le délai pro) */}
      {policyError && (
        <EspacePolicyErrorModal
          {...policyError}
          onClose={() => setPolicyError(null)}/>
      )}

      <EspaceFooter/>
    </div>
  );
};

/* ====== Bottom nav app-style ====== */
// Tab bar fixée en bas, 4 onglets toujours accessibles depuis n'importe
// quelle vue. Patterns iOS / Android : indicateur d'onglet actif en
// pastille colorée, label + icône, transition fluide.
const EspaceBottomNav = ({ view, setView }) => {
  const tabs = [
    { id: "dash",    label: "Accueil",   icon: "sparkle" },
    { id: "history", label: "Mes RDV",   icon: "calendar" },
    { id: "contact", label: "Contact",   icon: "mail" },
    { id: "profile", label: "Profil",    icon: "user" },
  ];
  return (
    <nav style={{
      position: "fixed", left: 0, right: 0, bottom: 0,
      zIndex: 40,
      padding: "8px 12px calc(8px + env(safe-area-inset-bottom))",
      background: "color-mix(in oklab, var(--surface) 92%, transparent)",
      borderTop: "1px solid var(--line)",
      backdropFilter: "blur(16px) saturate(180%)",
      WebkitBackdropFilter: "blur(16px) saturate(180%)",
    }}>
      <div style={{
        maxWidth: 560, margin: "0 auto",
        display: "grid", gridTemplateColumns: `repeat(${tabs.length}, 1fr)`,
        gap: 4,
      }}>
        {tabs.map(t => {
          const active = view === t.id;
          return (
            <button key={t.id} onClick={() => setView(t.id)}
              style={{
                display: "flex", flexDirection: "column", alignItems: "center", gap: 3,
                padding: "8px 6px",
                background: "transparent", border: "none",
                cursor: "pointer", fontFamily: "inherit",
                color: active ? "var(--cli-accent)" : "var(--ink-3)",
                transition: "color .2s ease, transform .12s ease",
                position: "relative",
              }}
              onMouseDown={e => { e.currentTarget.style.transform = "scale(0.94)"; }}
              onMouseUp={e => { e.currentTarget.style.transform = "scale(1)"; }}
              onMouseLeave={e => { e.currentTarget.style.transform = "scale(1)"; }}>
              {/* Indicateur d'onglet actif : pastille colorée au-dessus */}
              {active && (
                <span aria-hidden style={{
                  position: "absolute", top: -8,
                  width: 24, height: 3, borderRadius: 99,
                  background: "var(--cli-accent)",
                }}/>
              )}
              <span style={{
                width: 30, height: 30, borderRadius: 9,
                background: active ? "var(--cli-soft)" : "transparent",
                display: "inline-flex", alignItems: "center", justifyContent: "center",
                transition: "background .2s ease",
              }}>
                <Icon name={t.icon} size={16} stroke={active ? 2.2 : 1.9}/>
              </span>
              <span style={{
                fontSize: 10.5, fontWeight: active ? 600 : 500,
                letterSpacing: "-0.005em",
              }}>{t.label}</span>
            </button>
          );
        })}
      </div>
    </nav>
  );
};

/* ====== Vue Contact ====== */
// Page dédiée pour contacter l'équipe ClientBase + actions rapides
// (WhatsApp, email direct, FAQ). Form simple — l'envoi va dans mailto
// pour l'instant (pas de backend mail wired).
const EspaceContact = ({ client, onBack }) => {
  const [subject, setSubject] = React.useState("");
  const [message, setMessage] = React.useState("");
  const submit = (e) => {
    e.preventDefault();
    if (!subject.trim() || !message.trim()) {
      showToast("Sujet et message requis", "warn"); return;
    }
    // Build mailto avec sujet + message pré-remplis + identité client
    const body = `Bonjour,\n\n${message}\n\n—\nDe : ${client.firstName} ${client.lastName}\nEmail : ${client.email}\nClient ID : ${client.id}`;
    const url = `mailto:clientbase.fr@gmail.com?subject=${encodeURIComponent("[Espace client] " + subject)}&body=${encodeURIComponent(body)}`;
    window.location.href = url;
    setTimeout(() => {
      showToast("Votre client mail s'ouvre…");
      setSubject(""); setMessage("");
    }, 200);
  };
  return (
    <div style={{ display: "flex", flexDirection: "column" }}>
      <button onClick={onBack} style={{
        alignSelf: "flex-start", padding: "6px 12px", marginBottom: 6,
        background: "transparent", border: "none", color: "var(--ink-3)",
        fontSize: 13, cursor: "pointer", fontFamily: "inherit",
      }}>← Retour</button>

      <h2 style={{
        margin: "0 0 5px",
        fontFamily: "var(--ff-display)", fontSize: 22, fontWeight: 680,
        letterSpacing: "-0.025em", color: "var(--ink)", lineHeight: 1.15,
      }}>
        Une question ?
      </h2>
      <p style={{ fontSize: 14.5, color: "var(--ink-4)", margin: "0 0 22px" }}>
        L'équipe ClientBase vous répond en moins de 24h en semaine.
      </p>

      {/* Quick contact : 2 cards verticales centrées (match preview .contact-quick) */}
      <div style={{
        display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10, marginBottom: 18,
      }}>
        <a href="https://wa.me/33000000000" target="_blank" rel="noopener"
          style={{
            display: "flex", flexDirection: "column", alignItems: "center", gap: 8,
            padding: "18px 14px",
            background: "var(--surface)", border: "1px solid var(--line)",
            borderRadius: 14, textAlign: "center", color: "inherit", textDecoration: "none",
            transition: "border-color .15s, transform .15s, box-shadow .2s",
          }}
          onMouseEnter={e => {
            e.currentTarget.style.borderColor = "var(--cli-accent)";
            e.currentTarget.style.transform = "translateY(-2px)";
            e.currentTarget.style.boxShadow = "0 4px 14px -8px rgba(15,18,30,0.10)";
          }}
          onMouseLeave={e => {
            e.currentTarget.style.borderColor = "var(--line)";
            e.currentTarget.style.transform = "translateY(0)";
            e.currentTarget.style.boxShadow = "none";
          }}>
          <span style={{
            width: 44, height: 44, borderRadius: 11,
            background: "#25D366", color: "#fff",
            display: "inline-flex", alignItems: "center", justifyContent: "center",
            fontSize: 20,
          }}>💬</span>
          <strong style={{ fontSize: 14, fontWeight: 620, color: "var(--ink)", letterSpacing: "-0.005em" }}>WhatsApp</strong>
          <small style={{ fontSize: 11.5, color: "var(--ink-4)" }}>Réponse rapide</small>
        </a>
        <a href="mailto:clientbase.fr@gmail.com"
          style={{
            display: "flex", flexDirection: "column", alignItems: "center", gap: 8,
            padding: "18px 14px",
            background: "var(--surface)", border: "1px solid var(--line)",
            borderRadius: 14, textAlign: "center", color: "inherit", textDecoration: "none",
            transition: "border-color .15s, transform .15s, box-shadow .2s",
          }}
          onMouseEnter={e => {
            e.currentTarget.style.borderColor = "var(--cli-accent)";
            e.currentTarget.style.transform = "translateY(-2px)";
            e.currentTarget.style.boxShadow = "0 4px 14px -8px rgba(15,18,30,0.10)";
          }}
          onMouseLeave={e => {
            e.currentTarget.style.borderColor = "var(--line)";
            e.currentTarget.style.transform = "translateY(0)";
            e.currentTarget.style.boxShadow = "none";
          }}>
          <span style={{
            width: 44, height: 44, borderRadius: 11,
            background: "var(--cli-soft)", color: "var(--cli-ink)",
            display: "inline-flex", alignItems: "center", justifyContent: "center",
            fontSize: 20,
          }}>📨</span>
          <strong style={{ fontSize: 14, fontWeight: 620, color: "var(--ink)", letterSpacing: "-0.005em" }}>Email direct</strong>
          <small style={{ fontSize: 11.5, color: "var(--ink-4)" }}>clientbase.fr@gmail.com</small>
        </a>
      </div>

      {/* Form contact détaillé — card sober */}
      <div style={{
        padding: 20, background: "var(--surface)",
        border: "1px solid var(--line)", borderRadius: 14,
        marginBottom: 4,
      }}>
        <h3 style={{
          margin: "0 0 14px",
          fontSize: 15, fontWeight: 680, letterSpacing: "-0.012em",
          color: "var(--ink)",
          display: "flex", alignItems: "center", gap: 8,
        }}>
          <span style={{
            width: 28, height: 28, borderRadius: 8,
            background: "var(--cli-soft)", color: "var(--cli-ink)",
            display: "inline-flex", alignItems: "center", justifyContent: "center", fontSize: 14,
          }}>✉️</span>
          Envoyer un message
        </h3>
        <form onSubmit={submit} style={{ display: "flex", flexDirection: "column", gap: 14 }}>
          <div>
            <label style={{
              display: "block", marginBottom: 7,
              fontSize: 13, color: "var(--ink-3)", fontWeight: 560,
            }}>Sujet</label>
            <input value={subject} onChange={e => setSubject(e.target.value)}
              placeholder="Comment pouvons-nous vous aider ?"
              style={{
                width: "100%", padding: "11px 14px",
                background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 10,
                fontSize: 14.5, color: "var(--ink)", fontFamily: "inherit",
                boxSizing: "border-box",
              }}/>
          </div>
          <div>
            <label style={{
              display: "block", marginBottom: 7,
              fontSize: 13, color: "var(--ink-3)", fontWeight: 560,
            }}>Votre message</label>
            <textarea value={message} onChange={e => setMessage(e.target.value)}
              rows={5}
              placeholder="Décrivez votre question ou problème…"
              style={{
                width: "100%", padding: "11px 14px",
                background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 10,
                fontSize: 14.5, color: "var(--ink)", fontFamily: "inherit", resize: "vertical",
                boxSizing: "border-box", lineHeight: 1.5,
              }}/>
            <p style={{ fontSize: 12, color: "var(--ink-4)", margin: "5px 0 0" }}>
              Vos coordonnées (email + nom) seront automatiquement incluses.
            </p>
          </div>
          <button type="submit" style={{
            width: "100%", padding: "11px 18px",
            background: "var(--cli-accent)", color: "#fff",
            border: "none", borderRadius: 10,
            fontSize: 14, fontWeight: 600,
            cursor: "pointer", fontFamily: "inherit",
            display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 8,
            letterSpacing: "-0.005em",
          }}>
            Envoyer le message →
          </button>
        </form>
      </div>

      {/* Section title + FAQ accordéon (match preview) */}
      <div style={{
        display: "flex", alignItems: "center", gap: 10,
        margin: "26px 0 12px",
        fontSize: 11.5, textTransform: "uppercase", letterSpacing: "0.08em",
        color: "var(--ink-4)", fontWeight: 680,
      }}>
        <span aria-hidden style={{ fontSize: 14 }}>❓</span>
        <span>Questions fréquentes</span>
        <span aria-hidden style={{ flex: 1, height: 1, background: "var(--line)" }}/>
      </div>
      <div style={{
        padding: "8px 20px",
        background: "var(--surface)", border: "1px solid var(--line)",
        borderRadius: 14,
      }}>
        {[
          { q: "Comment annuler ou modifier un RDV ?", a: "Ouvrez le RDV depuis l'accueil ou « Mes RDV », puis utilisez les boutons « Modifier » ou « Annuler ». Attention : votre pro peut imposer un délai minimum (souvent 24h avant le RDV)." },
          { q: "Je ne reconnais pas un RDV dans ma liste", a: "Si un RDV apparaît chez vous sans que vous l'ayez pris, contactez d'abord le pro concerné. Si c'est suspect, écrivez-nous : on bloque immédiatement la fiche." },
          { q: "Comment ajouter un nouveau pro à mon compte ?", a: "Vos pros apparaissent automatiquement dès que vous prenez votre 1er RDV chez eux via leur lien de réservation ClientBase." },
          { q: "Mes données personnelles sont-elles sécurisées ?", a: "Vos données ne servent QU'à vos pros pour gérer vos RDV. Aucune pub, aucune revente. Hébergement en France, RGPD strict, export sur demande." },
          { q: "Comment supprimer mon compte ClientBase ?", a: "Allez dans Profil → bas de page → « Supprimer mon compte ». Suppression définitive et immédiate de toutes vos données." },
        ].map((item, i) => <FaqItem key={i} q={item.q} a={item.a}/>)}
      </div>
    </div>
  );
};

/* FAQ item style preview : chevron › qui pivote 90° + border-bottom inter-items */
const FaqItem = ({ q, a }) => {
  const [open, setOpen] = React.useState(false);
  return (
    <div style={{
      padding: "14px 0",
      borderBottom: "1px solid var(--line-2)",
      cursor: "pointer",
    }} onClick={() => setOpen(o => !o)}>
      <div style={{
        display: "flex", alignItems: "center", justifyContent: "space-between",
        gap: 14,
        fontSize: 14, fontWeight: 600, color: "var(--ink)",
      }}>
        <span>{q}</span>
        <span aria-hidden style={{
          fontSize: 18, color: open ? "var(--cli-accent)" : "var(--ink-4)",
          flexShrink: 0,
          transform: open ? "rotate(90deg)" : "rotate(0)",
          transition: "transform .2s ease, color .2s ease",
          lineHeight: 1,
        }}>›</span>
      </div>
      {open && (
        <div style={{
          marginTop: 10, fontSize: 13.5, color: "var(--ink-3)", lineHeight: 1.6,
        }}>{a}</div>
      )}
    </div>
  );
};

/* ====== Header ====== */
const EspaceHeader = ({ client, view, setView, onOpenSettings, onLogout }) => {
  const initials = `${(client.firstName || "M")[0]}${(client.lastName || "D")[0]}`.toUpperCase();
  const [menuOpen, setMenuOpen] = React.useState(false);
  const menuRef = React.useRef(null);
  React.useEffect(() => {
    if (!menuOpen) return;
    const onDown = (e) => {
      if (menuRef.current && !menuRef.current.contains(e.target)) setMenuOpen(false);
    };
    document.addEventListener("mousedown", onDown);
    return () => document.removeEventListener("mousedown", onDown);
  }, [menuOpen]);

  return (
    <header style={{
      padding: "24px 18px 14px", maxWidth: 880, margin: "0 auto",
      display: "flex", alignItems: "center", gap: 14, flexWrap: "wrap",
    }}>
      <div style={{
        width: 52, height: 52, borderRadius: 50,
        background: `linear-gradient(135deg, var(--cli-accent) 0%, color-mix(in oklab, var(--cli-accent) 70%, var(--accent)) 100%)`,
        color: "#fff", display: "flex", alignItems: "center", justifyContent: "center",
        fontWeight: 600, fontSize: 18, fontFamily: 'var(--ff-display)',
        boxShadow: `0 6px 16px -8px var(--cli-accent)`,
        flexShrink: 0,
      }}>
        {initials}
      </div>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontSize: 12, color: "var(--ink-4)", textTransform: "uppercase", letterSpacing: "0.06em", fontWeight: 540 }}>
          Espace cliente · {client.pros.length} pro{client.pros.length > 1 ? "s" : ""}
        </div>
        <h1 style={{ margin: 0, fontFamily: "var(--ff-display)", fontSize: 22, fontWeight: 580, letterSpacing: "-0.022em", marginTop: 2 }}>
          Bonjour {client.firstName}
        </h1>
      </div>

      {/* Menu compte version premium : header user (avatar + nom + email),
          sections organisées (Mon compte / Aide), zone danger en bas.
          Backdrop blur + scale-in subtil pour la sensation native. */}
      <div ref={menuRef} style={{ position: "relative" }}>
        <button onClick={() => setMenuOpen(v => !v)} title="Mon compte"
          aria-label="Menu compte"
          style={{
            width: 40, height: 40, borderRadius: "50%",
            background: menuOpen ? "var(--cli-soft)" : "var(--surface)",
            border: `1px solid ${menuOpen ? "var(--cli-accent)" : "var(--line)"}`,
            display: "flex", alignItems: "center", justifyContent: "center",
            cursor: "pointer", color: menuOpen ? "var(--cli-accent)" : "var(--ink-2)",
            transition: "all .18s cubic-bezier(.22,1,.36,1)",
            transform: menuOpen ? "rotate(60deg)" : "rotate(0)",
            boxShadow: menuOpen ? "0 6px 16px -8px var(--cli-accent)" : "none",
          }}>
          <Icon name="settings" size={17}/>
        </button>
        <div style={{
          position: "absolute", top: "calc(100% + 10px)", right: 0,
          width: 260, padding: 8,
          background: "color-mix(in oklab, var(--surface) 96%, transparent)",
          backdropFilter: "blur(20px) saturate(180%)",
          WebkitBackdropFilter: "blur(20px) saturate(180%)",
          border: "1px solid var(--line)",
          borderRadius: 14,
          boxShadow: "0 28px 60px -18px rgba(15,18,30,0.28), 0 8px 18px -8px rgba(15,18,30,0.10)",
          opacity: menuOpen ? 1 : 0,
          transform: menuOpen ? "translateY(0) scale(1)" : "translateY(-8px) scale(0.96)",
          transformOrigin: "top right",
          pointerEvents: menuOpen ? "auto" : "none",
          transition: "opacity .22s ease, transform .25s cubic-bezier(.22,1,.36,1)",
          zIndex: 50,
        }}>
          {/* Header user : avatar + nom + email */}
          <div style={{
            padding: "12px 12px 14px",
            display: "flex", alignItems: "center", gap: 11,
            borderBottom: "1px solid var(--line)",
            marginBottom: 6,
          }}>
            <div style={{
              width: 42, height: 42, borderRadius: 50, flexShrink: 0,
              background: `linear-gradient(135deg, var(--cli-accent) 0%, color-mix(in oklab, var(--cli-accent) 70%, var(--accent)) 100%)`,
              color: "#fff", display: "flex", alignItems: "center", justifyContent: "center",
              fontWeight: 600, fontSize: 14, fontFamily: 'var(--ff-display)',
              boxShadow: `0 4px 12px -6px var(--cli-accent)`,
            }}>{initials}</div>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{
                fontSize: 13.5, fontWeight: 600, color: "var(--ink)",
                letterSpacing: "-0.01em", lineHeight: 1.2,
                overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
              }}>
                {client.firstName} {client.lastName}
              </div>
              <div style={{
                fontSize: 11, color: "var(--ink-4)", marginTop: 2,
                overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
              }}>
                {client.email || "Compte client ClientBase"}
              </div>
            </div>
          </div>

          {/* Section : Mon compte */}
          <MenuSectionLabel>Mon compte</MenuSectionLabel>
          <MenuItem icon="user" label="Paramètres" hint="Profil, préférences, données"
            onClick={() => { setView("profile"); setMenuOpen(false); }}/>
          <MenuItem icon="calendar" label="Mes rendez-vous" hint="Historique + à venir"
            onClick={() => { setView("history"); setMenuOpen(false); }}/>

          {/* Section : Aide */}
          <MenuSectionLabel>Aide</MenuSectionLabel>
          <MenuItem icon="mail" label="Contact" hint="Email + WhatsApp + FAQ"
            onClick={() => { setView("contact"); setMenuOpen(false); }}/>
          <MenuItem icon="external" label="Centre d'aide"
            href="https://clientbase.fr/faq" target="_blank"/>

          {/* Footer : déconnexion en zone "danger" */}
          <div style={{
            marginTop: 6, paddingTop: 6,
            borderTop: "1px solid var(--line)",
          }}>
            <button onClick={onLogout} style={{
              display: "flex", alignItems: "center", gap: 11,
              width: "100%", padding: "10px 11px",
              background: "transparent", border: "none", borderRadius: 9,
              fontSize: 13, fontWeight: 540,
              color: "oklch(48% 0.16 25)",
              cursor: "pointer", fontFamily: "inherit", textAlign: "left",
              transition: "background .15s ease",
            }}
              onMouseEnter={e => { e.currentTarget.style.background = "oklch(96% 0.04 25)"; }}
              onMouseLeave={e => { e.currentTarget.style.background = "transparent"; }}>
              <span style={{
                width: 28, height: 28, borderRadius: 8, flexShrink: 0,
                background: "oklch(95% 0.05 25)",
                display: "inline-flex", alignItems: "center", justifyContent: "center",
              }}>
                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
                  strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                  <path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4M16 17l5-5-5-5M21 12H9"/>
                </svg>
              </span>
              <span style={{ flex: 1 }}>Se déconnecter</span>
            </button>
          </div>
        </div>
      </div>
    </header>
  );
};

// Label de section dans le menu (genre "Mon compte", "Aide")
const MenuSectionLabel = ({ children }) => (
  <div style={{
    padding: "10px 12px 4px",
    fontSize: 10, fontWeight: 700, color: "var(--ink-4)",
    textTransform: "uppercase", letterSpacing: "0.08em",
  }}>{children}</div>
);

// Item de menu uniforme : icône colorée + label + hint optionnel
const MenuItem = ({ icon, label, hint, onClick, href, target }) => {
  const content = (
    <>
      <span style={{
        width: 30, height: 30, borderRadius: 9, flexShrink: 0,
        background: "var(--bg-alt)", color: "var(--cli-accent)",
        display: "inline-flex", alignItems: "center", justifyContent: "center",
        transition: "background .15s ease",
      }} className="cb-menu-icon">
        <Icon name={icon} size={14} stroke={1.9}/>
      </span>
      <span style={{ flex: 1, minWidth: 0 }}>
        <span style={{ display: "block", fontSize: 13, fontWeight: 540, color: "var(--ink)", lineHeight: 1.25 }}>
          {label}
        </span>
        {hint && (
          <span style={{ display: "block", fontSize: 10.5, color: "var(--ink-4)", marginTop: 1 }}>
            {hint}
          </span>
        )}
      </span>
      <span aria-hidden style={{ color: "var(--ink-4)", fontSize: 13, lineHeight: 1 }}>›</span>
    </>
  );
  const baseStyle = {
    display: "flex", alignItems: "center", gap: 11,
    width: "100%", padding: "9px 11px",
    background: "transparent", border: "none", borderRadius: 9,
    cursor: "pointer", fontFamily: "inherit", textAlign: "left",
    color: "var(--ink)", textDecoration: "none",
    transition: "background .15s ease",
  };
  const handlers = {
    onMouseEnter: e => {
      e.currentTarget.style.background = "var(--bg-alt)";
      const ic = e.currentTarget.querySelector(".cb-menu-icon");
      if (ic) ic.style.background = "var(--cli-soft)";
    },
    onMouseLeave: e => {
      e.currentTarget.style.background = "transparent";
      const ic = e.currentTarget.querySelector(".cb-menu-icon");
      if (ic) ic.style.background = "var(--bg-alt)";
    },
  };
  if (href) {
    return <a href={href} target={target} rel={target === "_blank" ? "noopener noreferrer" : undefined}
      style={baseStyle} {...handlers}>{content}</a>;
  }
  return <button onClick={onClick} style={baseStyle} {...handlers}>{content}</button>;
};

// Ancienne style export gardé pour compat (utilisé par d'autres menus ailleurs)
const menuItemStyle = {
  display: "flex", alignItems: "center", gap: 9,
  width: "100%", padding: "8px 11px",
  background: "transparent", border: "none", borderRadius: 8,
  fontSize: 13, color: "var(--ink-2)", fontFamily: "inherit",
  cursor: "pointer", textAlign: "left",
};

/* ====== Dashboard (vue principale) ====== */
const EspaceDash = ({ client, onOpenRdv, onSeeHistory, onSeeProfile }) => {
  // Aggrège tous les upcoming de tous les pros, triés par date, avec la ref au pro
  const allUpcoming = React.useMemo(() => (
    client.pros.flatMap(p => p.upcoming.map(r => ({ ...r, _pro: p })))
      .sort((a, b) => a.date.localeCompare(b.date))
  ), [client.pros]);
  const next = allUpcoming[0];

  // Switcher « Vos pros préférés » : null = tous les pros, sinon un proId.
  // Filtre la liste des prochains RDV + les cartes de fidélité affichées.
  const [selectedProId, setSelectedProId] = React.useState(null);
  const filteredUpcoming = selectedProId
    ? allUpcoming.filter(r => r._pro.proId === selectedProId)
    : allUpcoming.slice(1); // en mode « Tous », le 1er RDV est déjà dans le hero
  const filteredPros = selectedProId
    ? client.pros.filter(p => p.proId === selectedProId)
    : client.pros;

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
      {/* Hero : prochain RDV — greeting adaptatif selon l'heure, j-X
          relatif pour le prochain RDV, compteur global en pill. */}
      {(() => {
        const hour = new Date().getHours();
        const greeting = hour < 6 ? "Bonne nuit" : hour < 12 ? "Bonjour" : hour < 18 ? "Bel après-midi" : "Bonsoir";
        const emoji = hour < 6 ? "🌙" : hour < 12 ? "☀️" : hour < 18 ? "👋" : "🌆";
        // Calcule j-X pour le prochain RDV (humanise "demain", "dans 3 jours")
        const daysLabel = (() => {
          if (!next) return null;
          try {
            const d = new Date(next.date + "T00:00:00");
            const today = new Date(); today.setHours(0,0,0,0);
            const diff = Math.round((d - today) / 86400000);
            if (diff === 0) return "Aujourd'hui";
            if (diff === 1) return "Demain";
            if (diff < 0) return null;
            if (diff < 7) return `Dans ${diff} jours`;
            return `Dans ${Math.round(diff/7)} semaine${diff >= 14 ? "s" : ""}`;
          } catch { return null; }
        })();
        return (
        <div className="esp-card" style={{
          padding: 24,
          background: "linear-gradient(135deg, var(--cli-soft) 0%, var(--surface) 80%)",
          border: "1px solid var(--cli-soft-2)",
          borderRadius: 16,
          position: "relative",
        }}>
          <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 10, marginBottom: 10 }}>
            <div style={{
              display: "inline-flex", alignItems: "center", gap: 6,
              fontSize: 12, color: "var(--cli-ink)",
              textTransform: "uppercase", letterSpacing: "0.06em", fontWeight: 660,
            }}>
              <span aria-hidden style={{
                display: "inline-block", width: 6, height: 6, borderRadius: "50%",
                background: "var(--cli-accent)",
                boxShadow: "0 0 0 3px color-mix(in oklab, var(--cli-accent) 20%, transparent)",
              }}/>
              {greeting} {client.firstName || "à vous"} {emoji}
            </div>
            {allUpcoming.length > 0 && (
              <span style={{
                padding: "3px 10px", borderRadius: 999, fontSize: 11, fontWeight: 660,
                background: "var(--surface)", color: "var(--cli-ink)",
                border: "1px solid var(--cli-soft-2)",
                textTransform: "uppercase", letterSpacing: "0.04em",
              }}>
                {allUpcoming.length} RDV à venir
              </span>
            )}
          </div>
          {next ? (
            <>
              <h2 style={{
                margin: "0 0 16px",
                fontFamily: "var(--ff-display)", fontSize: 22, fontWeight: 680,
                letterSpacing: "-0.025em", lineHeight: 1.2, color: "var(--ink)",
              }}>
                {daysLabel ? <>{daysLabel} <span style={{ color: "var(--ink-4)", fontWeight: 540 }}>· {_espaceFmtDate(next.date)}</span></> : <>Prochain RDV <span style={{ color: "var(--ink-4)", fontWeight: 540 }}>{_espaceFmtDate(next.date)}</span></>}
              </h2>
              {/* Carte RDV interne : grid date+infos+CTA, fond blanc, accents
                  violet sur la pastille date uniquement. */}
              <div style={{
                display: "grid", gridTemplateColumns: "64px 1fr auto",
                gap: 14, alignItems: "center",
                padding: 14,
                background: "var(--surface)",
                border: "1px solid var(--line)",
                borderRadius: 12,
              }}>
                <div style={{
                  textAlign: "center", padding: "8px 4px",
                  background: "var(--cli-soft)",
                  borderRadius: 10,
                  color: "var(--cli-ink)",
                }}>
                  <strong style={{
                    display: "block", fontSize: 20, fontWeight: 680, letterSpacing: "-0.02em", lineHeight: 1,
                    fontFamily: "var(--ff-display)",
                  }}>
                    {new Date(next.date + "T00:00:00").getDate()}
                  </strong>
                  <span style={{
                    display: "block", fontSize: 11, textTransform: "uppercase",
                    letterSpacing: "0.05em", fontWeight: 660, marginTop: 3,
                  }}>
                    {_espaceFmtDate(next.date).split(" ")[2]?.slice(0, 3) || ""}
                  </span>
                </div>
                <div style={{ minWidth: 0 }}>
                  <div style={{
                    fontSize: 14.5, fontWeight: 620, color: "var(--ink)",
                    overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
                    letterSpacing: "-0.005em",
                  }}>{next.service}</div>
                  <div style={{
                    fontSize: 13, color: "var(--ink-4)", marginTop: 3,
                    overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
                  }}>
                    {_espaceFmtHour(next.h)} · chez {next._pro.name}
                  </div>
                </div>
                <button onClick={() => onOpenRdv(next, next._pro)} style={{
                  padding: "9px 14px",
                  background: "var(--ink)", color: "#fff",
                  border: "none", borderRadius: 999,
                  fontSize: 12.5, fontWeight: 600, cursor: "pointer",
                  flexShrink: 0, fontFamily: "inherit",
                  letterSpacing: "-0.005em",
                  transition: "background .15s ease",
                }}
                  onMouseEnter={e => { e.currentTarget.style.background = "var(--cli-accent)"; }}
                  onMouseLeave={e => { e.currentTarget.style.background = "var(--ink)"; }}>
                  Détail →
                </button>
              </div>
            </>
          ) : (
            <div style={{ marginTop: 4 }}>
              <h2 style={{
                margin: "0 0 6px", fontFamily: "var(--ff-display)",
                fontSize: 20, fontWeight: 680, letterSpacing: "-0.022em", color: "var(--ink)",
              }}>
                Aucun rendez-vous à venir 🌿
              </h2>
              <p style={{ margin: 0, fontSize: 14, color: "var(--ink-3)", lineHeight: 1.55 }}>
                Réservez votre prochain RDV depuis le lien de votre pro. Tous vos futurs RDV apparaîtront ici.
              </p>
            </div>
          )}
        </div>
        );
      })()}

      {/* === Switcher « Vos pros préférés » (style preview) === */}
      {client.pros.length > 0 && (
        <EspaceProTabs
          pros={client.pros}
          selectedProId={selectedProId}
          onSelect={setSelectedProId}/>
      )}

      {/* === Section title : Vos prochains RDV (style preview) === */}
      {filteredUpcoming.length > 0 && (
        <>
          <EspaceSectionLine icon="📅" label="Vos prochains RDV"/>
          <div style={{ display: "flex", flexDirection: "column", gap: 10, marginTop: -2 }}>
            {filteredUpcoming.map(r => (
              <EspaceRdvRow key={r.id} rdv={r} pro={r._pro} onOpen={() => onOpenRdv(r, r._pro)}/>
            ))}
          </div>
        </>
      )}

      {/* === Section title : Cartes de fidélité === */}
      {filteredPros.length > 0 && (
        <>
          <EspaceSectionLine icon="🎁" label="Cartes de fidélité"/>
          <div style={{ display: "flex", flexDirection: "column", gap: 12, marginTop: -2 }}>
            {filteredPros.map(pro => (
              <ProFidelityCard key={pro.proId} pro={pro}/>
            ))}
          </div>
        </>
      )}

      {/* Empty state pros */}
      {client.pros.length === 0 && (
        <div style={{
          padding: "28px 22px", textAlign: "center",
          background: "var(--surface)", border: "1px dashed var(--line-strong)",
          borderRadius: 14, marginTop: 8,
        }}>
          <div style={{ fontSize: 32, marginBottom: 8 }}>👋</div>
          <div style={{ fontSize: 15, fontWeight: 620, color: "var(--ink)", marginBottom: 4 }}>
            Aucun pro connecté pour l'instant
          </div>
          <div style={{ fontSize: 13, color: "var(--ink-4)", lineHeight: 1.55 }}>
            Vos salons apparaîtront ici dès qu'ils auront pris leur premier rendez-vous avec vous.
          </div>
        </div>
      )}

      {/* === Section title : Actions rapides === */}
      <EspaceSectionLine label="Actions rapides"/>
      <div style={{
        display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10, marginTop: -2,
      }}>
        <EspaceShortcut icon="📅" title="Nouveau RDV"
          hint="Chez un de vos pros"
          onClick={() => {
            // Renvoie vers la page publique du 1er pro (ou la liste annuaire si aucun pro)
            const first = client.pros[0];
            if (first?.bookingSlug) window.location.href = `/${first.bookingSlug}`;
            else window.location.href = "/annuaire";
          }}/>
        <EspaceShortcut icon="📜" title="Mon historique"
          hint="Tous vos RDV passés"
          onClick={onSeeHistory}/>
        <EspaceShortcut icon="⭐" title="Laisser un avis"
          hint="Notez vos RDV récents"
          onClick={onSeeHistory}/>
        <EspaceShortcut icon="👤" title="Mon profil"
          hint="Préférences & sécurité"
          onClick={onSeeProfile}/>
      </div>
    </div>
  );
};

/* Switcher « Vos pros préférés » — rangée scrollable d'onglets :
   « Tous » + un onglet par pro (avatar initiales + nb de RDV à venir) +
   un onglet « Ajouter un pro ». Sélectionner un pro filtre les RDV et
   cartes de fidélité affichés en dessous (style preview client). */
const EspaceProTabs = ({ pros, selectedProId, onSelect }) => {
  const initials = (name) => (name || "?")
    .split(" ").map(x => x[0]).slice(0, 2).join("").toUpperCase();

  const tabBase = {
    display: "flex", alignItems: "center", gap: 10,
    padding: "9px 13px", borderRadius: 12, cursor: "pointer",
    background: "var(--surface)", border: "1px solid var(--line)",
    whiteSpace: "nowrap", flexShrink: 0, fontFamily: "inherit", textAlign: "left",
    transition: "border-color .15s, background .15s",
  };
  const tabOn = {
    background: "var(--cli-soft)", borderColor: "var(--cli-accent)",
    boxShadow: "inset 0 0 0 1px var(--cli-accent)",
  };
  const avBase = {
    width: 32, height: 32, borderRadius: "50%",
    display: "inline-flex", alignItems: "center", justifyContent: "center",
    fontWeight: 680, fontSize: 12, flexShrink: 0,
    background: "var(--cli-soft)", color: "var(--cli-ink)",
  };

  return (
    <>
      <EspaceSectionLine label="Vos pros préférés"/>
      <div className="esp-pro-tabs" style={{
        display: "flex", gap: 8, overflowX: "auto", paddingBottom: 6, marginTop: -2,
        scrollbarWidth: "none",
      }}>
        <style>{`.esp-pro-tabs::-webkit-scrollbar{display:none}`}</style>

        {/* Onglet « Tous » */}
        <button onClick={() => onSelect(null)}
          style={{ ...tabBase, ...(selectedProId === null ? tabOn : {}) }}>
          <span style={{ ...avBase }}>★</span>
          <span>
            <strong style={{ display: "block", fontSize: 13, fontWeight: 620, color: "var(--ink)", letterSpacing: "-0.005em" }}>Tous mes pros</strong>
            <small style={{ display: "block", fontSize: 11.5, color: "var(--ink-4)", marginTop: 1 }}>{pros.length} salon{pros.length > 1 ? "s" : ""}</small>
          </span>
        </button>

        {/* Un onglet par pro */}
        {pros.map(pro => {
          const on = selectedProId === pro.proId;
          const count = (pro.upcoming || []).length;
          return (
            <button key={pro.proId} onClick={() => onSelect(pro.proId)}
              style={{ ...tabBase, ...(on ? tabOn : {}) }}>
              <span style={{ ...avBase }}>{initials(pro.name)}</span>
              <span>
                <strong style={{ display: "block", fontSize: 13, fontWeight: 620, color: "var(--ink)", letterSpacing: "-0.005em" }}>{pro.name}</strong>
                <small style={{ display: "block", fontSize: 11.5, color: "var(--ink-4)", marginTop: 1 }}>
                  {pro.type ? `${pro.type} · ` : ""}{count > 0 ? `${count} RDV à venir` : "Aucun RDV à venir"}
                </small>
              </span>
            </button>
          );
        })}

        {/* Onglet « Ajouter un pro » */}
        <button onClick={() => { window.location.href = "/annuaire"; }}
          style={{ ...tabBase, background: "var(--bg-alt)", borderStyle: "dashed" }}>
          <span style={{
            ...avBase, background: "var(--surface)",
            border: "1.5px dashed var(--line-strong)", color: "var(--ink-4)", fontSize: 16,
          }}>+</span>
          <span>
            <strong style={{ display: "block", fontSize: 13, fontWeight: 620, color: "var(--ink-3)", letterSpacing: "-0.005em" }}>Ajouter un pro</strong>
            <small style={{ display: "block", fontSize: 11.5, color: "var(--ink-4)", marginTop: 1 }}>via lien ou annuaire</small>
          </span>
        </button>
      </div>
    </>
  );
};

/* Section title compact — bandeau uppercase tracked + ligne séparatrice
   (cohérence visuelle avec les section-title du preview). */
const EspaceSectionLine = ({ icon, label }) => (
  <div style={{
    display: "flex", alignItems: "center", gap: 10,
    margin: "20px 0 12px",
    fontSize: 11.5, textTransform: "uppercase", letterSpacing: "0.08em",
    color: "var(--ink-4)", fontWeight: 680,
  }}>
    {icon && <span aria-hidden style={{ fontSize: 14 }}>{icon}</span>}
    <span>{label}</span>
    <span aria-hidden style={{ flex: 1, height: 1, background: "var(--line)" }}/>
  </div>
);

/* Raccourci sober façon preview : white card avec icône violet doux */
const EspaceShortcut = ({ icon, title, hint, onClick }) => (
  <button onClick={onClick} style={{
    display: "flex", flexDirection: "column", alignItems: "flex-start", gap: 8,
    padding: 16, background: "var(--surface)",
    border: "1px solid var(--line)", borderRadius: 12,
    cursor: "pointer", fontFamily: "inherit", textAlign: "left",
    color: "inherit",
    transition: "border-color .15s, transform .15s, box-shadow .2s",
  }}
    onMouseEnter={e => {
      e.currentTarget.style.borderColor = "var(--cli-accent)";
      e.currentTarget.style.transform = "translateY(-2px)";
      e.currentTarget.style.boxShadow = "0 4px 14px -8px rgba(15,18,30,0.10)";
    }}
    onMouseLeave={e => {
      e.currentTarget.style.borderColor = "var(--line)";
      e.currentTarget.style.transform = "translateY(0)";
      e.currentTarget.style.boxShadow = "none";
    }}>
    <span style={{
      width: 38, height: 38, borderRadius: 10,
      background: "var(--cli-soft)", color: "var(--cli-accent)",
      display: "inline-flex", alignItems: "center", justifyContent: "center",
      fontSize: 18,
    }}>{icon}</span>
    <strong style={{ fontSize: 14, fontWeight: 620, color: "var(--ink)", letterSpacing: "-0.005em" }}>{title}</strong>
    <small style={{ fontSize: 12, color: "var(--ink-4)" }}>{hint}</small>
  </button>
);

/* Carte de fidélité — gradient violet plein style preview "fid-card".
   Texte blanc, progression en haut (X/Y + visites label), barre blanche
   semi-transparente, récompense avec emoji 🎁 en bas. */
const ProFidelityCard = ({ pro }) => {
  const f = pro.fidelite;
  const remaining = Math.max(0, f.target - f.visits);
  const pct = Math.min(100, Math.round((f.visits / Math.max(1, f.target)) * 100));
  // Emoji selon le type d'activité
  const emoji = /coiff/i.test(pro.type || "") ? "💇"
              : /ongle|nail/i.test(pro.type || "") ? "💅"
              : /esth/i.test(pro.type || "") ? "💆"
              : /barb/i.test(pro.type || "") ? "💈"
              : "✨";
  return (
    <div style={{
      padding: 22,
      background: `linear-gradient(135deg, var(--cli-accent) 0%, color-mix(in oklab, var(--cli-accent) 70%, var(--ink)) 100%)`,
      color: "#fff", borderRadius: 16,
      position: "relative", overflow: "hidden",
      boxShadow: "0 8px 20px -10px color-mix(in oklab, var(--cli-accent) 60%, transparent)",
    }}>
      {/* Orbs décoratifs en arrière */}
      <div aria-hidden style={{
        position: "absolute", top: -40, right: -40, width: 160, height: 160, borderRadius: "50%",
        background: "rgba(255,255,255,0.08)", pointerEvents: "none",
      }}/>
      <div aria-hidden style={{
        position: "absolute", bottom: -30, left: -30, width: 120, height: 120, borderRadius: "50%",
        background: "rgba(255,255,255,0.06)", pointerEvents: "none",
      }}/>

      <div style={{ position: "relative" }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: 14 }}>
          <span style={{
            fontSize: 13, textTransform: "uppercase", letterSpacing: "0.08em",
            fontWeight: 680, opacity: 0.88,
          }}>
            {pro.name} {pro.type ? `· ${pro.type}` : ""}
          </span>
          <span style={{ fontSize: 24 }} aria-hidden>{emoji}</span>
        </div>

        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", marginBottom: 10 }}>
          <span style={{
            fontSize: 36, fontWeight: 720, letterSpacing: "-0.03em", lineHeight: 1,
            fontFamily: "var(--ff-display)",
          }}>
            {f.visits}<small style={{ fontSize: 18, fontWeight: 500, opacity: 0.7, marginLeft: 2 }}>/{f.target}</small>
          </span>
          <span style={{ fontSize: 13, fontWeight: 540, opacity: 0.88 }}>visites</span>
        </div>

        <div style={{
          height: 8, background: "rgba(255,255,255,0.2)", borderRadius: 4,
          overflow: "hidden", marginBottom: 14,
        }}>
          <div style={{
            height: "100%", width: `${pct}%`, background: "#fff", borderRadius: 4,
            boxShadow: "0 0 12px rgba(255,255,255,0.5)",
            transition: "width .5s cubic-bezier(.22,1,.36,1)",
          }}/>
        </div>

        <div style={{
          fontSize: 13, opacity: 0.95, display: "flex", alignItems: "center", gap: 8, lineHeight: 1.45,
        }}>
          <span style={{ fontSize: 18 }} aria-hidden>🎁</span>
          <span>
            {remaining > 0
              ? <>Plus que <strong style={{ fontWeight: 700 }}>{remaining} visite{remaining > 1 ? "s" : ""}</strong> pour {f.rewardLabel}</>
              : <>🎉 Récompense débloquée : {f.rewardLabel}</>}
          </span>
        </div>
      </div>
    </div>
  );
};

const EspaceRdvRow = ({ rdv, pro, onOpen }) => (
  <button onClick={onOpen} style={{
    display: "grid", gridTemplateColumns: "64px 1fr auto", gap: 14, width: "100%",
    padding: 14, background: "var(--surface)", border: "1px solid var(--line)",
    borderRadius: 12, fontFamily: "inherit", cursor: "pointer", textAlign: "left",
    alignItems: "center",
    transition: "border-color .15s var(--ease,cubic-bezier(.22,1,.36,1)), transform .15s, box-shadow .2s",
  }}
    onMouseEnter={e => {
      e.currentTarget.style.borderColor = "var(--cli-soft-2)";
      e.currentTarget.style.transform = "translateY(-1px)";
      e.currentTarget.style.boxShadow = "0 4px 14px -8px rgba(15,18,30,0.10)";
    }}
    onMouseLeave={e => {
      e.currentTarget.style.borderColor = "var(--line)";
      e.currentTarget.style.transform = "translateY(0)";
      e.currentTarget.style.boxShadow = "none";
    }}>
    {/* Pastille date violet doux */}
    <div style={{
      textAlign: "center", padding: "8px 4px",
      background: "var(--cli-soft)",
      borderRadius: 10,
      color: "var(--cli-ink)",
    }}>
      <strong style={{
        display: "block", fontSize: 20, fontWeight: 680, letterSpacing: "-0.02em", lineHeight: 1,
        fontFamily: "var(--ff-display)",
      }}>
        {new Date(rdv.date + "T00:00:00").getDate()}
      </strong>
      <span style={{
        display: "block", fontSize: 11, textTransform: "uppercase",
        letterSpacing: "0.05em", fontWeight: 660, marginTop: 3,
      }}>
        {_espaceFmtDate(rdv.date).split(" ")[2]?.slice(0, 3) || ""}
      </span>
    </div>
    {/* Infos service */}
    <div style={{ minWidth: 0 }}>
      <div style={{
        fontSize: 14.5, fontWeight: 620, color: "var(--ink)",
        letterSpacing: "-0.005em",
        overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
      }}>{rdv.service}</div>
      <div style={{
        fontSize: 13, color: "var(--ink-4)", marginTop: 3,
        overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
      }}>
        {_espaceFmtHour(rdv.h)} · {rdv.d}h · {pro?.name || ""} · <strong style={{ fontWeight: 660, color: "var(--ink-3)" }}>{rdv.price}€</strong>
      </div>
      <span style={{
        display: "inline-flex", alignItems: "center",
        marginTop: 6, padding: "3px 9px",
        background: "color-mix(in oklab, oklch(72% 0.16 145) 18%, var(--surface))",
        color: "oklch(35% 0.12 145)",
        borderRadius: 999, fontSize: 10.5, fontWeight: 680,
        textTransform: "uppercase", letterSpacing: "0.04em",
      }}>
        Confirmé
      </span>
    </div>
    {/* Flèche */}
    <div style={{ color: "var(--ink-4)", fontSize: 18, flexShrink: 0, padding: "0 4px" }}>→</div>
  </button>
);
/* ====== Détail d'un RDV ====== */
const EspaceRdvDetail = ({ rdv, pro, onBack, onCancelRequest }) => (
  <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
    <button onClick={onBack} style={{
      alignSelf: "flex-start", display: "inline-flex", alignItems: "center", gap: 6,
      padding: "6px 12px", background: "transparent", border: "none", color: "var(--ink-3)",
      fontSize: 13, cursor: "pointer", fontFamily: "inherit",
    }}>← Retour</button>

    <div className="esp-card" style={{ padding: 22 }}>
      <div style={{
        padding: "14px 16px",
        background: `linear-gradient(135deg, oklch(95% 0.06 ${pro?.hue || 278}), oklch(94% 0.04 ${((pro?.hue || 278) + 30) % 360}))`,
        borderRadius: 13, marginBottom: 16,
      }}>
        <div style={{
          fontSize: 11.5, color: `oklch(40% 0.12 ${pro?.hue || 278})`,
          textTransform: "uppercase", letterSpacing: "0.06em", fontWeight: 540, marginBottom: 4,
        }}>
          Chez {pro?.name || "votre pro"}
        </div>
        <h2 style={{ margin: 0, fontFamily: "var(--ff-display)", fontSize: 21, fontWeight: 580, letterSpacing: "-0.022em" }}>
          {rdv.service}
        </h2>
        <div style={{ marginTop: 8, fontSize: 13.5, color: "var(--ink-2)" }}>
          {_espaceFmtDate(rdv.date)} · <strong>{_espaceFmtHour(rdv.h)}</strong> ({rdv.d}h)
        </div>
        <div style={{ marginTop: 10, display: "flex", alignItems: "baseline", gap: 6 }}>
          <span style={{ fontFamily: "var(--ff-display)", fontSize: 28, fontWeight: 580, color: `oklch(40% 0.13 ${pro?.hue || 278})`, letterSpacing: "-0.025em" }}>{rdv.price}</span>
          <span style={{ fontSize: 14, color: "var(--ink-3)", fontWeight: 540 }}>€</span>
        </div>
      </div>

      <div style={{
        padding: "10px 12px", background: "var(--bg-alt)", borderRadius: 11,
        fontSize: 12, color: "var(--ink-2)", marginBottom: 14,
      }}>
        <div style={{ display: "flex", justifyContent: "space-between", padding: "3px 0" }}>
          <span>Pro</span><strong style={{ fontWeight: 540 }}>{pro?.owner || "—"}</strong>
        </div>
        <div style={{ display: "flex", justifyContent: "space-between", padding: "3px 0" }}>
          <span>Adresse</span><strong style={{ fontWeight: 540, textAlign: "right" }}>{pro?.address || "—"}</strong>
        </div>
      </div>

      <div style={{ display: "flex", flexDirection: "column", gap: 7 }}>
        <button className="esp-tab" style={{ width: "100%", textAlign: "left", padding: "11px 14px" }}>
          📅 Ajouter à mon agenda
        </button>
        <a href={`https://wa.me/`} target="_blank" rel="noopener" style={{ textDecoration: "none" }}>
          <button className="esp-tab" style={{ width: "100%", textAlign: "left", padding: "11px 14px" }}>
            💬 Envoyer un message
          </button>
        </a>
        {/* Modifier : ouvre EspaceModifyModal qui demande date + heure puis
            appelle client_modify_appointment côté Supabase. */}
        <button onClick={() => onModifyRequest(rdv.id)} style={{
          padding: "11px 14px", background: "transparent", border: "1px solid var(--cli-soft-2)",
          borderRadius: 999, fontSize: 13, fontWeight: 540, color: "var(--cli-ink)",
          cursor: "pointer", textAlign: "left", fontFamily: "inherit",
        }}>
          🗓 Déplacer ce rendez-vous
        </button>
        <button onClick={() => onCancelRequest(rdv.id)} style={{
          padding: "11px 14px", background: "transparent", border: "1px solid oklch(88% 0.06 25)",
          borderRadius: 999, fontSize: 13, fontWeight: 540, color: "oklch(48% 0.16 25)",
          cursor: "pointer", textAlign: "left", fontFamily: "inherit",
        }}>
          🗑 Annuler ce rendez-vous
        </button>
        {/* Note politique : on rappelle au client la règle du pro pour
            qu'il ne soit pas surpris par un refus serveur. */}
        <div style={{
          marginTop: 6, padding: "8px 12px",
          background: "var(--bg-alt)", borderRadius: 9,
          fontSize: 11.5, color: "var(--ink-3)", lineHeight: 1.5,
        }}>
          Modification et annulation possibles jusqu'à un délai défini par {pro?.name || "votre pro"}. Au-delà, contactez-{pro?.owner ? "le" : "la"} directement.
        </div>
      </div>
    </div>
  </div>
);

/* ====== Modal d'erreur policy ======
   Affichée quand le client tente d'annuler / déplacer un RDV trop tard
   par rapport au délai exigé par le pro. Au lieu d'un simple toast, on
   explique clairement le pourquoi + on facilite la prise de contact.
   action: "cancel" | "modify" */
const EspacePolicyErrorModal = ({ action, proName, ownerName, phone, hoursRequired, hoursLeft, onClose }) => {
  const isCancel = action === "cancel";
  // Formatage humain du délai requis (24h → "24 heures", 168h → "7 jours")
  const fmtHours = (h) => {
    if (!h) return "défini par le pro";
    if (h >= 168 && h % 168 === 0) {
      const w = h / 168; return `${w} semaine${w > 1 ? "s" : ""}`;
    }
    if (h >= 24 && h % 24 === 0) {
      const d = h / 24; return `${d} jour${d > 1 ? "s" : ""}`;
    }
    return `${h} heure${h > 1 ? "s" : ""}`;
  };
  const fmtLeft = (h) => {
    if (h == null) return "trop peu";
    if (h < 0) return "déjà passé";
    if (h < 1) return `${Math.round(h * 60)} min restantes`;
    if (h < 24) return `${Math.round(h)} h restantes`;
    return `${Math.round(h / 24)} j restant${h >= 48 ? "s" : ""}`;
  };
  // Construit le lien WhatsApp / tel / mailto à partir des infos pro.
  const cleanPhone = (phone || "").replace(/[^\d+]/g, "");
  const whatsappLink = cleanPhone ? `https://wa.me/${cleanPhone.replace(/^0/, "33")}` : null;
  const telLink = cleanPhone ? `tel:${cleanPhone}` : null;

  return (
    <div onClick={onClose} style={{
      position: "fixed", inset: 0, background: "rgba(15,16,32,0.6)",
      display: "flex", alignItems: "center", justifyContent: "center",
      zIndex: 9999, padding: 16,
      backdropFilter: "blur(4px)", WebkitBackdropFilter: "blur(4px)",
      animation: "cbPolicyFadeIn .2s ease",
    }}>
      <style>{`@keyframes cbPolicyFadeIn { from { opacity: 0; } to { opacity: 1; } }
        @keyframes cbPolicyPopIn { from { opacity: 0; transform: translateY(20px) scale(0.96); } to { opacity: 1; transform: translateY(0) scale(1); } }`}</style>
      <div onClick={e => e.stopPropagation()} className="esp-card" style={{
        maxWidth: 420, width: "100%", padding: 0, overflow: "hidden",
        animation: "cbPolicyPopIn .28s cubic-bezier(.22,1,.36,1)",
      }}>
        {/* Header coloré warn */}
        <div style={{
          padding: "20px 22px 18px",
          background: "linear-gradient(135deg, oklch(94% 0.08 50) 0%, oklch(96% 0.05 30) 100%)",
          borderBottom: "1px solid oklch(86% 0.07 50)",
          textAlign: "center",
        }}>
          <div style={{
            width: 54, height: 54, borderRadius: 50, margin: "0 auto 10px",
            background: "oklch(70% 0.16 60)", color: "#fff",
            display: "flex", alignItems: "center", justifyContent: "center",
            boxShadow: "0 8px 20px -8px oklch(70% 0.16 60)",
            fontSize: 26, fontWeight: 700,
          }}>!</div>
          <h3 style={{
            margin: 0, fontFamily: "var(--ff-display)", fontSize: 18, fontWeight: 600,
            letterSpacing: "-0.02em", color: "oklch(38% 0.14 50)",
          }}>
            {isCancel ? "Annulation impossible" : "Déplacement impossible"}
          </h3>
          <p style={{
            margin: "6px 0 0", fontSize: 12.5, color: "oklch(40% 0.10 50)",
            lineHeight: 1.5,
          }}>
            Le délai exigé par <strong>{proName}</strong> est dépassé.
          </p>
        </div>

        {/* Body : détails + actions */}
        <div style={{ padding: "18px 22px 22px" }}>
          {/* Détail du délai */}
          <div style={{
            padding: "12px 14px", marginBottom: 14,
            background: "var(--bg-alt)", border: "1px solid var(--line)",
            borderRadius: 10, fontSize: 13, color: "var(--ink-2)", lineHeight: 1.55,
          }}>
            {isCancel
              ? <>Vos annulations sont autorisées <strong>au moins {fmtHours(hoursRequired)} avant</strong> le rendez-vous.</>
              : <>Vos déplacements sont autorisés <strong>au moins {fmtHours(hoursRequired)} avant</strong> le rendez-vous.</>}
            {hoursLeft != null && (
              <div style={{
                marginTop: 6, fontSize: 11.5, color: "var(--ink-3)",
              }}>Il vous {hoursLeft < 0 ? "restait" : "reste"} <strong>{fmtLeft(hoursLeft)}</strong>.</div>
            )}
          </div>

          {/* CTA contact pro */}
          <div style={{ fontSize: 13, color: "var(--ink-2)", marginBottom: 10, lineHeight: 1.5 }}>
            {isCancel
              ? "Pour annuler quand même, contactez directement votre prestataire :"
              : "Pour modifier votre RDV, contactez directement votre prestataire :"}
          </div>
          <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
            {whatsappLink && (
              <a href={whatsappLink} target="_blank" rel="noopener noreferrer" style={{
                display: "flex", alignItems: "center", gap: 10,
                padding: "11px 14px",
                background: "var(--sage-soft)", color: "var(--sage-ink)",
                border: "1px solid oklch(82% 0.07 160)",
                borderRadius: 11, textDecoration: "none", fontWeight: 540, fontSize: 13.5,
              }}>
                <Icon name="chat" size={15}/> WhatsApp {ownerName ? `· ${ownerName}` : ""}
              </a>
            )}
            {telLink && (
              <a href={telLink} style={{
                display: "flex", alignItems: "center", gap: 10,
                padding: "11px 14px",
                background: "var(--cli-soft)", color: "var(--cli-ink)",
                border: "1px solid var(--cli-soft-2)",
                borderRadius: 11, textDecoration: "none", fontWeight: 540, fontSize: 13.5,
              }}>
                <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor"
                  strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                  <path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"/>
                </svg>
                Appeler {phone ? `· ${phone}` : ""}
              </a>
            )}
            {!whatsappLink && !telLink && (
              <div style={{
                padding: "11px 14px", background: "var(--bg-alt)",
                border: "1px dashed var(--line-strong)", borderRadius: 11,
                fontSize: 12.5, color: "var(--ink-3)", textAlign: "center",
              }}>
                Coordonnées du pro indisponibles. Voyez vos précédents échanges (email, SMS).
              </div>
            )}
          </div>

          <button onClick={onClose} style={{
            width: "100%", marginTop: 12, padding: "10px 14px",
            background: "transparent", border: "1px solid var(--line)",
            borderRadius: 10, fontSize: 13, fontWeight: 540, color: "var(--ink-3)",
            cursor: "pointer", fontFamily: "inherit",
          }}>
            J'ai compris
          </button>
        </div>
      </div>
    </div>
  );
};

/* ====== Modal de modification (déplacement) d'un RDV ====== */
const EspaceModifyModal = ({ rdv, onClose, onConfirm }) => {
  if (!rdv) return null;
  const [date, setDate] = React.useState(rdv.date);
  const [time, setTime] = React.useState(() => {
    const h = Math.floor(rdv.h);
    const m = Math.round((rdv.h - h) * 60);
    return `${String(h).padStart(2,"0")}:${String(m).padStart(2,"0")}`;
  });
  const [busy, setBusy] = React.useState(false);
  const submit = async () => {
    if (!date || !time) return;
    setBusy(true);
    const [hh, mm] = time.split(":").map(Number);
    const newHour = hh + (mm || 0) / 60;
    try { await onConfirm(rdv.id, date, newHour); }
    finally { setBusy(false); }
  };
  return (
    <div onClick={onClose} style={{
      position: "fixed", inset: 0, background: "rgba(15,16,32,0.55)",
      display: "flex", alignItems: "center", justifyContent: "center",
      zIndex: 9999, padding: 16,
    }}>
      <div onClick={e => e.stopPropagation()} className="esp-card" style={{
        maxWidth: 420, width: "100%", padding: 22,
      }}>
        <h3 style={{ margin: 0, fontFamily: "var(--ff-display)", fontSize: 18, fontWeight: 580, letterSpacing: "-0.02em" }}>
          Déplacer ce rendez-vous
        </h3>
        <p style={{ marginTop: 8, fontSize: 13, color: "var(--ink-3)", lineHeight: 1.5 }}>
          Choisissez un nouveau créneau. Si l'horaire n'est pas disponible, on vous le dira.
        </p>
        <div style={{ marginTop: 14, display: "flex", flexDirection: "column", gap: 10 }}>
          <label style={{ fontSize: 12.5, color: "var(--ink-2)", fontWeight: 540 }}>
            Nouvelle date
            <input type="date" value={date}
              onChange={e => setDate(e.target.value)}
              min={new Date().toISOString().slice(0,10)}
              style={{
                width: "100%", marginTop: 4, padding: "10px 12px",
                background: "var(--bg-alt)", border: "1px solid var(--line-strong)",
                borderRadius: 9, fontSize: 14, fontFamily: "inherit",
                boxSizing: "border-box", color: "var(--ink)",
              }}/>
          </label>
          <label style={{ fontSize: 12.5, color: "var(--ink-2)", fontWeight: 540 }}>
            Nouvelle heure
            <input type="time" value={time}
              onChange={e => setTime(e.target.value)}
              step="900"
              style={{
                width: "100%", marginTop: 4, padding: "10px 12px",
                background: "var(--bg-alt)", border: "1px solid var(--line-strong)",
                borderRadius: 9, fontSize: 14, fontFamily: "inherit",
                boxSizing: "border-box", color: "var(--ink)",
              }}/>
          </label>
        </div>
        <div style={{ marginTop: 18, display: "flex", gap: 8 }}>
          <button onClick={onClose} disabled={busy} className="esp-tab"
            style={{ flex: 1, textAlign: "center", padding: "11px 14px" }}>Annuler</button>
          <button onClick={submit} disabled={busy} style={{
            flex: 2, padding: "11px 14px", background: "var(--cli-accent)", color: "#fff",
            border: "none", borderRadius: 999, fontSize: 13, fontWeight: 580,
            cursor: "pointer", fontFamily: "inherit",
          }}>{busy ? "Déplacement…" : "Confirmer le nouveau créneau"}</button>
        </div>
      </div>
    </div>
  );
};

/* ====== Modal de confirmation d'annulation ====== */
const EspaceCancelModal = ({ rdv, onClose, onConfirm }) => {
  if (!rdv) return null;
  return (
    <div onClick={onClose} style={{
      position: "fixed", inset: 0, background: "rgba(15,18,30,0.55)",
      zIndex: 9999, display: "flex", alignItems: "center", justifyContent: "center",
      padding: 18, backdropFilter: "blur(6px)",
    }}>
      <div onClick={e => e.stopPropagation()} style={{
        background: "var(--surface)", borderRadius: 18, maxWidth: 400, width: "100%",
        padding: 26, textAlign: "center",
        boxShadow: "0 40px 80px -20px rgba(0,0,0,0.5)",
        animation: "questIn .25s cubic-bezier(.22,1,.36,1)",
      }}>
        <div style={{
          width: 56, height: 56, borderRadius: "50%",
          background: "oklch(95% 0.06 25)", color: "oklch(48% 0.16 25)",
          margin: "0 auto 14px",
          display: "flex", alignItems: "center", justifyContent: "center", fontSize: 26, fontWeight: 600,
        }}>!</div>
        <h3 style={{ margin: "0 0 6px", fontFamily: "var(--ff-display)", fontSize: 18, fontWeight: 580 }}>
          Annuler ce rendez-vous ?
        </h3>
        <p style={{ margin: "0 0 14px", fontSize: 13, color: "var(--ink-3)", lineHeight: 1.5 }}>
          Votre pro sera prévenue immédiatement. Si l'annulation est moins de 24h avant le RDV, des frais peuvent s'appliquer.
        </p>
        <div style={{
          padding: "10px 12px", background: "var(--bg-alt)", borderRadius: 10,
          fontSize: 12, color: "var(--ink-2)", marginBottom: 14, textAlign: "left",
        }}>
          {_espaceFmtDate(rdv.date)} · <strong style={{ fontWeight: 540 }}>{rdv.service}</strong> · {_espaceFmtHour(rdv.h)}
        </div>
        <div style={{ display: "flex", flexDirection: "column", gap: 7 }}>
          <button onClick={onConfirm} style={{
            padding: 12, background: "#FF3B30", color: "#fff", border: "none",
            borderRadius: 11, fontSize: 13.5, fontWeight: 540, cursor: "pointer", fontFamily: "inherit",
          }}>
            Oui, annuler définitivement
          </button>
          <button onClick={onClose} style={{
            padding: 12, background: "var(--bg-alt)", color: "var(--ink)", border: "none",
            borderRadius: 11, fontSize: 13.5, fontWeight: 540, cursor: "pointer", fontFamily: "inherit",
          }}>
            Garder mon rendez-vous
          </button>
        </div>
      </div>
    </div>
  );
};

/* ====== Carte fidélité ====== */
const FideliteCard = ({ fidelite }) => {
  const f = fidelite;
  const cells = Array.from({ length: f.target }, (_, i) => i < f.visits);
  return (
    <div className="esp-card" style={{ padding: 18 }}>
      <div style={{
        fontSize: 11, color: "var(--cli-ink)", textTransform: "uppercase",
        letterSpacing: "0.06em", fontFamily: "var(--ff-text)", fontWeight: 540, marginBottom: 6,
      }}>
        🎁 Carte fidélité
      </div>
      <div style={{ fontFamily: "var(--ff-display)", fontSize: 17, fontWeight: 580, marginBottom: 12 }}>
        {f.visits}/{f.target} visites
      </div>
      <div style={{ display: "grid", gridTemplateColumns: `repeat(${f.target}, 1fr)`, gap: 4, marginBottom: 10 }}>
        {cells.map((done, i) => (
          <div key={i} style={{
            aspectRatio: "1 / 1", borderRadius: 6,
            background: done ? "var(--cli-accent)" : "var(--bg-alt)",
            border: done ? "none" : "1px dashed var(--line-strong)",
            display: "flex", alignItems: "center", justifyContent: "center",
            color: "#fff", fontSize: 12, fontWeight: 600,
          }}>
            {done ? "✓" : ""}
          </div>
        ))}
      </div>
      <div style={{ fontSize: 11.5, color: "var(--ink-3)", lineHeight: 1.4 }}>
        {f.target - f.visits > 0
          ? <>Plus que <strong style={{ color: "var(--cli-ink)" }}>{f.target - f.visits}</strong> visite{f.target - f.visits > 1 ? "s" : ""} pour : {f.rewardLabel}</>
          : <>🎉 Récompense débloquée : {f.rewardLabel}</>}
      </div>
    </div>
  );
};

/* ====== Raccourcis (historique + profil) ====== */
const ShortcutsCard = ({ onSeeHistory, onSeeProfile }) => (
  <div className="esp-card" style={{ padding: 18, display: "flex", flexDirection: "column", gap: 8 }}>
    <div style={{
      fontSize: 11, color: "var(--ink-4)", textTransform: "uppercase",
      letterSpacing: "0.06em", fontFamily: "var(--ff-text)", fontWeight: 540, marginBottom: 4,
    }}>
      Accès rapide
    </div>
    <button onClick={onSeeHistory} className="esp-tab" style={{
      width: "100%", textAlign: "left", padding: "12px 14px",
      display: "flex", justifyContent: "space-between", alignItems: "center",
    }}>
      <span>📜 Mes RDV passés</span>
      <span style={{ color: "var(--ink-4)" }}>›</span>
    </button>
    <button onClick={onSeeProfile} className="esp-tab" style={{
      width: "100%", textAlign: "left", padding: "12px 14px",
      display: "flex", justifyContent: "space-between", alignItems: "center",
    }}>
      <span>⚙️ Paramètres &amp; préférences</span>
      <span style={{ color: "var(--ink-4)" }}>›</span>
    </button>
  </div>
);

/* ====== Historique (agrégé tous pros) ====== */
const EspaceHistory = ({ client, onBack }) => {
  const [reviewing, setReviewing] = React.useState(null);
  const [reviewedIds, setReviewedIds] = React.useState({});

  // Tous les RDV (upcoming + history) avec ref au pro
  const all = React.useMemo(() => {
    const upcoming = client.pros.flatMap(p => p.upcoming.map(r => ({ ...r, _pro: p, _kind: "upcoming" })));
    const past     = client.pros.flatMap(p => p.history.map(r => ({ ...r, _pro: p, _kind: "past" })));
    return { upcoming: upcoming.sort((a, b) => a.date.localeCompare(b.date)),
             past:     past.sort((a, b) => b.date.localeCompare(a.date)) };
  }, [client.pros]);
  const totalSpent = all.past.reduce((s, r) => s + r.price, 0);
  const proCount = new Set([...all.upcoming, ...all.past].map(r => r._pro.proId)).size;

  // Groupe les RDV passés par mois (format "Mois Année" — ex "Avril 2026")
  const monthsGrouped = React.useMemo(() => {
    const map = new Map();
    all.past.forEach(r => {
      const d = new Date(r.date + "T00:00:00");
      const key = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`;
      const label = d.toLocaleDateString("fr-FR", { month: "long", year: "numeric" });
      if (!map.has(key)) map.set(key, { label: label.charAt(0).toUpperCase() + label.slice(1), items: [] });
      map.get(key).items.push(r);
    });
    return [...map.values()];
  }, [all.past]);

  const submitReview = (rating, comment) => {
    if (!reviewing) return;
    setReviewedIds(ids => ({ ...ids, [reviewing.rdv.id]: { rating, comment } }));
    setReviewing(null);
    showToast(`Merci pour votre avis ! Il sera visible chez ${reviewing.pro.name}.`);
  };

  const totalCount = all.upcoming.length + all.past.length;

  return (
    <div style={{ display: "flex", flexDirection: "column" }}>
      <button onClick={onBack} style={{
        alignSelf: "flex-start", padding: "6px 12px", marginBottom: 6,
        background: "transparent", border: "none", color: "var(--ink-3)",
        fontSize: 13, cursor: "pointer", fontFamily: "inherit",
      }}>← Retour</button>

      <h2 style={{
        margin: "0 0 5px",
        fontFamily: "var(--ff-display)", fontSize: 22, fontWeight: 680,
        letterSpacing: "-0.025em", color: "var(--ink)", lineHeight: 1.15,
      }}>
        Mes rendez-vous
      </h2>
      <p style={{ fontSize: 14.5, color: "var(--ink-4)", margin: "0 0 22px" }}>
        {totalCount > 0
          ? <>Historique complet · <strong style={{ fontWeight: 660, color: "var(--ink-3)" }}>{all.past.length} RDV passés</strong> · <strong style={{ fontWeight: 660, color: "var(--ink-3)" }}>{all.upcoming.length} à venir</strong> · {totalSpent}€ chez {proCount} pro{proCount > 1 ? "s" : ""}</>
          : "Votre historique apparaîtra ici une fois votre premier RDV passé."}
      </p>

      {totalCount === 0 ? (
        <div style={{
          padding: "48px 24px", textAlign: "center",
          background: "var(--surface)", border: "1px dashed var(--line-strong)",
          borderRadius: 14,
        }}>
          <div style={{ fontSize: 42, marginBottom: 12 }}>📭</div>
          <h3 style={{ margin: "0 0 6px", fontSize: 16, fontWeight: 660, color: "var(--ink)" }}>
            Pas encore de RDV
          </h3>
          <p style={{ margin: 0, fontSize: 13.5, color: "var(--ink-4)", lineHeight: 1.6 }}>
            Vos rendez-vous apparaîtront ici dès que vous en aurez pris un avec l'un de vos pros.
          </p>
        </div>
      ) : (
        <>
          {/* === Upcoming === */}
          {all.upcoming.length > 0 && (
            <div style={{ marginBottom: 24 }}>
              <div style={{
                fontSize: 11.5, textTransform: "uppercase", letterSpacing: "0.08em",
                color: "var(--ink-4)", fontWeight: 680, padding: "0 0 10px",
                borderBottom: "1px solid var(--line)", marginBottom: 12,
              }}>
                À venir · {all.upcoming.length}
              </div>
              <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
                {all.upcoming.map(r => (
                  <HistRow key={r.id} rdv={r} pro={r._pro} kind="upcoming"
                    review={null}
                    onClick={null}/>
                ))}
              </div>
            </div>
          )}

          {/* === Past, groupé par mois === */}
          {monthsGrouped.map(group => (
            <div key={group.label} style={{ marginBottom: 24 }}>
              <div style={{
                fontSize: 11.5, textTransform: "uppercase", letterSpacing: "0.08em",
                color: "var(--ink-4)", fontWeight: 680, padding: "0 0 10px",
                borderBottom: "1px solid var(--line)", marginBottom: 12,
              }}>
                {group.label} · {group.items.length}
              </div>
              <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
                {group.items.map(r => (
                  <HistRow key={r.id} rdv={r} pro={r._pro} kind="past"
                    review={reviewedIds[r.id] || null}
                    onClick={() => !reviewedIds[r.id] && setReviewing({ rdv: r, pro: r._pro })}/>
                ))}
              </div>
            </div>
          ))}
        </>
      )}

      {reviewing && (
        <EspaceReviewModal
          rdv={reviewing.rdv} pro={reviewing.pro}
          onClose={() => setReviewing(null)}
          onSubmit={submitReview}/>
      )}
    </div>
  );
};

/* Hist row : pattern hist-row du preview — date 3-lignes monospace,
   service noir + small gris, prix monospace + status en sous. */
const HistRow = ({ rdv, pro, kind, review, onClick }) => {
  const d = new Date(rdv.date + "T00:00:00");
  const dayShort = ["DIM","LUN","MAR","MER","JEU","VEN","SAM"][d.getDay()];
  const dayNum = d.getDate();
  const monthShort = d.toLocaleDateString("fr-FR", { month: "short" }).replace(".", "");
  const interactive = onClick && kind === "past" && !review;
  return (
    <div onClick={interactive ? onClick : undefined} style={{
      display: "grid", gridTemplateColumns: "auto 1fr auto", gap: 14, alignItems: "center",
      padding: "13px 14px",
      background: "var(--surface)", border: "1px solid var(--line)",
      borderRadius: 11,
      cursor: interactive ? "pointer" : "default",
      transition: "background .15s, border-color .15s",
    }}
      onMouseEnter={interactive ? (e => { e.currentTarget.style.background = "var(--bg-alt)"; e.currentTarget.style.borderColor = "var(--cli-soft-2)"; }) : undefined}
      onMouseLeave={interactive ? (e => { e.currentTarget.style.background = "var(--surface)"; e.currentTarget.style.borderColor = "var(--line)"; }) : undefined}>
      {/* Date */}
      <div style={{
        fontFamily: "var(--ff-mono)", fontSize: 11.5, color: "var(--ink-4)",
        textAlign: "center", minWidth: 42, lineHeight: 1.2,
      }}>
        {dayShort}
        <strong style={{
          display: "block", color: "var(--ink)", fontSize: 16, fontWeight: 680,
          fontFamily: "var(--ff-display)", letterSpacing: "-0.015em", lineHeight: 1.1,
        }}>{dayNum}</strong>
        {monthShort}
      </div>
      {/* Service info */}
      <div style={{ minWidth: 0 }}>
        <strong style={{
          display: "block", fontSize: 14, fontWeight: 620, color: "var(--ink)",
          letterSpacing: "-0.005em",
          overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
        }}>{rdv.service}</strong>
        <small style={{
          display: "block", fontSize: 12.5, color: "var(--ink-4)", marginTop: 1,
          overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
        }}>
          {_espaceFmtHour(rdv.h)} · {rdv.d ? `${rdv.d}h · ` : ""}{pro?.name || ""}
        </small>
      </div>
      {/* Price + status */}
      <div style={{ textAlign: "right", flexShrink: 0 }}>
        <strong style={{
          display: "block", fontSize: 14, fontWeight: 660, color: "var(--ink)",
          fontFamily: "var(--ff-mono)", fontVariantNumeric: "tabular-nums",
        }}>{rdv.price}€</strong>
        <small style={{
          display: "block", marginTop: 2, fontSize: 11, fontWeight: 540,
          color: kind === "upcoming" ? "oklch(35% 0.12 145)"
               : review ? "var(--cli-accent)"
               : "var(--ink-4)",
        }}>
          {kind === "upcoming" ? "Confirmé"
           : review ? "★ Avis donné"
           : "✓ Effectué"}
        </small>
      </div>
    </div>
  );
};

/* ====== Modal "Laisser un avis" ====== */
const EspaceReviewModal = ({ rdv, pro, onClose, onSubmit }) => {
  const [rating, setRating] = React.useState(0);
  const [hover, setHover] = React.useState(0);
  const [comment, setComment] = React.useState("");

  const submit = () => {
    if (rating === 0) {
      showToast("Choisissez une note (1 à 5 étoiles)", "warn");
      return;
    }
    onSubmit(rating, comment.trim());
  };

  return (
    <div onClick={onClose} style={{
      position: "fixed", inset: 0, background: "rgba(15,18,30,0.55)",
      zIndex: 9999, display: "flex", alignItems: "center", justifyContent: "center",
      padding: 18, backdropFilter: "blur(6px)",
    }}>
      <div onClick={e => e.stopPropagation()} style={{
        background: "var(--surface)", borderRadius: 18, maxWidth: 440, width: "100%",
        padding: 26,
        boxShadow: "0 40px 80px -20px rgba(0,0,0,0.5)",
        animation: "questIn .25s cubic-bezier(.22,1,.36,1)",
      }}>
        <div style={{
          fontSize: 11, color: "var(--ink-4)",
          textTransform: "uppercase", letterSpacing: "0.06em", fontWeight: 540, marginBottom: 4,
        }}>
          Avis pour {pro.name}
        </div>
        <h3 style={{
          margin: "0 0 4px",
          fontFamily: "var(--ff-display)", fontSize: 20, fontWeight: 580, letterSpacing: "-0.022em",
        }}>
          Notez votre <em style={{ fontStyle: "italic", color: "var(--cli-accent)" }}>RDV</em>
        </h3>
        <p style={{
          margin: "0 0 16px",
          fontSize: 12.5, color: "var(--ink-3)", lineHeight: 1.5,
        }}>
          {rdv.service} · {_espaceFmtDate(rdv.date)} · {pro.owner}
        </p>

        {/* Étoiles */}
        <div style={{
          display: "flex", justifyContent: "center", gap: 6, marginBottom: 16,
          padding: "14px 0", background: "var(--bg-alt)", borderRadius: 12,
        }}>
          {[1,2,3,4,5].map(i => (
            <button key={i}
              onMouseEnter={() => setHover(i)}
              onMouseLeave={() => setHover(0)}
              onClick={() => setRating(i)}
              style={{
                background: "transparent", border: "none", padding: 4,
                cursor: "pointer", fontSize: 32, lineHeight: 1,
                color: (hover ? i <= hover : i <= rating) ? "oklch(70% 0.16 80)" : "var(--line-strong)",
                transition: "transform .12s ease, color .12s ease",
                transform: hover === i || rating === i ? "scale(1.15)" : "scale(1)",
                fontFamily: "inherit",
              }}>
              ★
            </button>
          ))}
        </div>

        {/* Commentaire */}
        <label style={{
          display: "block", marginBottom: 6,
          fontSize: 11, color: "var(--ink-4)",
          textTransform: "uppercase", letterSpacing: "0.05em", fontWeight: 540,
        }}>Votre commentaire (optionnel)</label>
        <textarea value={comment} onChange={e => setComment(e.target.value)}
          placeholder="Décrivez votre expérience..."
          maxLength={400}
          style={{
            width: "100%", padding: "10px 13px",
            background: "var(--bg-alt)", border: "1px solid var(--line)",
            borderRadius: 11, fontSize: 13.5, fontFamily: "inherit", color: "var(--ink)",
            minHeight: 80, resize: "vertical", lineHeight: 1.5,
          }}/>
        <div style={{
          textAlign: "right", marginTop: 4,
          fontSize: 10.5, color: "var(--ink-4)",
        }}>
          {comment.length} / 400
        </div>

        {/* Anti-fraude info */}
        <div style={{
          marginTop: 12, padding: "9px 12px",
          background: "var(--cli-soft)", borderRadius: 10,
          fontSize: 11.5, color: "var(--cli-ink)", lineHeight: 1.5,
          display: "flex", alignItems: "flex-start", gap: 7,
        }}>
          <svg width="14" height="14" viewBox="0 0 24 24" style={{ flexShrink: 0, marginTop: 1 }}>
            <path fill="var(--cli-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>
          Votre avis sera marqué « <strong style={{ fontWeight: 580 }}>Vérifié par ClientBase</strong> » car il est lié à un vrai RDV terminé. Votre nom apparaîtra abrégé (ex. Marie D.).
        </div>

        {/* Actions */}
        <div style={{ display: "flex", gap: 8, marginTop: 16 }}>
          <button onClick={onClose} style={{
            flex: 1, padding: 12, background: "var(--bg-alt)", color: "var(--ink)",
            border: "none", borderRadius: 11, fontSize: 13.5, fontWeight: 540,
            cursor: "pointer", fontFamily: "inherit",
          }}>
            Annuler
          </button>
          <button onClick={submit} style={{
            flex: 1.5, padding: 12, background: "var(--cli-accent)", color: "#fff",
            border: "none", borderRadius: 11, fontSize: 13.5, fontWeight: 540,
            cursor: "pointer", fontFamily: "inherit",
          }}>
            Publier mon avis
          </button>
        </div>
      </div>
    </div>
  );
};

// Header de section avec icône colorée (audience cli-accent) + titre.
// Remplace les anciens « 👤 Identité » émojis par un visuel plus propre
// et plus cohérent avec le reste de l'app (cf. SettingsSection pro).
const EspaceSectionHeader = ({ icon, title, tone = "cli" }) => {
  const bg  = tone === "warn" ? "oklch(95% 0.06 25)"
            : tone === "sage" ? "var(--sage-soft)"
            : "var(--cli-soft)";
  const ink = tone === "warn" ? "oklch(48% 0.16 25)"
            : tone === "sage" ? "var(--sage-ink, oklch(38% 0.10 160))"
            : "var(--cli-ink)";
  return (
    <div style={{ display: "flex", alignItems: "center", gap: 11, marginBottom: 16 }}>
      <span style={{
        width: 32, height: 32, borderRadius: 9, flexShrink: 0,
        background: bg, color: ink,
        display: "inline-flex", alignItems: "center", justifyContent: "center",
      }}>
        <Icon name={icon} size={15}/>
      </span>
      <h4 style={{
        margin: 0, fontSize: 15, fontWeight: 580,
        letterSpacing: "-0.012em", color: "var(--ink)",
      }}>{title}</h4>
    </div>
  );
};

const _fmtCreatedAt = (iso) => {
  if (!iso) return null;
  try {
    const d = new Date(iso.length === 10 ? iso + "T00:00:00" : iso);
    const months = ["janvier","février","mars","avril","mai","juin","juillet","août","septembre","octobre","novembre","décembre"];
    return `${d.getDate()} ${months[d.getMonth()]} ${d.getFullYear()}`;
  } catch { return null; }
};

/* ====== Profil + préférences (Paramètres) ====== */
const EspaceProfile = ({ client, setClient, onBack, onLogout }) => {
  const [savingPwd, setSavingPwd] = React.useState(false);
  const [pwd, setPwd] = React.useState({ current: "", next: "", confirm: "" });

  const togglePref = (key) => {
    setClient(c => ({ ...c, preferences: { ...c.preferences, [key]: !c.preferences[key] }}));
    showToast("Préférence enregistrée");
  };

  const saveInfo = (field, val) => {
    setClient(c => ({ ...c, [field]: val }));
  };

  // Initiales auto-générées à partir du nom : utilisées pour l'avatar
  // si pas de photo. Hue dérivée du nom complet pour un dégradé stable.
  const initials = ((client.firstName?.[0] || "") + (client.lastName?.[0] || "")).toUpperCase() || "?";
  const hue = React.useMemo(() => {
    let h = 0;
    const str = (client.firstName || "") + (client.lastName || "");
    for (const c of str) h = (h * 31 + c.charCodeAt(0)) >>> 0;
    return 260 + (h % 70); // 260-330 : nuances violet/bleu client
  }, [client.firstName, client.lastName]);

  const memberSince = _fmtCreatedAt(client.createdAt);

  // Export RGPD : génère un JSON téléchargeable avec les données client
  // (infos perso + préférences + liste pros + historique). Conforme à
  // l'article 20 RGPD (droit à la portabilité des données).
  const exportData = () => {
    try {
      const payload = {
        exportedAt: new Date().toISOString(),
        client: {
          firstName: client.firstName,
          lastName: client.lastName,
          email: client.email,
          phone: client.phone,
          createdAt: client.createdAt,
          preferences: client.preferences,
        },
        pros: (client.pros || []).map(p => ({
          name: p.name, owner: p.owner, type: p.type,
          fidelite: p.fidelite,
          upcomingCount: (p.upcoming || []).length,
          historyCount: (p.history || []).length,
        })),
      };
      const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json" });
      const url = URL.createObjectURL(blob);
      const a = document.createElement("a");
      a.href = url;
      a.download = `clientbase-export-${new Date().toISOString().slice(0,10)}.json`;
      document.body.appendChild(a);
      a.click();
      a.remove();
      URL.revokeObjectURL(url);
      showToast("Export RGPD téléchargé 📥");
    } catch {
      showToast("Export indisponible, réessayez", "warn");
    }
  };

  const submitPwd = (e) => {
    e.preventDefault();
    if (!pwd.current || !pwd.next || !pwd.confirm) {
      showToast("Remplissez les 3 champs", "warn"); return;
    }
    if (pwd.next !== pwd.confirm) {
      showToast("Les deux nouveaux mots de passe ne correspondent pas", "warn"); return;
    }
    if (pwd.next.length < 6) {
      showToast("Mot de passe trop court (6 caractères minimum)", "warn"); return;
    }
    setSavingPwd(true);
    setTimeout(() => {
      setSavingPwd(false);
      setPwd({ current: "", next: "", confirm: "" });
      showToast("Mot de passe modifié");
    }, 500);
  };

  return (
    <div style={{ display: "flex", flexDirection: "column" }}>
      <button onClick={onBack} style={{
        alignSelf: "flex-start", padding: "6px 12px", marginBottom: 6,
        background: "transparent", border: "none", color: "var(--ink-3)",
        fontSize: 13, cursor: "pointer", fontFamily: "inherit",
      }}>← Retour</button>

      {/* === Profile head : centré, gradient cli-soft, avatar gradient,
          nom + email + badge "Modifier mon profil" === */}
      <div style={{
        display: "flex", flexDirection: "column", alignItems: "center", textAlign: "center",
        padding: "26px 18px",
        background: "linear-gradient(135deg, var(--cli-soft) 0%, var(--surface) 80%)",
        border: "1px solid var(--cli-soft-2)",
        borderRadius: 16,
        marginBottom: 20,
      }}>
        <div style={{
          width: 76, height: 76, borderRadius: "50%",
          background: `linear-gradient(135deg, var(--cli-accent), color-mix(in oklab, var(--cli-accent) 70%, var(--ink)))`,
          color: "#fff",
          display: "inline-flex", alignItems: "center", justifyContent: "center",
          fontWeight: 680, fontSize: 28, fontFamily: "var(--ff-display)",
          marginBottom: 14,
          letterSpacing: "-0.02em",
          boxShadow: "0 8px 20px -8px var(--cli-accent)",
        }}>{initials}</div>
        <h2 style={{
          margin: "0 0 4px",
          fontFamily: "var(--ff-display)", fontSize: 20, fontWeight: 680,
          letterSpacing: "-0.022em", color: "var(--ink)",
        }}>
          {client.firstName} {client.lastName}
        </h2>
        <p style={{
          margin: "0 0 14px", fontSize: 13.5, color: "var(--ink-4)",
          wordBreak: "break-all",
        }}>{client.email}</p>
        {memberSince && (
          <div style={{
            display: "inline-flex", alignItems: "center", gap: 5,
            padding: "5px 14px",
            background: "var(--surface)", border: "1px solid var(--cli-soft-2)",
            borderRadius: 999, fontSize: 12.5, fontWeight: 580, color: "var(--cli-ink)",
          }}>
            <span aria-hidden>✨</span>
            Membre depuis le {memberSince}
          </div>
        )}
      </div>

      {/* === Section : Mes informations === */}
      <EspaceSectionLine label="Mes informations"/>
      <div style={{
        padding: 20, background: "var(--surface)",
        border: "1px solid var(--line)", borderRadius: 14,
        marginBottom: 4,
      }}>
        <ProfField label="Prénom" value={client.firstName} onChange={v => saveInfo("firstName", v)}/>
        <ProfField label="Nom" value={client.lastName} onChange={v => saveInfo("lastName", v)}/>
        <ProfField label="Email" value={client.email} onChange={v => saveInfo("email", v)} type="email"/>
        <ProfField label="Téléphone" value={client.phone} onChange={v => saveInfo("phone", v)} type="tel" last/>
      </div>

      {/* === Section : Préférences notifications === */}
      <div style={{
        display: "flex", alignItems: "center", gap: 10,
        margin: "26px 0 12px",
        fontSize: 11.5, textTransform: "uppercase", letterSpacing: "0.08em",
        color: "var(--ink-4)", fontWeight: 680,
      }}>
        <span aria-hidden style={{ fontSize: 14 }}>🔔</span>
        <span>Préférences notifications</span>
        <span aria-hidden style={{ flex: 1, height: 1, background: "var(--line)" }}/>
      </div>
      <div style={{
        padding: "4px 20px",
        background: "var(--surface)", border: "1px solid var(--line)",
        borderRadius: 14,
      }}>
        <ProfToggle title="SMS de rappel"
          hint="Recevoir un SMS 24h avant chaque RDV"
          value={client.preferences.smsReminders}
          onChange={() => togglePref("smsReminders")}/>
        <ProfToggle title="Email de rappel"
          hint="Recevoir un email 24h avant chaque RDV"
          value={client.preferences.emailReminders}
          onChange={() => togglePref("emailReminders")}/>
        <ProfToggle title="Communications marketing"
          hint="Promos, nouveautés et conseils beauté de vos pros"
          value={client.preferences.marketing}
          onChange={() => togglePref("marketing")} last/>
      </div>

      {/* === Section : Sécurité & compte === */}
      <div style={{
        display: "flex", alignItems: "center", gap: 10,
        margin: "26px 0 12px",
        fontSize: 11.5, textTransform: "uppercase", letterSpacing: "0.08em",
        color: "var(--ink-4)", fontWeight: 680,
      }}>
        <span aria-hidden style={{ fontSize: 14 }}>🔐</span>
        <span>Sécurité & compte</span>
        <span aria-hidden style={{ flex: 1, height: 1, background: "var(--line)" }}/>
      </div>
      <div style={{
        padding: "4px 0",
        background: "var(--surface)", border: "1px solid var(--line)",
        borderRadius: 14, overflow: "hidden",
      }}>
        <ProfActionRow icon="🔑" iconTone="cli"
          title="Modifier mon mot de passe"
          onClick={() => {
            const next = prompt("Nouveau mot de passe (6+ caractères)");
            if (!next) return;
            if (next.length < 6) { showToast("6 caractères minimum", "warn"); return; }
            setSavingPwd(true);
            setTimeout(() => { setSavingPwd(false); showToast("Mot de passe modifié"); }, 500);
          }}/>
        <ProfActionRow icon="📥" iconTone="cli"
          title="Exporter mes données (RGPD)"
          onClick={exportData}/>
        <ProfActionRow icon="🗑️" iconTone="danger"
          title="Supprimer mon compte"
          danger last
          onClick={() => showToast("Suppression, disponible bientôt", "warn")}/>
      </div>

      {/* === Bouton déconnexion dark full-width === */}
      <button onClick={onLogout} style={{
        marginTop: 22, padding: "13px 18px",
        background: "var(--ink)", color: "#fff",
        border: "none", borderRadius: 10,
        fontSize: 14, fontWeight: 600,
        cursor: "pointer", fontFamily: "inherit",
        display: "flex", alignItems: "center", justifyContent: "center", gap: 8,
        letterSpacing: "-0.005em",
        transition: "background .15s ease",
      }}
        onMouseEnter={e => { e.currentTarget.style.background = "var(--cli-accent)"; }}
        onMouseLeave={e => { e.currentTarget.style.background = "var(--ink)"; }}>
        <span>👋</span>
        Me déconnecter
      </button>

      <p style={{
        textAlign: "center", fontSize: 11.5, color: "var(--ink-4)",
        margin: "20px 0 0", lineHeight: 1.55,
      }}>
        ClientBase v2.0 · Compte créé le {memberSince || "—"}<br/>
        <a href="/legal" style={{ color: "var(--cli-accent)" }}>Mentions légales</a> · <a href="/legal#cgu" style={{ color: "var(--cli-accent)" }}>CGU</a>
      </p>
    </div>
  );
};

/* Field profile : label gras au-dessus, input plein, border-bottom inter-fields */
const ProfField = ({ label, value, onChange, type = "text", last }) => (
  <div style={{
    paddingTop: 14, paddingBottom: 14,
    borderBottom: last ? "none" : "1px solid var(--line-2)",
  }}>
    <label style={{
      display: "block", marginBottom: 7,
      fontSize: 13, color: "var(--ink-3)", fontWeight: 560,
    }}>{label}</label>
    <input value={value || ""} type={type}
      onChange={e => onChange(e.target.value)}
      style={{
        width: "100%", padding: "11px 14px",
        background: "var(--surface)", border: "1px solid var(--line)",
        borderRadius: 10, fontSize: 14.5, color: "var(--ink)",
        fontFamily: "inherit", boxSizing: "border-box",
      }}/>
  </div>
);

/* Toggle row style preview : title + hint + iOS switch */
const ProfToggle = ({ title, hint, value, onChange, last }) => (
  <div style={{
    display: "flex", alignItems: "center", justifyContent: "space-between",
    gap: 14, paddingTop: 14, paddingBottom: 14,
    borderBottom: last ? "none" : "1px solid var(--line-2)",
  }}>
    <div style={{ flex: 1, minWidth: 0 }}>
      <strong style={{ display: "block", fontSize: 14.5, fontWeight: 600, color: "var(--ink)" }}>{title}</strong>
      {hint && <small style={{ display: "block", fontSize: 12.5, color: "var(--ink-4)", marginTop: 2, lineHeight: 1.5 }}>{hint}</small>}
    </div>
    <label style={{ cursor: "pointer", flexShrink: 0 }}>
      <input type="checkbox" checked={!!value} onChange={onChange}
        style={{ display: "none" }}/>
      <span style={{
        display: "block", width: 44, height: 26, borderRadius: 999,
        background: value ? "var(--cli-accent)" : "var(--bg-soft, var(--bg-alt))",
        border: `1px solid ${value ? "var(--cli-accent)" : "var(--line)"}`,
        position: "relative",
        transition: "all .2s ease",
      }}>
        <span style={{
          position: "absolute", top: 2, left: value ? 20 : 2,
          width: 20, height: 20, background: "#fff",
          borderRadius: "50%", boxShadow: "0 1px 3px rgba(0,0,0,.2)",
          transition: "left .2s ease",
        }}/>
      </span>
    </label>
  </div>
);

/* Action row : icône + label + flèche → ; last = sans border-bottom */
const ProfActionRow = ({ icon, iconTone = "cli", title, onClick, danger, last }) => {
  const tones = {
    cli:    { bg: "var(--cli-soft)",     fg: "var(--cli-ink)" },
    danger: { bg: "var(--red-soft, oklch(95% 0.05 25))", fg: "var(--red, oklch(48% 0.16 25))" },
  };
  const t = tones[iconTone] || tones.cli;
  return (
    <button onClick={onClick} style={{
      width: "100%", padding: "14px 20px",
      background: "transparent", border: "none",
      borderBottom: last ? "none" : "1px solid var(--line-2)",
      cursor: "pointer", fontFamily: "inherit", textAlign: "left",
      display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12,
      transition: "background .15s ease",
    }}
      onMouseEnter={e => { e.currentTarget.style.background = "var(--bg-alt)"; }}
      onMouseLeave={e => { e.currentTarget.style.background = "transparent"; }}>
      <span style={{ display: "flex", alignItems: "center", gap: 12 }}>
        <span style={{
          width: 32, height: 32, borderRadius: 9, flexShrink: 0,
          background: t.bg, color: t.fg,
          display: "inline-flex", alignItems: "center", justifyContent: "center",
          fontSize: 14,
        }}>{icon}</span>
        <span style={{
          fontWeight: 600, color: danger ? "var(--red, oklch(48% 0.16 25))" : "var(--ink)",
          fontSize: 14,
        }}>{title}</span>
      </span>
      <span style={{ color: danger ? "var(--red, oklch(48% 0.16 25))" : "var(--ink-4)", fontSize: 16 }}>→</span>
    </button>
  );
};

const EspaceField = ({ label, value, onChange, type = "text" }) => (
  <div style={{ marginBottom: 12 }}>
    <label style={{
      display: "block", marginBottom: 4,
      fontSize: 11, fontFamily: "var(--ff-text)",
      color: "var(--ink-4)", textTransform: "uppercase", letterSpacing: "0.05em", fontWeight: 540,
    }}>{label}</label>
    <input type={type} value={value || ""} onChange={e => onChange(e.target.value)}
      style={{
        width: "100%", padding: "10px 13px",
        background: "var(--bg-alt)", border: "1px solid var(--line)", borderRadius: 9,
        fontSize: 13.5, color: "var(--ink)", fontFamily: "inherit",
      }}/>
  </div>
);

const EspaceToggle = ({ label, hint, value, onChange }) => (
  <div onClick={onChange} style={{
    display: "flex", justifyContent: "space-between", alignItems: "center",
    padding: "11px 13px", background: "var(--bg-alt)", borderRadius: 10,
    marginBottom: 7, cursor: "pointer",
  }}>
    <div style={{ minWidth: 0 }}>
      <div style={{ fontSize: 13, fontWeight: 540 }}>{label}</div>
      {hint && <div style={{ fontSize: 11, color: "var(--ink-4)", marginTop: 1 }}>{hint}</div>}
    </div>
    <div style={{
      width: 36, height: 22, borderRadius: 999, flexShrink: 0,
      background: value ? "var(--cli-accent)" : "var(--line-strong)",
      position: "relative", transition: "background .2s ease",
    }}>
      <div style={{
        position: "absolute", top: 2,
        left: value ? 16 : 2,
        width: 18, height: 18, borderRadius: "50%", background: "#fff",
        boxShadow: "0 1px 3px rgba(0,0,0,0.2)",
        transition: "left .2s cubic-bezier(.22,1,.36,1)",
      }}/>
    </div>
  </div>
);

/* ====== Footer ====== */
const EspaceFooter = () => (
  <footer style={{
    padding: "26px 18px 36px", textAlign: "center",
    fontSize: 11.5, color: "var(--ink-4)",
    borderTop: "1px solid var(--line)", marginTop: 40,
    background: "var(--bg-section)",
  }}>
    Propulsé par <a href="/v2/" style={{ color: "var(--cli-ink)", fontWeight: 540 }}>ClientBase</a>
    · Vos données sont traitées conformément au RGPD.
  </footer>
);

/* ====== Login client (email + mot de passe, compte unique multi-pros) ====== */
const CLIENT_SESSION_KEY = "cb_client_v1";

const EspaceLogin = ({ initialMode = "login" }) => {
  const [mode, setMode] = React.useState(initialMode); // login | signup
  // Pré-remplissage de l'email si l'user vient d'être rejeté sur le login
  // pro avec un email client (stash dans sessionStorage par cbAuth.login).
  // On consomme le flag (remove) après lecture pour ne pas le re-utiliser.
  const initialEmail = (() => {
    try {
      const e = sessionStorage.getItem("cb_prefill_email");
      if (e) { sessionStorage.removeItem("cb_prefill_email"); return e; }
    } catch {}
    return "";
  })();
  const [form, setForm] = React.useState({
    email: initialEmail, password: "", firstName: "", lastName: "", phone: "",
  });
  const [error, setError] = React.useState(null);
  const [loading, setLoading] = React.useState(false);
  const [showPwd, setShowPwd] = React.useState(false);
  // Caps Lock detector pour l'input mot de passe en mode login (le mode
  // signup utilise window.PasswordField qui gère déjà sa propre détection).
  const [capsOn, setCapsOn] = React.useState(false);
  const [pwdFocused, setPwdFocused] = React.useState(false);
  const onPwdKey = (e) => {
    try { setCapsOn(e.getModifierState && e.getModifierState("CapsLock")); } catch {}
  };
  // Email destinataire après signup → affiche l'écran "Vérifiez votre boîte mail"
  // (même composant que côté Pro pour cohérence visuelle stricte).
  const [emailSentTo, setEmailSentTo] = React.useState(null);

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

  const submit = async (e) => {
    e.preventDefault();
    setError(null);
    if (!/^[^\s@]+@[^\s@]+\.[a-zA-Z]{2,}$/.test(form.email.trim())) {
      setError("Email invalide. Exemple : prenom@exemple.fr");
      return;
    }
    if (mode === "signup") {
      // Côté signup, on applique exactement la même grille de sécurité que
      // pour le compte pro (10 car + maj + min + chiffre + symbole) via
      // cbAuth.validatePassword pour cohérence avec la checklist affichée.
      const pwErr = (window.cbAuth && window.cbAuth.validatePassword)
        ? window.cbAuth.validatePassword(form.password)
        : (form.password.length < 10 ? "Mot de passe trop court." : null);
      if (pwErr) { setError(pwErr); return; }
      if (!form.firstName.trim() || !form.lastName.trim()) {
        setError("Votre prénom et nom sont requis.");
        return;
      }
    } else if (form.password.length < 6) {
      setError("Mot de passe trop court (6 caractères minimum).");
      return;
    }

    setLoading(true);

    // Auth client — passe par le même Supabase auth que le pro pour avoir
    // une vraie vérification email systématique. Le rôle est stocké en
    // user_metadata pour distinguer les comptes côté backend.
    if (window.cbSupabase) {
      try {
        if (mode === "signup") {
          const { data, error } = await window.cbSupabase.auth.signUp({
            email: form.email.trim().toLowerCase(),
            password: form.password,
            options: {
              data: {
                role: "client",
                first_name: form.firstName.trim(),
                last_name: form.lastName.trim(),
                phone: form.phone.trim(),
              },
            },
          });
          setLoading(false);
          if (error) { setError(error.message); return; }
          // Mémorise le rôle pour la nav vitrine post-logout.
          try { localStorage.setItem("cb_last_audience", "client"); } catch {}
          // Confirm email activé côté Supabase = pas de session immédiate.
          // On affiche l'écran "Vérifiez votre boîte mail".
          if (!data.session) { setEmailSentTo(form.email); return; }
          // Sinon (Confirm email OFF), session direct → on file vers l'espace démo.
          window.location.href = "/?espace=me";
          return;
        }
        // Login client : signOut préventif pour clear toute session zombie
        // (cas où le user a un onglet admin/pro ouvert ailleurs qui aurait
        // posé une session pro dans localStorage). signInWithPassword va
        // ensuite poser proprement la session client.
        try { await window.cbSupabase.auth.signOut(); } catch {}
        try { localStorage.removeItem("cb_client_v1"); } catch {}
        const { data, error } = await window.cbSupabase.auth.signInWithPassword({
          email: form.email.trim().toLowerCase(),
          password: form.password,
        });
        setLoading(false);
        if (error) { setError(error.message); return; }
        window.cbDebug && window.cbDebug.log("[client login] signIn OK · user.id:", data.user && data.user.id, "email:", data.user && data.user.email);
        // Gate role : un compte pro ne doit PAS pouvoir se connecter ici.
        // role === "client"       → autorisé (signup espace client + booking)
        // role === "pro"          → rejeté direct
        // role manquant (legacy)  → on vérifie via la DB si une ligne
        //   businesses existe pour cet user. Si oui = pro → rejette. Sinon
        //   = client legacy ou compte créé sans role explicite → autorise.
        //   Ça résout le cas des comptes créés via le parcours de réservation
        //   avant qu'on ait ajouté role:"client" au signUp.
        // Detection PRO basée UNIQUEMENT sur la table `businesses` (la
        // source de vérité). Le role dans user_metadata peut être absent,
        // périmé, ou mal posé — on ne s'en sert plus comme gate principal.
        //   - businesses count > 0 → c'est un PRO → rejet
        //   - businesses count = 0 → c'est un client (legacy ou signup
        //     booking) → autorise, peu importe ce que dit metadata.
        // Si la requête échoue (RLS bizarre, réseau), on autorise plutôt
        // que de bloquer un vrai client.
        const _PRO_LOGIN_ERROR = "Cet email correspond à un compte PROFESSIONNEL. Connectez-vous via l'espace pro (lien en bas de page), ou utilisez une autre adresse email pour créer un compte client.";
        let isPro = false;
        try {
          const { count } = await window.cbSupabase
            .from("businesses")
            .select("user_id", { count: "exact", head: true })
            .eq("user_id", data.user.id);
          isPro = (count || 0) > 0;
          window.cbDebug && window.cbDebug.log("[client login] user.id:", data.user.id, "businesses count:", count, "→ isPro:", isPro);
        } catch (e) {
          window.cbDebug && window.cbDebug.warn("[client login] businesses check failed:", e);
          isPro = false;
        }
        if (isPro) {
          try { await window.cbSupabase.auth.signOut(); } catch {}
          // Cleanup explicite pour bypasser la race avec _hydrateFromSupabase
          // (cf. fix symétrique dans app-state.jsx cbAuth.login).
          try {
            localStorage.removeItem("cb_client_v1");
            localStorage.removeItem("cb_last_audience");
          } catch {}
          // Stash l'email pour pré-remplir le formulaire pro après redirect.
          try { sessionStorage.setItem("cb_prefill_email", form.email.trim().toLowerCase()); } catch {}
          // Erreur enrichie (objet) avec CTA "Aller au formulaire pro".
          setError({
            kind: "wrongAudience",
            text: "Cet email correspond à un compte PROFESSIONNEL, pas client.",
            ctaLabel: "Continuer vers l'espace pro →",
            ctaHref: "/connexion",
          });
          return;
        }
        try {
          localStorage.setItem(CLIENT_SESSION_KEY, JSON.stringify({
            email: data.user.email,
            firstName: data.user.user_metadata?.first_name || "",
            lastName: data.user.user_metadata?.last_name || "",
            loginAt: Date.now(),
          }));
          localStorage.setItem("cb_last_audience", "client");
        } catch {}
        window.location.href = "/?espace=me";
        return;
      } catch (e) {
        setLoading(false);
        setError("Erreur réseau. Réessayez.");
        return;
      }
    }

    // Fallback (pas de Supabase) — flow démo local
    await new Promise(r => setTimeout(r, 300));
    try {
      localStorage.setItem(CLIENT_SESSION_KEY, JSON.stringify({
        email: form.email.trim().toLowerCase(),
        firstName: form.firstName || "Marie",
        lastName: form.lastName || "Dupont",
        loginAt: Date.now(),
      }));
    } catch {}
    if (mode === "signup") {
      setEmailSentTo(form.email);
      setLoading(false);
      return;
    }
    window.location.href = "/?espace=me";
  };

  // Écran "Vérifiez votre boîte mail" partagé avec le compte Pro
  if (emailSentTo) {
    return (
      <AuthEmailSent
        email={emailSentTo}
        role="client"
        onEdit={() => { setEmailSentTo(null); setMode("signup"); }}
        onResend={async () => {
          await new Promise(r => setTimeout(r, 400));
        }}
      />
    );
  }

  const isLogin = mode === "login";

  // Badge identifie le rôle (Client), symétrique du badge "Votre compte pro"
  // côté LoginPage pour cohérence visuelle entre les flows.
  const eyebrow = "Votre compte client";
  const title = isLogin ? "Bon retour." : "Créer votre compte client.";
  const subtitle = isLogin
    ? "Un seul compte pour retrouver vos RDV chez tous vos pros ClientBase."
    : "Gratuit. Un compte qui fonctionne chez tous vos pros, sans en créer un par salon.";
  const submitLabel = loading
    ? (isLogin ? "Connexion…" : "Création…")
    : (isLogin ? "Se connecter" : "Créer mon compte");
  const switchLink = isLogin
    ? { text: "Pas encore de compte ?", label: "Créer un compte", action: () => { setMode("signup"); setError(null); } }
    : { text: "Déjà un compte ?", label: "Se connecter", action: () => { setMode("login"); setError(null); } };

  // Bottom slot harmonisé pro/client : un seul bouton démo + promises
  // ✓. On vire l'ancien encart « Aperçu démo » qui faisait doublon —
  // le bouton full-width « Voir le compte démo » avec point pulsant le
  // remplace, identique côté pro et côté client. URL absolue vers
  // /v2/?espace=demo (pour ouvrir l'espace cliente démo).
  const SharedBottomSlot = window.CbSignupBottomSlot || null;
  const bottomSlot = SharedBottomSlot
    ? <SharedBottomSlot
        tone="client"
        demoLabel="Voir le compte démo"
        demoHref="/v2/?espace=demo"
      />
    : null;

  // ===== Avatar live + progression + encouragement (signup uniquement) =====
  // Miroir exact du topSlot de la signup pro, adapté au compte client :
  // les 4 champs requis sont firstName, lastName, email, password.
  // L'avatar utilise les initiales du prénom + nom, le dégradé varie selon
  // une teinte dérivée du prénom (touche fun) qui converge vers cli-accent.
  const cliInitials = ((form.firstName || "").trim()[0] || "")
    + ((form.lastName || "").trim()[0] || "");
  const cliInitialsUpper = cliInitials.toUpperCase();
  const cliHue = (() => {
    let h = 0;
    for (const c of (form.firstName + form.lastName)) h = (h * 31 + c.charCodeAt(0)) >>> 0;
    return 260 + (h % 60); // 260-320 : nuances violet/indigo, harmonisé client
  })();
  const cliIsEmailValid = /^[^\s@]+@[^\s@]+\.[a-zA-Z]{2,}$/.test(form.email.trim());
  const cliIsPwdValid = (window.cbAuth && window.cbAuth.validatePassword)
    ? !window.cbAuth.validatePassword(form.password)
    : form.password.length >= 10;
  const cliFilledCount = (
    (form.firstName.trim() ? 1 : 0) +
    (form.lastName.trim()  ? 1 : 0) +
    (cliIsEmailValid       ? 1 : 0) +
    (cliIsPwdValid         ? 1 : 0)
  );
  const cliProgressPct = (cliFilledCount / 4) * 100;
  const cliEncouragement =
    cliFilledCount === 0 ? "Allez, c'est parti, ça prend 30 secondes."
    : cliFilledCount === 4 ? "Tout est prêt 🎉 Cliquez pour créer votre compte."
    : cliFilledCount >= 2 ? `Plus que ${4 - cliFilledCount} champ${4 - cliFilledCount > 1 ? "s" : ""} !`
    : "On continue…";

  // Bind local capitalisé pour que JSX traite l'identifiant comme un composant
  // (sinon `<window.PasswordField>` est ambigu pour le parser JSX).
  const SharedPasswordField   = window.PasswordField   || null;
  const SharedEmailSuggestPill = window.EmailSuggestPill || null;

  const topSlot = !isLogin ? (
    <div style={{
      marginTop: 22, padding: "16px 18px",
      background: "var(--surface)", border: "1px solid var(--line)",
      borderRadius: 14, display: "flex", alignItems: "center", gap: 14,
      animation: "cbAuthFadeUp .5s cubic-bezier(.22,1,.36,1) both",
    }}>
      <div style={{
        width: 52, height: 52, borderRadius: 50, flexShrink: 0,
        background: cliInitialsUpper
          ? `linear-gradient(135deg, oklch(78% 0.14 ${cliHue}), oklch(60% 0.20 ${(cliHue + 25) % 360}))`
          : "var(--bg-alt)",
        color: cliInitialsUpper ? "white" : "var(--ink-4)",
        display: "flex", alignItems: "center", justifyContent: "center",
        fontFamily: "var(--ff-display)", fontWeight: 600, fontSize: 20,
        boxShadow: cliInitialsUpper ? "0 6px 16px -6px var(--cli-accent)" : "none",
        transition: "background .35s ease, box-shadow .35s ease",
      }}>
        {cliInitialsUpper || "?"}
      </div>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontSize: 13, color: "var(--ink-3)", fontWeight: 500, marginBottom: 6 }}>
          {cliEncouragement}
        </div>
        <div style={{
          height: 5, background: "var(--bg-alt)",
          borderRadius: 999, overflow: "hidden",
        }}>
          <div style={{
            width: `${cliProgressPct}%`, height: "100%",
            background: "linear-gradient(90deg, var(--cli-accent), oklch(60% 0.22 300))",
            borderRadius: 999,
            transition: "width .35s cubic-bezier(.22,1,.36,1)",
          }}/>
        </div>
        {/* Chips de champs : remplis = ✓ sage, manquants pulsent doucement
            pour guider l'œil. Miroir du signup pro pour cohérence stricte. */}
        <div style={{
          marginTop: 6,
          display: "flex", flexWrap: "wrap", gap: 5,
        }}>
          {[
            { ok: !!form.firstName.trim(), label: "Prénom" },
            { ok: !!form.lastName.trim(),  label: "Nom" },
            { ok: cliIsEmailValid,         label: "Email" },
            { ok: cliIsPwdValid,           label: "Mot de passe" },
          ].map((f, i) => (
            <span key={f.label} style={{
              display: "inline-flex", alignItems: "center", gap: 4,
              padding: "2px 7px 2px 5px",
              background: f.ok ? "var(--sage-soft)" : "var(--bg-alt)",
              border: `1px solid ${f.ok ? "color-mix(in oklab, var(--sage) 30%, transparent)" : "var(--line)"}`,
              borderRadius: 999,
              fontSize: 10.5, fontWeight: 540,
              color: f.ok ? "var(--sage-ink, oklch(38% 0.10 160))" : "var(--ink-4)",
              animation: f.ok
                ? "none"
                : `cbAuthFieldPulse 2s ease-in-out ${i * 0.18}s infinite`,
              transition: "background .25s ease, color .25s ease, border-color .25s ease",
            }}>
              <span aria-hidden style={{
                width: 11, height: 11, borderRadius: "50%", flexShrink: 0,
                background: f.ok ? "var(--sage)" : "transparent",
                border: f.ok ? "none" : "1.4px solid var(--line-strong)",
                color: "#fff",
                display: "inline-flex", alignItems: "center", justifyContent: "center",
              }}>
                {f.ok && (
                  <svg width="7" height="7" viewBox="0 0 24 24" fill="none" stroke="currentColor"
                    strokeWidth="4" strokeLinecap="round" strokeLinejoin="round">
                    <path d="M5 12l4 4 10-10"/>
                  </svg>
                )}
              </span>
              {f.label}
            </span>
          ))}
        </div>
      </div>
    </div>
  ) : null;

  return (
    <AuthShell
      role="client"
      eyebrow={eyebrow}
      title={title}
      subtitle={subtitle}
      error={error}
      onSubmit={submit}
      submitting={loading}
      submitLabel={submitLabel}
      switchLink={switchLink}
      topSlot={topSlot}
      bottomSlot={bottomSlot}
    >
      {!isLogin && (
        <div className="cb-auth-field" style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10 }}>
          <div>
            <label style={espaceUnifiedLabelStyle}>Prénom</label>
            <input type="text" value={form.firstName}
              onChange={e => update("firstName", e.target.value)}
              placeholder="Marie"
              autoComplete="given-name"
              style={espaceUnifiedInputStyle}/>
          </div>
          <div>
            <label style={espaceUnifiedLabelStyle}>Nom</label>
            <input type="text" value={form.lastName}
              onChange={e => update("lastName", e.target.value)}
              placeholder="Dupont"
              autoComplete="family-name"
              style={espaceUnifiedInputStyle}/>
          </div>
        </div>
      )}

      <div className="cb-auth-field" style={{ position: "relative" }}>
        <label style={espaceUnifiedLabelStyle}>Email</label>
        <input type="email" value={form.email}
          onChange={e => update("email", e.target.value)}
          onKeyDown={e => {
            // Tab accepte la suggestion d'auto-complétion email côté client
            if (e.key === "Tab" && !e.shiftKey && window.cbSuggestEmail) {
              const s = window.cbSuggestEmail(form.email);
              if (s && s !== form.email) {
                e.preventDefault();
                update("email", s);
              }
            }
          }}
          placeholder="prenom@exemple.fr"
          autoComplete="email"
          style={espaceUnifiedInputStyle}/>
        {form.email.length > 3 && (
          <span style={{
            position: "absolute", right: 14, top: 36,
            fontSize: 14,
            color: cliIsEmailValid ? "var(--sage)" : "var(--ink-4)",
            transition: "color .2s ease",
          }}>
            {cliIsEmailValid ? "✓" : "…"}
          </span>
        )}
        {SharedEmailSuggestPill && (
          <SharedEmailSuggestPill
            value={form.email}
            onAccept={s => update("email", s)}
            tone="var(--cli-accent)"
          />
        )}
      </div>

      <div className="cb-auth-field">
        <label style={espaceUnifiedLabelStyle}>Mot de passe</label>
        {/* Sur signup, on délègue à window.PasswordField (défini dans
            pages-b.jsx) pour réutiliser exactement la même checklist animée
            + jauge 5 segments que la signup pro. Sur login, on garde le
            simple input historique pour ne pas alourdir l'écran. */}
        {!isLogin && SharedPasswordField ? (
          <SharedPasswordField
            value={form.password}
            onChange={v => update("password", v)}
            placeholder="10 caractères minimum"
            autoComplete="new-password"
            showMeter={true}
          />
        ) : (
          <div>
            <div style={{ position: "relative" }}>
              <input type={showPwd ? "text" : "password"} value={form.password}
                onChange={e => update("password", e.target.value)}
                onKeyDown={onPwdKey}
                onKeyUp={onPwdKey}
                onFocus={() => setPwdFocused(true)}
                onBlur={() => setPwdFocused(false)}
                placeholder={isLogin ? "Votre mot de passe" : "10 caractères minimum"}
                autoComplete={isLogin ? "current-password" : "new-password"}
                style={{ ...espaceUnifiedInputStyle, paddingRight: 44 }}/>
              <button type="button" onClick={() => setShowPwd(v => !v)}
                aria-label={showPwd ? "Masquer" : "Afficher"}
                style={{
                  position: "absolute", right: 6, top: "50%", transform: "translateY(-50%)",
                  width: 34, height: 34, background: "transparent", border: "none",
                  cursor: "pointer", color: "var(--ink-4)", borderRadius: 6,
                  display: "inline-flex", alignItems: "center", justifyContent: "center",
                }}>
                <Icon name={showPwd ? "eyeOff" : "eye"} size={16}/>
              </button>
            </div>
            {capsOn && pwdFocused && (
              <div style={{
                marginTop: 6, padding: "5px 10px",
                display: "inline-flex", alignItems: "center", gap: 6,
                background: "oklch(97% 0.04 60)", color: "oklch(40% 0.16 60)",
                border: "1px solid oklch(86% 0.10 60)", borderRadius: 8,
                fontSize: 11.5, fontWeight: 540,
                animation: "cbAuthFadeUp .22s cubic-bezier(.22,1,.36,1) both",
              }}>
                <span aria-hidden style={{ fontSize: 13 }}>⇪</span>
                Caps Lock est activé
              </div>
            )}
          </div>
        )}
        {isLogin && (
          <div style={{ marginTop: 8, textAlign: "right" }}>
            <a href="#" onClick={e => { e.preventDefault(); alert("Réinitialisation par email, disponible bientôt."); }}
              style={{ fontSize: 12.5, color: "var(--cli-accent)", fontWeight: 520, textDecoration: "none" }}>
              Mot de passe oublié ?
            </a>
          </div>
        )}
      </div>

      {!isLogin && (
        <div className="cb-auth-field">
          <label style={espaceUnifiedLabelStyle}>
            Téléphone <span style={{ color: "var(--ink-4)", fontWeight: 500, textTransform: "none", letterSpacing: 0 }}>(optionnel)</span>
          </label>
          <input type="tel" value={form.phone}
            onChange={e => update("phone", e.target.value)}
            placeholder="06 12 34 56 78"
            autoComplete="tel"
            style={espaceUnifiedInputStyle}/>
        </div>
      )}
    </AuthShell>
  );
};

// Styles inputs réutilisés par EspaceLogin, alignés sur ceux du compte Pro
// pour une cohérence visuelle parfaite entre les deux flux.
const espaceUnifiedInputStyle = {
  width: "100%", padding: "11px 14px", fontSize: 16, fontFamily: "inherit",
  background: "var(--bg)", border: "1px solid var(--line-strong)", borderRadius: 9,
  color: "var(--ink)", outline: "none",
  transition: "border-color .15s, box-shadow .15s",
};
const espaceUnifiedLabelStyle = {
  display: "block", fontSize: 13, fontWeight: 500,
  color: "var(--ink-2)", marginBottom: 6,
};

const espaceInputStyle = {
  width: "100%", padding: "11px 14px",
  background: "var(--bg-alt)", border: "1px solid var(--line)",
  borderRadius: 11, fontSize: 14, fontFamily: "inherit", color: "var(--ink)",
};
const espaceLabelStyle = {
  display: "block", marginBottom: 5,
  fontSize: 11, color: "var(--ink-4)",
  textTransform: "uppercase", letterSpacing: "0.05em", fontWeight: 540,
};

/* ====== 404 ====== */
const EspaceNotFound = () => (
  <div style={{
    minHeight: "100vh", display: "flex", alignItems: "center", justifyContent: "center",
    background: "var(--bg)", padding: 24, textAlign: "center",
  }}>
    <div>
      <div style={{ fontSize: 48, marginBottom: 12 }}>🤔</div>
      <h2 style={{ margin: "0 0 6px", fontFamily: "var(--ff-display)", fontSize: 20 }}>Espace introuvable</h2>
      <p style={{ margin: "0 0 14px", fontSize: 13.5, color: "var(--ink-3)" }}>
        Le lien que vous avez utilisé n'est plus valide ou a expiré.
      </p>
      <a href="/v2/" className="btn btn-ghost">Retour à l'accueil</a>
    </div>
  </div>
);

// Helper récup token URL, retourne :
//   - null/undefined : pas d'espace activé
//   - "" (string vide) : espace activé sans token → page de login
//   - "<token>" : token concret (incluant "demo")
const getEspaceTokenFromURL = () => {
  try {
    const u = new URL(window.location.href);
    if (!u.searchParams.has("espace") && u.pathname !== "/v2/espace") return null;
    return u.searchParams.get("espace") || "";
  } catch { return null; }
};
window.getEspaceTokenFromURL = getEspaceTokenFromURL;
// Exports explicites : permet à index.html d'utiliser EspaceLogin dans une
// page React standard (routes /connexion-client et /inscription-client) sans
// passer par window.location.href (= reload de page entier).
window.EspacePage = EspacePage;
window.EspaceLogin = EspaceLogin;
