/* ClientBase — Public booking page (/?book=<slug>) */

// 6 jours glissants à partir d'aujourd'hui (i = 0..5)
const PICKER_DAYS = 6;
const _bookingDateForI = (i) => cbDateForOffset(cbTodayOffset() + i);
const dayLabelOf = (i) => CB_FR_DAYS_SHORT[_bookingDateForI(i).getDay()];
const dayDateOf  = (i) => _bookingDateForI(i).getDate();
const dayMonthOf = (i) => CB_FR_MONTHS_SHORT[_bookingDateForI(i).getMonth()];
const isoIdxOf   = (i) => { const dow = _bookingDateForI(i).getDay(); return dow === 0 ? 7 : dow; };
// Compatibilité avec les composants existants qui appelaient DAY_LABELS_FULL[i]
const DAY_LABELS_FULL = Array.from({ length: PICKER_DAYS }, (_, i) => dayLabelOf(i));
const DAY_DATES       = Array.from({ length: PICKER_DAYS }, (_, i) => dayDateOf(i));
const BOOKING_MONTH   = CB_FR_MONTHS_LONG[new Date().getMonth()];

const fmtHourBooking = (h) => {
  const hh = Math.floor(h);
  const mm = Math.round((h - hh) * 60);
  return `${hh}h${mm.toString().padStart(2, "0")}`;
};

// Resolve schedule for a given ISO weekday (1..7) with legacy fallback
const resolveDayHours = (bs, isoIdx) => {
  if (bs.schedule && bs.schedule[isoIdx]) {
    const d = bs.schedule[isoIdx];
    return {
      open: !!d.open,
      start: d.start, end: d.end,
      break: !!d.break,
      breakStart: d.breakStart != null ? d.breakStart : 12.5,
      breakEnd:   d.breakEnd   != null ? d.breakEnd   : 13.5,
    };
  }
  const daysOpen = bs.daysOpen || [1, 2, 3, 4, 5, 6];
  return {
    open: daysOpen.includes(isoIdx),
    start: (bs.hours && bs.hours.start) || 9,
    end: (bs.hours && bs.hours.end) || 18,
    break: false, breakStart: 12.5, breakEnd: 13.5,
  };
};

// Map 0..5 (Mon..Sat demo) to ISO 1..7 and to a demo date in Apr 2026
const demoDateFor = (i) => cbYmd(_bookingDateForI(i));

const isInVacation = (bs, ymd) => {
  const vacs = bs.vacations || [];
  return vacs.some(v => ymd >= v.start && ymd <= v.end);
};

/* Given a day index (0-5 = today + i) and current data, return ALL slot
   start hours of that day, each tagged { h, taken: boolean }. Taken slots
   are kept in the list so the UI can render them disabled (barré + grisé). */
const generateAvailableSlots = (day, data, serviceId) => {
  const bs = data.bookingSettings || {};
  const service = data.services.find(s => s.id === serviceId);
  if (!service) return [];
  const isoIdx = isoIdxOf(day);
  const dayHours = resolveDayHours(bs, isoIdx);
  if (!dayHours.open) return [];
  const ymd = demoDateFor(day);
  if (isInVacation(bs, ymd)) return [];
  const step = (bs.slotDuration || 30) / 60;
  const slots = [];

  const hasBreak = dayHours.break && dayHours.breakStart != null && dayHours.breakEnd != null
                   && dayHours.breakEnd > dayHours.breakStart;
  const targetOffset = cbTodayOffset() + day;

  for (let h = dayHours.start; h + service.duration <= dayHours.end + 0.001; h += step) {
    const sStart = h;
    const sEnd = h + service.duration;
    if (hasBreak && sStart < dayHours.breakEnd && sEnd > dayHours.breakStart) continue;
    const taken = data.appointments.some(a => {
      if (a.day !== targetOffset) return false;
      return sStart < a.h + a.d && sEnd > a.h;
    });
    slots.push({ h: +h.toFixed(2), taken });
  }
  return slots;
};

const BookingPage = ({ slug }) => {
  // Bloque le zoom auto iOS pendant qu'on est sur la page de réservation —
  // restauré au démontage pour ne pas affecter le reste du site.
  React.useEffect(() => {
    const meta = document.querySelector('meta[name="viewport"]');
    const original = meta ? meta.getAttribute('content') : null;
    if (meta) {
      meta.setAttribute('content',
        'width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover');
    }
    return () => {
      if (meta && original) meta.setAttribute('content', original);
    };
  }, []);

  // État de chargement : null=loading, {pro,data}=ok, false=not found
  const [loaded, setLoaded] = React.useState(null);
  const [data, setData] = React.useState(null);
  const [pro, setPro] = React.useState(null);

  const [step, setStep] = React.useState(1);
  const [serviceId, setServiceId] = React.useState("");
  const [day, setDay] = React.useState(null);
  const [hour, setHour] = React.useState(null);
  const [form, setForm] = React.useState({
    firstName: "", lastName: "", email: "", phone: "", social: "", noSocial: false, notes: "", rgpd: false,
  });
  const [error, setError] = React.useState(null);
  const [submitting, setSubmitting] = React.useState(false);
  const [confirmed, setConfirmed] = React.useState(null);

  // Chargement depuis Supabase (RPC publique) ; fallback localStorage en bêta.
  React.useEffect(() => {
    let cancelled = false;
    (async () => {
      // 1) Cloud
      if (window.cbSupabase) {
        try {
          const { data: rpc, error } = await window.cbSupabase.rpc("get_public_booking", { p_slug: slug });
          if (cancelled) return;
          if (!error && rpc && rpc.business) {
            setPro({
              businessName: rpc.business.name,
              ownerName:    rpc.business.owner,
              bookingSlug:  rpc.business.booking_slug,
              phone:        rpc.business.phone,
              contactEmail: rpc.business.contact_email,
              city:         rpc.business.city,
              avatar:       rpc.business.avatar_data_url || "",
              hue:          rpc.business.hue || 30,
              emailVerified: !!rpc.email_verified,
            });
            setData({
              services: (rpc.services || []).map(s => ({
                id: s.id, name: s.name,
                price: Number(s.price || 0),
                duration: Number(s.duration || 1),
                description: s.description || "",
                active: s.active !== false,
              })),
              appointments: (rpc.appointments || []).map(a => ({
                id: a.id, day: Number(a.day), h: Number(a.h), d: Number(a.d),
              })),
              bookingSettings: {
                ...(rpc.settings ? {
                  enabled: rpc.settings.enabled,
                  slotDuration: rpc.settings.slot_duration,
                  leadTimeMinutes: rpc.settings.lead_time_minutes,
                  schedule: rpc.settings.schedule || {},
                  preferredSocial: rpc.settings.preferred_social || "instagram",
                } : SEED_DATA.bookingSettings),
                vacations: (rpc.vacations || []).map(v => ({
                  id: v.id, start: v.starts_on, end: v.ends_on, reason: v.reason,
                })),
              },
            });
            setLoaded(true);
            return;
          }
        } catch (e) { console.error("[BookingPage] RPC", e); }
      }
      // 2) Fallback localStorage
      const localPro = cbAuth.findUserBySlug(slug);
      if (localPro) {
        setPro(localPro);
        try {
          const saved = localStorage.getItem(CB_LS_KEY);
          setData(saved ? { ...SEED_DATA, ...JSON.parse(saved) } : SEED_DATA);
        } catch { setData(SEED_DATA); }
        setLoaded(true);
      } else {
        setLoaded(false);
      }
    })();
    return () => { cancelled = true; };
  }, [slug]);

  if (loaded === null) {
    return (
      <div style={{
        minHeight: "100vh", display: "flex", alignItems: "center", justifyContent: "center",
        background: "var(--bg)", color: "var(--ink-3)", fontSize: 13.5,
      }}>
        Chargement…
      </div>
    );
  }
  if (loaded === false) return <BookingNotFound slug={slug}/>;

  const bs = (data && data.bookingSettings) || SEED_DATA.bookingSettings;
  const activeServices = (data && data.services ? data.services : []).filter(s => {
    if (s.active === false) return false; // per-service toggle (nouvelle règle)
    if (bs.activeServiceIds && !bs.activeServiceIds.includes(s.id)) return false; // ancien filtre opt-in
    return true;
  });
  const service = activeServices.find(s => s.id === serviceId);

  const submit = async () => {
    setError(null);
    if (!form.firstName.trim() || !form.lastName.trim()) {
      return setError("Votre prénom et nom sont requis.");
    }
    if (!/^[^\s@]+@[^\s@]+\.[a-zA-Z]{2,}$/.test(form.email.trim())) {
      return setError("Email invalide. Exemple : prenom@exemple.fr");
    }
    if (!form.rgpd) return setError("Vous devez accepter le traitement de vos données.");

    setSubmitting(true);
    try {
      // Cloud path : RPC publique
      if (window.cbSupabase) {
        const { data: rpc, error } = await window.cbSupabase.rpc("public_book_appointment", {
          p_slug: slug,
          p_service_id: serviceId,
          p_day: cbTodayOffset() + day,  // offset absolu, stable
          p_h: hour,
          p_first: form.firstName,
          p_last: form.lastName,
          p_email: form.email.trim(),
          p_phone: form.phone.trim(),
          p_notes: form.notes.trim(),
          p_social: form.noSocial ? "" : form.social.trim(),
        });
        if (error) {
          setError(_prettifyBookingError(error.message));
          setSubmitting(false);
          return;
        }
        // Le créneau vient d'être pris : on l'injecte dans le state local
        // pour que d'éventuels nouveaux RDV pris dans la même session
        // (pour soi ou un proche) ne puissent pas réutiliser ce slot.
        setData(d => ({
          ...d,
          appointments: [
            ...(d.appointments || []),
            { id: rpc.appointment_id, day: cbTodayOffset() + day, h: hour, d: service.duration },
          ],
        }));
        setConfirmed({
          clientId: rpc.client_id, appointmentId: rpc.appointment_id,
          day, hour, service,
        });
        setStep(4);
        setSubmitting(false);
        return;
      }

      // Fallback localStorage (bêta locale)
      const { nextData, clientId, appointmentId } = commitBooking(data, {
        serviceId, day, h: hour,
        firstName: form.firstName, lastName: form.lastName,
        email: form.email, phone: form.phone, notes: form.notes,
      });
      try { localStorage.setItem(CB_LS_KEY, JSON.stringify(nextData)); }
      catch (e) { /* ignore */ }
      setData(nextData);
      setConfirmed({ clientId, appointmentId, day, hour, service });
      setStep(4);
    } finally {
      setSubmitting(false);
    }
  };

  return (
    <div style={{
      minHeight: "100vh",
      background: "var(--bg)",
      display: "flex", flexDirection: "column",
    }}>
      <style>{`
        /* Anti-zoom iOS : tous les champs >=16px */
        .cb-booking input, .cb-booking textarea, .cb-booking select { font-size: 16px !important; }
        @media (max-width: 480px) {
          .cb-book-name-row { grid-template-columns: 1fr !important; }
        }
      `}</style>
      <div className="cb-booking" style={{ display: "contents" }}>
      <BookingHeader pro={pro}/>

      <main style={{ flex: 1, padding: "32px 16px 72px" }}>
        <div style={{ maxWidth: 640, margin: "0 auto" }}>
          {step !== 4 && <BookingStepper step={step}/>}

          {step === 1 && (
            <>
              <StepService
                pro={pro}
                services={activeServices}
                serviceId={serviceId}
                setServiceId={setServiceId}
                onNext={() => setStep(2)}
              />
              <div style={{ marginTop: 28 }}>
                <CancellationNotice pro={pro} data={data}/>
              </div>
            </>
          )}

          {step === 2 && service && (
            <StepSlot
              service={service}
              data={data}
              bookingSettings={bs}
              day={day} setDay={setDay}
              hour={hour} setHour={setHour}
              onBack={() => setStep(1)}
              onNext={() => setStep(3)}
            />
          )}

          {step === 3 && service && day != null && hour != null && (
            <>
              <StepInfo
                pro={pro}
                service={service} day={day} hour={hour}
                preferredSocial={(bs && bs.preferredSocial) || "instagram"}
                form={form} setForm={setForm}
                error={error}
                submitting={submitting}
                onBack={() => setStep(2)}
                onSubmit={submit}
              />
            </>
          )}

          {step === 4 && confirmed && (
            <>
              <StepConfirmed
                pro={pro}
                confirmed={confirmed}
                onRestart={() => {
                  setConfirmed(null);
                  setServiceId("");
                  setDay(null);
                  setHour(null);
                  setForm({ firstName: "", lastName: "", email: "", phone: "", notes: "", rgpd: false });
                  setStep(1);
                }}
              />
              <div style={{ marginTop: 24 }}>
                <CancellationNotice pro={pro} data={data}/>
              </div>
            </>
          )}
        </div>
      </main>

      <BookingFooter pro={pro}/>
      </div>
    </div>
  );
};

/* ============ Header / Footer ============ */
const BookingHeader = ({ pro }) => (
  <header style={{
    padding: "20px 16px",
    borderBottom: "1px solid var(--line)",
    background: "var(--surface)",
  }}>
    <div style={{ maxWidth: 640, margin: "0 auto", display: "flex", alignItems: "center", gap: 12 }}>
      {pro.avatar ? (
        <img src={pro.avatar} alt={pro.ownerName || ""} style={{
          width: 44, height: 44, borderRadius: 50, objectFit: "cover", flexShrink: 0,
          boxShadow: "var(--sh-1)",
        }}/>
      ) : (
        <div style={{
          width: 44, height: 44, borderRadius: 50,
          background: `linear-gradient(135deg, oklch(80% 0.12 ${pro.hue || 30}), oklch(68% 0.16 ${((pro.hue || 30)+40)%360}))`,
          color: "white", display: "flex", alignItems: "center", justifyContent: "center",
          fontWeight: 600, fontSize: 16, fontFamily: "var(--ff-display)",
          boxShadow: `0 4px 10px -4px oklch(70% 0.14 ${pro.hue || 30})`,
        }}>
          {(pro.ownerName || pro.businessName || "CB").split(" ").map(x => x[0]).slice(0, 2).join("").toUpperCase()}
        </div>
      )}
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{
          fontFamily: "var(--ff-display)", fontSize: 17, fontWeight: 580, letterSpacing: "-0.02em",
          display: "inline-flex", alignItems: "center", gap: 6,
        }}>
          <span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{pro.businessName}</span>
          {pro.emailVerified && (
            <svg width={18} height={18} viewBox="0 0 24 24" style={{ flexShrink: 0 }}
                 role="img" aria-label="Compte vérifié par ClientBase">
              <title>Compte vérifié par ClientBase</title>
              <path fill="var(--accent)" d="M22.25 12L20.86 10.41L21.43 8.36L19.39 7.74L18.95 5.66L16.83 5.5L15.69 3.71L13.66 4.47L12 3.16L10.34 4.47L8.31 3.71L7.17 5.5L5.05 5.66L4.61 7.74L2.57 8.36L3.14 10.41L1.75 12L3.14 13.59L2.57 15.64L4.61 16.26L5.05 18.34L7.17 18.5L8.31 20.29L10.34 19.53L12 20.84L13.66 19.53L15.69 20.29L16.83 18.5L18.95 18.34L19.39 16.26L21.43 15.64L20.86 13.59L22.25 12Z"/>
              <path d="M8.5 12L11 14.5L15.5 10" stroke="white" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" fill="none"/>
            </svg>
          )}
        </div>
        <div style={{ fontSize: 12, color: "var(--ink-4)" }}>
          Prise de rendez-vous en ligne
        </div>
      </div>
      <a href="/" style={{
        fontSize: 12, color: "var(--ink-4)",
        textDecoration: "none", display: "inline-flex", alignItems: "center", gap: 4,
      }}>
        <Logo size={18} withWordmark={false}/>
      </a>
    </div>
  </header>
);

const BookingFooter = ({ pro }) => (
  <footer style={{
    padding: "20px 16px",
    borderTop: "1px solid var(--line)",
    background: "var(--bg-section)",
    fontSize: 12, color: "var(--ink-4)",
    textAlign: "center",
  }}>
    Propulsé par <a href="/" style={{ color: "var(--accent-ink)", fontWeight: 520 }}>ClientBase</a> · vos données sont traitées conformément au RGPD.
  </footer>
);

/* ============ Stepper indicator ============ */
const BookingStepper = ({ step }) => {
  const steps = ["Prestation", "Créneau", "Vos infos"];
  return (
    <div style={{
      display: "flex", gap: 6, alignItems: "center", justifyContent: "center",
      marginBottom: 24, fontSize: 12.5,
    }}>
      {steps.map((label, i) => {
        const n = i + 1;
        const done = step > n;
        const active = step === n;
        return (
          <React.Fragment key={n}>
            <div style={{
              display: "inline-flex", alignItems: "center", gap: 6,
              padding: "4px 10px 4px 6px",
              borderRadius: 999,
              background: active ? "var(--accent-soft)" : "transparent",
              color: active ? "var(--accent-ink)" : done ? "var(--sage)" : "var(--ink-4)",
              fontWeight: active ? 600 : 500,
            }}>
              <span style={{
                width: 20, height: 20, borderRadius: 50,
                background: active ? "var(--accent)" : done ? "var(--sage)" : "var(--line-strong)",
                color: "white",
                display: "flex", alignItems: "center", justifyContent: "center",
                fontSize: 11, fontWeight: 600,
              }}>
                {done ? <Icon name="check" size={10} stroke={3}/> : n}
              </span>
              <span style={{ display: "none" }} className="cb-show-sm">{label}</span>
              <span className="cb-hide-sm">{label}</span>
            </div>
            {i < steps.length - 1 && (
              <span aria-hidden style={{
                width: 14, height: 1,
                background: step > n ? "var(--sage)" : "var(--line-strong)",
              }}/>
            )}
          </React.Fragment>
        );
      })}
    </div>
  );
};

/* ============ Step 1 — Choose service ============ */
const StepService = ({ pro, services, serviceId, setServiceId, onNext }) => (
  <div>
    <h1 style={{
      fontSize: "clamp(26px, 4vw, 36px)",
      letterSpacing: "-0.03em", marginBottom: 6,
    }}>
      Prenez rendez-vous avec <span style={{ color: "var(--accent)" }}>{pro.businessName}</span>.
    </h1>
    <p style={{ fontSize: 15, color: "var(--ink-3)", marginBottom: 24 }}>
      Choisissez la prestation que vous souhaitez.
    </p>

    <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
      {services.map(s => {
        const active = s.id === serviceId;
        return (
          <button key={s.id} onClick={() => setServiceId(s.id)} style={{
            padding: "14px 16px",
            background: active ? "var(--accent-soft)" : "var(--surface)",
            border: "1px solid " + (active ? "var(--accent)" : "var(--line)"),
            borderRadius: 12, cursor: "pointer", fontFamily: "inherit",
            textAlign: "left", display: "flex", gap: 12, alignItems: "center",
            width: "100%",
          }}>
            <div style={{
              width: 20, height: 20, borderRadius: 50,
              border: `2px solid ${active ? "var(--accent)" : "var(--line-strong)"}`,
              background: active ? "var(--accent)" : "transparent",
              display: "flex", alignItems: "center", justifyContent: "center",
              flexShrink: 0,
            }}>
              {active && <span style={{ width: 6, height: 6, borderRadius: 50, background: "white" }}/>}
            </div>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontWeight: 520, fontSize: 15, color: "var(--ink)" }}>{s.name}</div>
              <div style={{ fontSize: 13, color: "var(--ink-4)", marginTop: 2 }}>
                Durée {s.duration >= 1 ? `${Math.floor(s.duration)}h${s.duration % 1 ? (s.duration % 1) * 60 : ""}` : `${s.duration * 60} min`}
              </div>
            </div>
            <div style={{
              fontFamily: "var(--ff-display)", fontSize: 20, fontWeight: 580,
              letterSpacing: "-0.02em", color: active ? "var(--accent-ink)" : "var(--ink)",
            }}>
              {s.price} €
            </div>
          </button>
        );
      })}
    </div>

    <div style={{ marginTop: 24 }}>
      <button className="btn btn-accent btn-lg" disabled={!serviceId} onClick={onNext} style={{
        width: "100%", opacity: serviceId ? 1 : 0.5, cursor: serviceId ? "pointer" : "not-allowed",
      }}>
        Choisir un créneau <Icon name="arrow" size={15}/>
      </button>
    </div>
  </div>
);

/* ============ Step 2 — Choose date + time slot ============ */
const StepSlot = ({ service, data, bookingSettings, day, setDay, hour, setHour, onBack, onNext }) => {
  const bs = bookingSettings;
  const openDayIdx = (i) => {
    const h = resolveDayHours(bs, i + 1);
    if (!h.open) return false;
    if (isInVacation(bs, demoDateFor(i))) return false;
    return true;
  };

  const selectedSlots = day != null
    ? generateAvailableSlots(day, data, service.id)
    : [];
  const dayVacation = day != null ? (bs.vacations || []).find(v => demoDateFor(day) >= v.start && demoDateFor(day) <= v.end) : null;
  const upcomingVacations = (bs.vacations || []).filter(v => v.end >= demoDateFor(0));

  return (
    <div>
      <h2 style={{ fontSize: "clamp(22px, 3vw, 28px)", letterSpacing: "-0.02em", marginBottom: 4 }}>
        Quand vous convient-il&nbsp;?
      </h2>
      <p style={{ fontSize: 14, color: "var(--ink-3)", marginBottom: 20 }}>
        Pour {service.name} · {service.duration >= 1 ? `${Math.floor(service.duration)}h${service.duration % 1 ? (service.duration % 1) * 60 : ""}` : `${service.duration * 60} min`}
      </p>

      {/* Vacation notice banner */}
      {upcomingVacations.length > 0 && (
        <div style={{
          padding: "12px 14px", marginBottom: 18,
          background: "var(--warn-soft)", border: "1px solid var(--warn-soft-2)",
          borderRadius: 10, fontSize: 12.5, color: "var(--warn-ink)", lineHeight: 1.5,
        }}>
          <div style={{ fontWeight: 600, marginBottom: 2 }}>À noter — périodes fermées</div>
          {upcomingVacations.slice(0, 3).map(v => (
            <div key={v.id} style={{ marginTop: 2 }}>
              • {v.reason}&nbsp;: du {new Date(v.start).toLocaleDateString("fr-FR")} au {new Date(v.end).toLocaleDateString("fr-FR")}
            </div>
          ))}
        </div>
      )}

      {/* Day picker */}
      <div style={{
        display: "grid", gridTemplateColumns: "repeat(6, 1fr)", gap: 6,
        marginBottom: 18,
      }}>
        {DAY_LABELS_FULL.map((lbl, i) => {
          const isOpen = openDayIdx(i);
          const active = day === i;
          return (
            <button key={i} onClick={() => { if (isOpen) { setDay(i); setHour(null); } }}
              disabled={!isOpen}
              style={{
                padding: "10px 8px",
                background: active ? "var(--accent)" : isOpen ? "var(--surface)" : "var(--bg-alt)",
                border: "1px solid " + (active ? "var(--accent)" : "var(--line)"),
                color: active ? "white" : isOpen ? "var(--ink-2)" : "var(--ink-4)",
                borderRadius: 10, cursor: isOpen ? "pointer" : "not-allowed",
                fontFamily: "inherit",
                display: "flex", flexDirection: "column", alignItems: "center",
                gap: 2, opacity: isOpen ? 1 : 0.5,
              }}>
              <span style={{ fontSize: 11, opacity: active ? 0.85 : 1 }}>{lbl}</span>
              <span style={{ fontFamily: "var(--ff-display)", fontSize: 18, fontWeight: 600, letterSpacing: "-0.02em" }}>
                {DAY_DATES[i]}
              </span>
            </button>
          );
        })}
      </div>
      <div style={{ fontSize: 11, color: "var(--ink-4)", textAlign: "center", marginBottom: 22, fontFamily: "inherit" }}>
        Du {dayDateOf(0)} {dayMonthOf(0)} au {dayDateOf(PICKER_DAYS - 1)} {dayMonthOf(PICKER_DAYS - 1)}
      </div>

      {/* Time slots */}
      {day != null ? (
        dayVacation ? (
          <div style={{
            padding: 20, textAlign: "center",
            background: "var(--warn-soft)", border: "1px solid var(--warn-soft-2)",
            borderRadius: 10, color: "var(--warn-ink)", fontSize: 14,
          }}>
            Fermé ce jour — {dayVacation.reason}. Choisissez un autre jour.
          </div>
        ) : selectedSlots.length === 0 ? (
          <div style={{
            padding: 20, textAlign: "center",
            background: "var(--bg-alt)", border: "1px dashed var(--line-strong)",
            borderRadius: 10, color: "var(--ink-3)", fontSize: 14,
          }}>
            Aucun créneau ce jour. Essayez un autre jour.
          </div>
        ) : (
          <div>
            {(() => {
              const avail = selectedSlots.filter(s => !s.taken).length;
              const taken = selectedSlots.length - avail;
              return (
                <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginBottom: 10 }}>
                  {avail} créneau{avail > 1 ? "x" : ""} libre{avail > 1 ? "s" : ""} {DAY_LABELS_FULL[day]} {DAY_DATES[day]}
                  {taken > 0 && (
                    <span style={{ color: "var(--ink-4)" }}> · {taken} déjà pris</span>
                  )}
                </div>
              );
            })()}
            <div style={{
              display: "grid",
              gridTemplateColumns: "repeat(auto-fill, minmax(76px, 1fr))",
              gap: 6,
            }}>
              {selectedSlots.map(({ h, taken }) => {
                const active = hour === h;
                if (taken) {
                  return (
                    <div key={h} title="Créneau déjà pris" style={{
                      padding: "10px 8px",
                      background: "var(--bg-alt)",
                      color: "var(--ink-4)",
                      border: "1px solid var(--line)",
                      borderRadius: 8,
                      fontFamily: "inherit", fontSize: 13, fontWeight: 520,
                      textAlign: "center",
                      textDecoration: "line-through",
                      textDecorationColor: "var(--ink-4)",
                      textDecorationThickness: "1.5px",
                      cursor: "not-allowed", userSelect: "none",
                      opacity: 0.7,
                    }}>{fmtHourBooking(h)}</div>
                  );
                }
                return (
                  <button key={h} onClick={() => setHour(h)} style={{
                    padding: "10px 8px",
                    background: active ? "var(--accent)" : "var(--surface)",
                    color: active ? "white" : "var(--ink)",
                    border: "1px solid " + (active ? "var(--accent)" : "var(--line)"),
                    borderRadius: 8, cursor: "pointer",
                    fontFamily: "inherit", fontSize: 13, fontWeight: 520,
                  }}>{fmtHourBooking(h)}</button>
                );
              })}
            </div>
          </div>
        )
      ) : (
        <div style={{ padding: 20, textAlign: "center", color: "var(--ink-4)", fontSize: 14 }}>
          Choisissez un jour pour voir les créneaux disponibles.
        </div>
      )}

      <div style={{ marginTop: 28, display: "flex", gap: 10 }}>
        <button className="btn btn-ghost btn-lg" onClick={onBack} style={{ flex: "0 0 auto" }}>
          ← Retour
        </button>
        <button className="btn btn-accent btn-lg" disabled={day == null || hour == null}
          onClick={onNext}
          style={{
            flex: 1, opacity: (day != null && hour != null) ? 1 : 0.5,
            cursor: (day != null && hour != null) ? "pointer" : "not-allowed",
          }}>
          Continuer <Icon name="arrow" size={15}/>
        </button>
      </div>
    </div>
  );
};

/* ============ Cancellation notice — pro contact ============ */
const CancellationNotice = ({ pro, data }) => {
  const phone = data.business && data.business.phone ? data.business.phone : null;
  const email = pro.email;
  return (
    <div style={{
      padding: "16px 18px", marginBottom: 16,
      background: "var(--surface)", border: "1px solid var(--line)",
      borderRadius: 14, boxShadow: "0 1px 2px rgba(15,18,30,0.03)",
      display: "flex", gap: 14, alignItems: "flex-start",
    }}>
      <div style={{
        width: 36, height: 36, borderRadius: 10, flexShrink: 0,
        background: "var(--accent-soft)", color: "var(--accent-ink)",
        display: "flex", alignItems: "center", justifyContent: "center",
      }}>
        <Icon name="calendar" size={17}/>
      </div>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontSize: 14, fontWeight: 580, color: "var(--ink)", lineHeight: 1.35 }}>
          Annuler ou modifier votre RDV
        </div>
        <div style={{ fontSize: 13, color: "var(--ink-3)", marginTop: 4, lineHeight: 1.5 }}>
          Contactez <strong style={{ color: "var(--ink-2)" }}>{pro.businessName}</strong> directement&nbsp;:
        </div>
        <div style={{ marginTop: 10, display: "flex", flexWrap: "wrap", gap: 6 }}>
          {phone && (
            <a href={`tel:${phone.replace(/\s+/g, "")}`} style={{
              display: "inline-flex", alignItems: "center", gap: 6,
              padding: "7px 12px",
              background: "var(--bg-alt)", border: "1px solid var(--line)",
              borderRadius: 999, fontSize: 13, fontWeight: 520,
              color: "var(--ink)", textDecoration: "none",
            }}>
              <Icon name="phone" size={13} style={{ color: "var(--accent-ink)" }}/> {phone}
            </a>
          )}
          {email && (
            <a href={`mailto:${email}`} style={{
              display: "inline-flex", alignItems: "center", gap: 6,
              padding: "7px 12px",
              background: "var(--bg-alt)", border: "1px solid var(--line)",
              borderRadius: 999, fontSize: 13, fontWeight: 520,
              color: "var(--ink)", textDecoration: "none",
              maxWidth: "100%", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", minWidth: 0,
            }}>
              <Icon name="mail" size={13} style={{ color: "var(--accent-ink)", flexShrink: 0 }}/>
              <span style={{ overflow: "hidden", textOverflow: "ellipsis" }}>{email}</span>
            </a>
          )}
        </div>
      </div>
    </div>
  );
};

const _SOCIAL_BOOK_LABEL = {
  instagram: "Instagram",
  facebook:  "Facebook",
  tiktok:    "TikTok",
  snapchat:  "Snapchat",
  whatsapp:  "WhatsApp",
};
const _SOCIAL_BOOK_PLACEHOLDER = {
  instagram: "@votrepseudo",
  facebook:  "Votre nom Facebook",
  tiktok:    "@votrepseudo",
  snapchat:  "votrepseudo",
  whatsapp:  "06 12 34 56 78",
};

/* ============ Step 3 — Contact info + RGPD ============ */
const StepInfo = ({ pro, service, day, hour, preferredSocial, form, setForm, error, submitting, onBack, onSubmit }) => (
  <div>
    <h2 style={{ fontSize: "clamp(22px, 3vw, 28px)", letterSpacing: "-0.02em", marginBottom: 4 }}>
      Pour finir, vos coordonnées.
    </h2>
    <p style={{ fontSize: 14, color: "var(--ink-3)", marginBottom: 20 }}>
      On les utilise uniquement pour vous contacter au sujet de ce rendez-vous.
    </p>

    {/* Summary card */}
    <div style={{
      padding: "14px 16px", marginBottom: 20,
      background: "var(--bg-alt)", border: "1px solid var(--line)",
      borderRadius: 10, display: "flex", gap: 12, alignItems: "center", flexWrap: "wrap",
    }}>
      <div style={{
        width: 36, height: 36, borderRadius: 9,
        background: "var(--accent-soft)", color: "var(--accent-ink)",
        display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0,
      }}>
        <Icon name="calendar" size={18}/>
      </div>
      <div style={{ flex: 1, minWidth: 180 }}>
        <div style={{ fontSize: 13.5, fontWeight: 520, color: "var(--ink)" }}>
          {service.name} · {service.price} €
        </div>
        <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginTop: 2 }}>
          {DAY_LABELS_FULL[day]} {DAY_DATES[day]} {BOOKING_MONTH} · {fmtHourBooking(hour)}
        </div>
      </div>
    </div>

    {error && (
      <div style={{
        padding: "10px 14px", marginBottom: 14,
        background: "oklch(96% 0.04 25)", border: "1px solid oklch(88% 0.06 25)",
        color: "oklch(42% 0.15 25)", borderRadius: 8, fontSize: 13.5,
      }}>{error}</div>
    )}

    <div style={{ display: "grid", gap: 12 }}>
      <div className="cb-book-name-row" style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10 }}>
        <FormField label="Prénom" value={form.firstName} onChange={v => setForm({ ...form, firstName: v })} placeholder="Léa" required autoFocus/>
        <FormField label="Nom" value={form.lastName} onChange={v => setForm({ ...form, lastName: v })} placeholder="Morel" required/>
      </div>
      <FormField label="Email" type="email" value={form.email} onChange={v => setForm({ ...form, email: v })} placeholder="lea@exemple.fr" required/>
      <FormField label="Téléphone" value={form.phone} onChange={v => setForm({ ...form, phone: v })} placeholder="06 12 34 56 78"/>

      {preferredSocial && preferredSocial !== "none" && (
        <div>
          <FormField
            label={`Votre ${_SOCIAL_BOOK_LABEL[preferredSocial] || "réseau"} (optionnel)`}
            value={form.social}
            onChange={v => setForm({ ...form, social: v, noSocial: false })}
            placeholder={_SOCIAL_BOOK_PLACEHOLDER[preferredSocial] || "@pseudo"}
            disabled={form.noSocial}
          />
          <label style={{
            display: "flex", alignItems: "center", gap: 8,
            marginTop: 8, fontSize: 12.5, color: "var(--ink-3)", cursor: "pointer",
          }}>
            <input type="checkbox" checked={form.noSocial}
              onChange={e => setForm({ ...form, noSocial: e.target.checked, social: e.target.checked ? "" : form.social })}
              style={{ accentColor: "var(--accent)", width: 15, height: 15 }}/>
            Je n'ai pas de {_SOCIAL_BOOK_LABEL[preferredSocial] || "compte"}
          </label>
          <div style={{ fontSize: 11.5, color: "var(--ink-4)", marginTop: 6, lineHeight: 1.45 }}>
            Permet à <strong>{pro && pro.businessName ? pro.businessName : "votre prestataire"}</strong> de vous contacter facilement au sujet de ce rendez-vous.
          </div>
        </div>
      )}

      <FormTextarea label="Une précision pour la pro ? (optionnel)" value={form.notes} onChange={v => setForm({ ...form, notes: v })} placeholder="Allergie éventuelle, couleur souhaitée…" rows={2}/>

      <div style={{
        marginTop: 4, padding: "12px 14px",
        background: "var(--bg-alt)", border: "1px solid var(--line)",
        borderRadius: 10, fontSize: 12, color: "var(--ink-3)", lineHeight: 1.55,
      }}>
        <div style={{ fontWeight: 520, color: "var(--ink-2)", marginBottom: 4 }}>
          Ce que deviennent vos informations
        </div>
        <p style={{ margin: 0 }}>
          Vos coordonnées sont transmises à <strong>{pro && pro.name ? pro.name : "la prestataire"}</strong>, responsable de leur traitement, pour gérer ce rendez-vous et son suivi.
          Elles sont hébergées en France (Paris) via <strong>ClientBase</strong>, sous-traitant technique.
          Aucun envoi à des tiers, aucune publicité. Vous pouvez à tout moment demander l'accès, la rectification ou la suppression de vos données auprès de la prestataire, ou en écrivant à <a href="mailto:clientbase.fr@gmail.com" style={{ color: "var(--accent-ink)", fontWeight: 520 }}>clientbase.fr@gmail.com</a>.
          Plus d'infos&nbsp;: <a href="/legal" target="_blank" rel="noopener noreferrer" style={{ color: "var(--accent-ink)", fontWeight: 520 }}>politique de confidentialité</a>.
        </p>
      </div>

      <label style={{
        display: "flex", gap: 10, alignItems: "flex-start",
        fontSize: 12.5, color: "var(--ink-3)", lineHeight: 1.5,
        cursor: "pointer", padding: "8px 0",
      }}>
        <input type="checkbox" checked={form.rgpd} onChange={e => setForm({ ...form, rgpd: e.target.checked })}
          style={{ accentColor: "var(--accent)", width: 16, height: 16, flexShrink: 0, marginTop: 2 }}/>
        <span>
          J'ai lu et j'accepte que mes données soient utilisées comme décrit ci-dessus pour traiter ce rendez-vous.
        </span>
      </label>
    </div>

    <div style={{ marginTop: 22, display: "flex", gap: 10 }}>
      <button className="btn btn-ghost btn-lg" onClick={onBack} style={{ flex: "0 0 auto" }}>
        ← Retour
      </button>
      <button className="btn btn-accent btn-lg" onClick={onSubmit} disabled={submitting} style={{ flex: 1, opacity: submitting ? 0.7 : 1 }}>
        {submitting ? "Envoi…" : "Confirmer mon rendez-vous"} {!submitting && <Icon name="check" size={14} stroke={2.6}/>}
      </button>
    </div>
  </div>
);

/* ============ Step 4 — Success ============ */
const StepConfirmed = ({ pro, confirmed, onRestart }) => (
  <div style={{ textAlign: "center", padding: "24px 0" }}>
    <div style={{
      width: 72, height: 72, borderRadius: 50, margin: "0 auto",
      background: "var(--sage-soft)", color: "var(--sage)",
      display: "flex", alignItems: "center", justifyContent: "center",
    }}>
      <Icon name="check" size={36} stroke={2.6}/>
    </div>
    <h2 style={{
      marginTop: 20, fontSize: "clamp(26px, 3.4vw, 34px)",
      letterSpacing: "-0.03em",
    }}>
      C'est confirmé&nbsp;!
    </h2>
    <p style={{
      marginTop: 12, fontSize: 15.5, color: "var(--ink-2)",
      maxWidth: 440, margin: "12px auto 0", lineHeight: 1.55,
    }}>
      Votre rendez-vous chez <strong>{pro.businessName}</strong> est réservé pour <strong>{DAY_LABELS_FULL[confirmed.day]} {DAY_DATES[confirmed.day]} {BOOKING_MONTH}</strong> à <strong>{fmtHourBooking(confirmed.hour)}</strong>.
    </p>

    <div style={{
      marginTop: 24, padding: "16px 18px", maxWidth: 440, margin: "24px auto 0",
      background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 12,
      display: "flex", flexDirection: "column", gap: 8,
      fontSize: 13.5, color: "var(--ink-2)",
    }}>
      <div style={{ display: "flex", justifyContent: "space-between" }}>
        <span style={{ color: "var(--ink-4)" }}>Prestation</span>
        <span style={{ fontWeight: 520 }}>{confirmed.service.name}</span>
      </div>
      <div style={{ display: "flex", justifyContent: "space-between" }}>
        <span style={{ color: "var(--ink-4)" }}>Durée</span>
        <span style={{ fontFamily: "inherit" }}>
          {confirmed.service.duration >= 1
            ? `${Math.floor(confirmed.service.duration)}h${confirmed.service.duration % 1 ? (confirmed.service.duration % 1) * 60 : ""}`
            : `${confirmed.service.duration * 60} min`}
        </span>
      </div>
      <div style={{ display: "flex", justifyContent: "space-between" }}>
        <span style={{ color: "var(--ink-4)" }}>Prix</span>
        <span style={{ fontFamily: "inherit", fontWeight: 520 }}>{confirmed.service.price} €</span>
      </div>
    </div>

    <div style={{
      marginTop: 20, fontSize: 13, color: "var(--ink-4)",
    }}>
      Un email de confirmation sera envoyé (en bêta, cette étape est simulée).
    </div>

    <div style={{ marginTop: 24 }}>
      <button className="btn btn-ghost" onClick={onRestart}>
        Réserver un autre rendez-vous
      </button>
    </div>
  </div>
);

/* ============ Slug not found / no pro ============ */
const BookingNotFound = ({ slug }) => (
  <div style={{
    minHeight: "100vh", background: "var(--bg)",
    display: "flex", alignItems: "center", justifyContent: "center",
    padding: "32px 20px",
  }}>
    <div style={{
      maxWidth: 480, textAlign: "center",
      padding: 32,
      background: "var(--surface)", border: "1px solid var(--line)",
      borderRadius: 16, boxShadow: "var(--sh-2)",
    }}>
      <div style={{ display: "flex", justifyContent: "center", marginBottom: 16 }}>
        <Logo size={28}/>
      </div>
      <h1 style={{ fontSize: 22, marginBottom: 8 }}>Lien de réservation introuvable</h1>
      <p style={{ fontSize: 14.5, color: "var(--ink-3)", lineHeight: 1.55 }}>
        Le lien <code style={{ fontFamily: "inherit", background: "var(--bg-alt)", padding: "2px 6px", borderRadius: 4, fontSize: 12.5 }}>?book={slug}</code> ne correspond à aucun compte sur cet appareil.
      </p>
      <div style={{
        marginTop: 20, padding: "12px 14px",
        background: "var(--bg-alt)", border: "1px dashed var(--line-strong)",
        borderRadius: 10, fontSize: 12.5, color: "var(--ink-3)", lineHeight: 1.5, textAlign: "left",
      }}>
        <strong style={{ color: "var(--ink-2)" }}>Note bêta&nbsp;:</strong> pendant la bêta, les réservations se font en local, dans le même navigateur que celui de la pro. Une vraie synchro serveur arrivera au lancement officiel.
      </div>
      <a href="/" className="btn btn-ghost" style={{ marginTop: 20 }}>
        Retour à l'accueil
      </a>
    </div>
  </div>
);

/* ================================================================
   PUBLIC LOYALTY CARD PAGE — /?card=<slug>&cli=<clientId>
================================================================ */
const CardPage = ({ slug, clientId }) => {
  const pro = cbAuth.findUserBySlug(slug);
  const [data, setData] = React.useState(() => {
    try {
      const saved = localStorage.getItem(CB_LS_KEY);
      if (saved) return { ...SEED_DATA, ...JSON.parse(saved) };
    } catch (e) { /* ignore */ }
    return SEED_DATA;
  });

  // Poll localStorage every 3s to reflect pro-side changes
  React.useEffect(() => {
    const t = setInterval(() => {
      try {
        const saved = localStorage.getItem(CB_LS_KEY);
        if (saved) setData({ ...SEED_DATA, ...JSON.parse(saved) });
      } catch (e) { /* ignore */ }
    }, 3000);
    return () => clearInterval(t);
  }, []);

  if (!pro) return <BookingNotFound slug={slug}/>;
  const client = data.clients.find(c => c.id === clientId);
  if (!client) {
    return (
      <div style={{
        minHeight: "100vh", background: "var(--bg)",
        display: "flex", alignItems: "center", justifyContent: "center", padding: 24,
      }}>
        <div style={{
          maxWidth: 420, textAlign: "center", padding: 32,
          background: "var(--surface)", border: "1px solid var(--line)",
          borderRadius: 16, boxShadow: "var(--sh-2)",
        }}>
          <h1 style={{ fontSize: 20, marginBottom: 8 }}>Carte introuvable</h1>
          <p style={{ fontSize: 14, color: "var(--ink-3)" }}>
            Ce lien de fidélité n'est plus valide. Demandez-en un nouveau à {pro.businessName}.
          </p>
        </div>
      </div>
    );
  }

  const total = data.fideliteRules.visits;
  const remaining = Math.max(0, total - client.fid);
  const rowCount = Math.ceil(total / 5);

  return (
    <div style={{
      minHeight: "100vh", background: "var(--bg)",
      display: "flex", flexDirection: "column",
    }}>
      <BookingHeader pro={pro}/>
      <main style={{ flex: 1, padding: "32px 16px 48px" }}>
        <div style={{ maxWidth: 440, margin: "0 auto" }}>
          <h1 style={{
            fontSize: "clamp(22px, 3vw, 28px)", letterSpacing: "-0.025em",
            marginBottom: 4, textAlign: "center",
          }}>
            Votre carte de fidélité
          </h1>
          <p style={{
            fontSize: 14, color: "var(--ink-3)",
            textAlign: "center", marginBottom: 20,
          }}>
            Chez <strong>{pro.businessName}</strong>
          </p>

          {/* The card */}
          <div style={{
            padding: 24,
            background: "linear-gradient(135deg, var(--accent) 0%, oklch(45% 0.22 288) 100%)",
            borderRadius: 18, color: "white", boxShadow: "var(--sh-3)",
          }}>
            <div style={{ display: "flex", justifyContent: "space-between", alignItems: "start" }}>
              <div>
                <div style={{ fontSize: 10.5, textTransform: "uppercase", letterSpacing: "0.1em", opacity: 0.85 }}>
                  {pro.businessName}
                </div>
                <div style={{
                  fontFamily: "var(--ff-display)", fontSize: 22,
                  fontWeight: 580, letterSpacing: "-0.025em", marginTop: 6,
                }}>{client.name}</div>
              </div>
              <Logo size={26} withWordmark={false}/>
            </div>

            <div style={{
              marginTop: 26,
              display: "grid",
              gridTemplateColumns: `repeat(${Math.min(total, 5)}, 1fr)`,
              gap: 8,
            }}>
              {Array.from({ length: total }).map((_, i) => {
                const stamped = i < client.fid;
                return (
                  <div key={i} style={{
                    aspectRatio: 1, borderRadius: 50,
                    background: stamped ? "rgba(255,255,255,0.95)" : "rgba(255,255,255,0.15)",
                    border: stamped ? "none" : "1.5px dashed rgba(255,255,255,0.4)",
                    display: "flex", alignItems: "center", justifyContent: "center",
                    color: "var(--accent)",
                  }}>
                    {stamped && <Icon name="check" size={16} stroke={3}/>}
                  </div>
                );
              })}
            </div>

            <div style={{
              marginTop: 20, padding: "12px 14px",
              background: "rgba(255,255,255,0.15)", borderRadius: 10,
              fontSize: 13, lineHeight: 1.5,
            }}>
              {client.fid >= total
                ? <><strong>🎁 Votre récompense est prête&nbsp;!</strong> {data.fideliteRules.rewardLabel}.</>
                : remaining === 1
                  ? <>Plus qu'<strong>une visite</strong> pour&nbsp;: {data.fideliteRules.rewardLabel}</>
                  : <>Encore <strong>{remaining} visites</strong> avant&nbsp;: {data.fideliteRules.rewardLabel}</>
              }
            </div>
          </div>

          {/* Info */}
          <div style={{
            marginTop: 16, padding: "12px 14px",
            background: "var(--bg-alt)", border: "1px dashed var(--line-strong)",
            borderRadius: 10, fontSize: 12.5, color: "var(--ink-3)", lineHeight: 1.5,
          }}>
            Cette carte se met à jour automatiquement après chaque visite. Conservez ce lien pour suivre votre progression.
          </div>

          {/* Book button */}
          <div style={{ marginTop: 20 }}>
            <a href={`?book=${slug}`} className="btn btn-accent btn-lg" style={{ width: "100%" }}>
              Prendre mon prochain RDV <Icon name="arrow" size={15}/>
            </a>
          </div>
        </div>
      </main>
      <BookingFooter pro={pro}/>
    </div>
  );
};

const _prettifyBookingError = (msg) => {
  const m = (msg || "").toUpperCase();
  if (m.includes("SLUG_NOT_FOUND") || m.includes("BOOKING_NOT_FOUND")) return "Ce professionnel n'existe plus.";
  if (m.includes("SERVICE_DISABLED")) return "Cette prestation n'est plus disponible — choisissez-en une autre.";
  if (m.includes("SERVICE_NOT_FOUND")) return "Cette prestation n'est plus disponible.";
  if (m.includes("CLIENT_NAME_REQUIRED")) return "Prénom et nom requis.";
  if (m.includes("EMAIL_INVALID"))      return "Email invalide.";
  if (m.includes("FIRST_REQUIRED") || m.includes("LAST_REQUIRED")) return "Prénom et nom requis.";
  if (m.includes("SLOT_REQUIRED"))      return "Créneau invalide.";
  if (m.includes("PUBLIC_BOOK_APPOINTMENT") && m.includes("DOES NOT EXIST")) {
    return "Le système de réservation n'est pas encore activé pour ce salon. Contactez-le par message direct.";
  }
  return "Une erreur est survenue. Réessayez ou contactez le salon directement.";
};

Object.assign(window, {
  BookingPage, BookingNotFound, CardPage,
  generateAvailableSlots, fmtHourBooking,
});
