// booking.jsx — public booking page + confirmation

const MONTHS = ["January","February","March","April","May","June","July","August","September","October","November","December"];
const DOW = ["SUN","MON","TUE","WED","THU","FRI","SAT"];

const TIMES = [
  "9:00 AM","9:30 AM","10:00 AM","10:30 AM","11:00 AM","11:30 AM",
  "12:00 PM","12:30 PM","1:00 PM","1:30 PM","2:00 PM","2:30 PM",
  "3:00 PM","3:30 PM","4:00 PM","4:30 PM"
];

// Returns true when the date has open slots OR (class mode) full/waitlist slots
// getTimesFn and getFullTimesFn are closures that capture staff context from BookingPage
function dateHasActivity(year, month, day, getTimesFn, getFullTimesFn) {
  const dateIso = datePartsToIso({ year, month, day });
  if (getTimesFn(dateIso).length > 0) return true;
  if (getFullTimesFn) return getFullTimesFn(dateIso).length > 0;
  return false;
}

function Calendar({ year, month, getTimesFn, getFullTimesFn, selectedDate, onSelectDate, onPrev, onNext }) {
  const firstOfMonth = new Date(year, month, 1);
  const startDay = firstOfMonth.getDay();
  const daysInMonth = new Date(year, month + 1, 0).getDate();
  const daysInPrev = new Date(year, month, 0).getDate();

  // build 6 weeks of cells
  const cells = [];
  for (let i = 0; i < 42; i++) {
    const dayOffset = i - startDay;
    let cellDay, cellMonth, cellYear, otherMonth;
    if (dayOffset < 0) {
      cellDay = daysInPrev + dayOffset + 1;
      cellMonth = month - 1; cellYear = year;
      otherMonth = true;
    } else if (dayOffset >= daysInMonth) {
      cellDay = dayOffset - daysInMonth + 1;
      cellMonth = month + 1; cellYear = year;
      otherMonth = true;
    } else {
      cellDay = dayOffset + 1;
      cellMonth = month; cellYear = year;
      otherMonth = false;
    }
    if (cellMonth < 0) { cellMonth = 11; cellYear -= 1; }
    if (cellMonth > 11) { cellMonth = 0; cellYear += 1; }
    cells.push({ day: cellDay, month: cellMonth, year: cellYear, otherMonth });
  }
  // trim trailing all-other-month rows
  while (cells.length > 35 && cells.slice(35).every(c => c.otherMonth)) cells.length = 35;

  return (
    <div className="cal-section">
      <h4 className="cal-section-label">Select a date &amp; time</h4>
      <div className="cal-h">
        <div className="cal-nav">
          <button onClick={onPrev} aria-label="Prev"><I.chevL size={14}/></button>
        </div>
        <div className="month">{MONTHS[month]} {year}</div>
        <div className="cal-nav">
          <button onClick={onNext} aria-label="Next"><I.chev size={14}/></button>
        </div>
      </div>
      <div className="cal-grid">
        {DOW.map(d => <div key={d} className="dow">{d}</div>)}
        {cells.map((c, i) => {
          const avail = !c.otherMonth && dateHasActivity(c.year, c.month, c.day, getTimesFn, getFullTimesFn);
          const isSelected = selectedDate &&
            selectedDate.year === c.year && selectedDate.month === c.month && selectedDate.day === c.day;
          return (
            <button
              key={i}
              disabled={!avail}
              onClick={() => avail && onSelectDate({year: c.year, month: c.month, day: c.day})}
              className={[
                "cal-day",
                c.otherMonth ? "is-other-month" : "",
                avail ? "has-slots" : "",
                isSelected ? "is-selected" : "",
              ].join(" ")}>
              {c.day}
            </button>
          );
        })}
      </div>
    </div>
  );
}

function TimesList({ selectedDate, getTimesFn, getFullTimesFn, selectedTime, onSelectTime, onJoinWaitlist, isClassMode, data, event, dateIso }) {
  const times     = selectedDate && getTimesFn     ? getTimesFn(dateIso)     : [];
  const fullTimes = selectedDate && getFullTimesFn ? getFullTimesFn(dateIso) : [];
  const noneAtAll = selectedDate && times.length === 0 && fullTimes.length === 0;
  return (
    <div className="times-section">
      <h4 className="bk-section-h">Available times</h4>
      <div className="times">
        {!selectedDate && <div className="empty-inline">Choose a date to see times.</div>}
        {noneAtAll && <div className="empty-inline">No open times for this date.</div>}
        {times.map(t => {
          const info = isClassMode ? getSlotInfo({ data, event, dateIso, time: t }) : null;
          return (
            <button key={t}
              className={"time-btn " + (selectedTime === t ? "is-selected" : "") + (isClassMode ? " has-capacity" : "")}
              onClick={() => onSelectTime(t)}>
              <span className="time-label">{t}</span>
              {info && (
                <span className={"slot-spaces " + (info.available <= 2 ? "is-low" : "")}>
                  {info.booked}/{info.total}
                </span>
              )}
            </button>
          );
        })}
        {fullTimes.map(t => (
          <button key={"full-" + t}
            className="time-btn is-full"
            onClick={() => onJoinWaitlist && onJoinWaitlist({ date: dateIso, time: t })}>
            <span className="time-label">{t}</span>
            <span className="slot-full-label">Full · Join waitlist</span>
          </button>
        ))}
      </div>
    </div>
  );
}

function getPaymentDue(event) {
  if (!event || Number(event.price || 0) <= 0 || (event.paymentMode || "none") === "none") return 0;
  if (event.paymentMode === "deposit") return Number(event.depositAmount || 0);
  return Number(event.price || 0); // covers "full" and "term"
}

function computeDiscount(coupon, originalAmount) {
  if (!coupon || originalAmount <= 0) return 0;
  if (coupon.discountType === "percent")
    return Math.round((originalAmount * coupon.discountValue) / 100 * 100) / 100;
  if (coupon.discountType === "fixed")
    return Math.min(originalAmount, coupon.discountValue);
  if (coupon.discountType === "free" || coupon.discountType === "trial")
    return originalAmount; // fully covers
  return 0;
}

function couponDiscountLabel(coupon) {
  if (!coupon) return "";
  if (coupon.discountType === "percent") return `${coupon.discountValue}% off`;
  if (coupon.discountType === "fixed")   return `£${coupon.discountValue} off`;
  if (coupon.discountType === "trial")   return `${coupon.trialDays}-day free trial`;
  if (coupon.discountType === "free")    return "Free access";
  return "Discount";
}

function priceLabel(event, currency) {
  if (!event || Number(event.price || 0) <= 0) return "Free";
  const money = formatMoney(event.price, currency);
  if (event.paymentMode === "term" && Number(event.termWeeks || 0) > 0) {
    const saving = (Number(event.classPrice || 0) * Number(event.termWeeks)) - Number(event.price);
    const savingStr = saving > 0 ? ` · Save ${formatMoney(saving, currency)}` : "";
    return `${money} / ${event.termWeeks}-week term${savingStr}`;
  }
  if (event.paymentMode === "deposit")
    return `${formatMoney(event.depositAmount || 0, currency)} deposit`;
  return `${money} per class`;
}

function PaymentSection({ data, event, form, setForm, errors, amountOverride }) {
  const baseAmount = getPaymentDue(event);
  if (!baseAmount) return null;

  const amount   = amountOverride !== undefined ? amountOverride : baseAmount;
  const currency = data.profile?.currency || "GBP";
  const userId   = data.userId;
  const update   = (patch) => setForm(f => ({ ...f, ...patch }));

  if (amount === 0) {
    return (
      <div className="coupon-free-notice">
        <I.check size={14}/> Fully covered by coupon — no payment required
      </div>
    );
  }

  const stripeAvailable = !!data.integrations?.Stripe?.connected;
  const paypalAvailable = !!data.integrations?.PayPal?.connected;
  const providers = [
    stripeAvailable && "Stripe",
    paypalAvailable && "PayPal",
  ].filter(Boolean);

  // ── Stripe state ─────────────────────────────────────────────
  const [stripeInit,   setStripeInit]   = React.useState(false);  // loading in progress
  const [stripeReady,  setStripeReady]  = React.useState(false);  // element mounted
  const [stripeErr,    setStripeErr]    = React.useState(null);
  const [stripeProc,   setStripeProc]   = React.useState(false);
  const stripeRef      = React.useRef(null);
  const elementsRef    = React.useRef(null);
  const stripeMounted  = React.useRef(false);
  const stripeDivRef   = React.useRef(null);

  // ── PayPal state ──────────────────────────────────────────────
  const [paypalInit,  setPaypalInit]   = React.useState(false);
  const [paypalReady, setPaypalReady]  = React.useState(false);
  const [paypalErr,   setPaypalErr]    = React.useState(null);
  const paypalMounted  = React.useRef(false);
  const paypalDivRef   = React.useRef(null);

  // Init Stripe when selected
  React.useEffect(() => {
    if (form.paymentProvider !== "Stripe" || !stripeAvailable || stripeMounted.current) return;
    stripeMounted.current = true;
    setStripeInit(true); setStripeErr(null); setStripeReady(false);

    createStripePaymentIntent(userId, amount, currency).then(result => {
      if (result.error || !result.clientSecret) {
        setStripeErr(result.error || "Failed to initialise Stripe. Check your keys.");
        setStripeInit(false); return;
      }
      if (!window.Stripe) { setStripeErr("Stripe.js did not load."); setStripeInit(false); return; }
      const stripe   = window.Stripe(result.publishableKey);
      const elements = stripe.elements({
        clientSecret: result.clientSecret,
        appearance: { theme: "night", variables: { colorPrimary: "#efa403", borderRadius: "8px" } },
      });
      stripeRef.current   = stripe;
      elementsRef.current = elements;
      const pe = elements.create("payment");
      pe.on("ready", () => { setStripeInit(false); setStripeReady(true); });
      pe.on("change", e => setStripeErr(e.error?.message || null));
      if (stripeDivRef.current) pe.mount(stripeDivRef.current);
    });
    return () => { stripeMounted.current = false; };
  }, [form.paymentProvider]);

  const handleStripePay = async () => {
    if (!stripeRef.current || !elementsRef.current) return;
    setStripeProc(true); setStripeErr(null);
    const { error, paymentIntent } = await stripeRef.current.confirmPayment({
      elements: elementsRef.current,
      redirect: "if_required",
    });
    if (error) {
      setStripeErr(error.message);
      setStripeProc(false);
    } else if (paymentIntent?.status === "succeeded" || paymentIntent?.status === "processing") {
      update({ paymentComplete: true, paymentAmount: amount, paymentProvider: "Stripe", paymentIntentId: paymentIntent.id });
      setStripeProc(false);
    } else {
      setStripeErr("Payment did not complete. Please try again.");
      setStripeProc(false);
    }
  };

  // Init PayPal when selected
  React.useEffect(() => {
    if (form.paymentProvider !== "PayPal" || !paypalAvailable || paypalMounted.current) return;
    paypalMounted.current = true;
    setPaypalInit(true); setPaypalErr(null); setPaypalReady(false);

    const clientId = data.integrations.PayPal.clientId;
    loadPaypalSdk(clientId, currency).then(() => {
      if (!paypalDivRef.current) return;
      paypalDivRef.current.innerHTML = "";
      window.paypal.Buttons({
        style: { layout: "vertical", color: "blue", shape: "rect", label: "pay", height: 44 },
        createOrder: async () => {
          const r = await createPaypalOrder(userId, amount, currency);
          if (r.error) throw new Error(r.error);
          return r.orderId;
        },
        onApprove: async (approveData) => {
          const r = await capturePaypalOrder(userId, approveData.orderID);
          if (r.status === "COMPLETED" || r.status === "APPROVED") {
            update({ paymentComplete: true, paymentAmount: amount, paymentProvider: "PayPal" });
          } else {
            setPaypalErr(r.error || "PayPal capture failed. Please try again.");
          }
        },
        onError: (err) => { setPaypalErr("PayPal error — please try another payment method."); console.error(err); },
      }).render(paypalDivRef.current);
      setPaypalInit(false); setPaypalReady(true);
    }).catch(e => { setPaypalErr(e.message); setPaypalInit(false); });
    return () => { paypalMounted.current = false; };
  }, [form.paymentProvider]);

  const switchProvider = (p) => {
    if (form.paymentProvider === p) return;
    stripeMounted.current  = false;
    paypalMounted.current  = false;
    setStripeReady(false); setStripeErr(null); setStripeInit(false);
    setPaypalReady(false);  setPaypalErr(null);  setPaypalInit(false);
    update({ paymentProvider: p, paymentComplete: false, paymentAmount: 0 });
  };

  const dueLabel = event.paymentMode === "deposit"
    ? "Deposit due now"
    : event.paymentMode === "term"
      ? `${Number(event.termWeeks || 0)}-week term — due now`
      : "Due now";

  return (
    <div className="payment-box">
      <div className="payment-head">
        <div>
          <h4>Payment</h4>
          <p>
            {dueLabel} · {formatMoney(amount, currency)}
            {amountOverride !== undefined && amountOverride < baseAmount && (
              <span style={{marginLeft:6, textDecoration:"line-through", color:"var(--text-faint)", fontSize:12}}>
                {formatMoney(baseAmount, currency)}
              </span>
            )}
          </p>
        </div>
        {form.paymentComplete && <span className="payment-paid"><I.check size={12}/> Paid</span>}
      </div>

      {providers.length === 0 ? (
        <div className="empty-inline">Connect Stripe or PayPal in Integrations to take payments.</div>
      ) : form.paymentComplete ? (
        <div className="payment-complete-notice">
          <I.check size={16}/> Payment confirmed via {form.paymentProvider}
        </div>
      ) : (
        <>
          <div className={"payment-options " + (errors.payment ? "invalid" : "")}>
            {providers.map(p => (
              <button key={p} type="button"
                className={"payment-option " + (form.paymentProvider === p ? "is-selected" : "")}
                onClick={() => switchProvider(p)}>
                <span className={"pay-logo " + p.toLowerCase()}>{p[0]}</span>
                <span>{p}</span>
              </button>
            ))}
          </div>

          {/* ── Stripe Payment Element ── */}
          {form.paymentProvider === "Stripe" && (
            <div className="stripe-element-wrapper">
              {stripeInit  && <div className="payment-sdk-loading"><span/>Loading payment form…</div>}
              {stripeErr   && <div className="payment-sdk-error">{stripeErr}</div>}
              <div ref={stripeDivRef} id="stripe-payment-element" style={{minHeight: stripeReady ? "auto" : 0}}/>
              {stripeReady && (
                <button className="payment-button" type="button"
                  disabled={stripeProc}
                  onClick={handleStripePay}>
                  {stripeProc ? "Processing…" : `Pay ${formatMoney(amount, currency)}`}
                </button>
              )}
            </div>
          )}

          {/* ── PayPal Buttons ── */}
          {form.paymentProvider === "PayPal" && (
            <div className="paypal-element-wrapper">
              {paypalInit && <div className="payment-sdk-loading"><span/>Loading PayPal…</div>}
              {paypalErr  && <div className="payment-sdk-error">{paypalErr}</div>}
              <div ref={paypalDivRef} id="paypal-button-container"/>
            </div>
          )}

          {!form.paymentProvider && (
            <div style={{fontSize:13, color:"var(--text-muted)", textAlign:"center", padding:"12px 0"}}>
              Select a payment method above to continue.
            </div>
          )}
        </>
      )}
    </div>
  );
}

function YourDetails({ form, setForm, event, data, errors, onSubmit, canSubmit,
  coupon, couponInput, setCouponInput, couponError, couponBusy,
  onApplyCoupon, onRemoveCoupon,
  voucher, voucherInput, setVoucherInput, voucherError, voucherBusy,
  onApplyVoucher, onRemoveVoucher,
  packagePurchase, packageEmail, setPackageEmail, packageOptions, packageBusy,
  onLookupPackage, onSelectPackage, onRemovePackage,
  memberSub, memberEmail, setMemberEmail, memberOptions, memberBusy,
  onLookupMembership, onSelectMembership, onRemoveMembership,
  effectiveAmount }) {
  const update = (k, v) => setForm(f => ({...f, [k]: v}));
  const updateAnswer = (id, value) => setForm(f => ({...f, answers: { ...(f.answers || {}), [id]: value }}));
  const questions = event?.questions || [];
  const hasPaidEvent = getPaymentDue(event) > 0;

  return (
    <div>
      <h4 className="bk-section-h">Your details</h4>
      <div className="bk-form">
        <div className="bk-field">
          <label>Full name <span className="req">*</span></label>
          <input className={"bk-input " + (errors.name ? "invalid" : "")}
            placeholder="Enter your full name"
            value={form.name} onChange={e => update("name", e.target.value)}/>
        </div>
        <div className="bk-field">
          <label>Email <span className="req">*</span></label>
          <input className={"bk-input " + (errors.email ? "invalid" : "")}
            type="text" inputMode="email" placeholder="Enter your email address"
            value={form.email} onChange={e => update("email", e.target.value)}/>
        </div>
        <div className="bk-field">
          <label>Phone <span className="opt">(optional)</span></label>
          <input className="bk-input" type="tel" placeholder="+44 7700 900000"
            value={form.phone} onChange={e => update("phone", e.target.value)}/>
        </div>
        <div className="bk-field">
          <label>Notes <span className="opt">(optional)</span></label>
          <textarea className="bk-textarea" maxLength={500}
            placeholder="Anything we should know ahead of time?"
            value={form.notes} onChange={e => update("notes", e.target.value)}/>
          <div className="bk-counter">{form.notes.length}/500</div>
        </div>
        {questions.length > 0 && (
          <div className="bk-extra-questions">
            <h4 className="bk-section-h">Booking questions</h4>
            {questions.map((question) => (
              <div className="bk-field" key={question.id}>
                <label>{question.label} {question.required && <span className="req">*</span>}</label>
                {question.type === "textarea" ? (
                  <textarea
                    className={"bk-textarea " + (errors[`answer-${question.id}`] ? "invalid" : "")}
                    value={form.answers?.[question.id] || ""}
                    onChange={(e) => updateAnswer(question.id, e.target.value)}
                  />
                ) : (
                  <input
                    className={"bk-input " + (errors[`answer-${question.id}`] ? "invalid" : "")}
                    value={form.answers?.[question.id] || ""}
                    onChange={(e) => updateAnswer(question.id, e.target.value)}
                  />
                )}
              </div>
            ))}
          </div>
        )}
        {event?.requireTerms && (
          <label className={"terms-check " + (errors.terms ? "invalid" : "")}>
            <input type="checkbox" checked={!!form.acceptedTerms} onChange={() => update("acceptedTerms", !form.acceptedTerms)}/>
            <span>{event.termsText || "I agree to the booking terms."}</span>
          </label>
        )}

        {/* ── Coupon code ── */}
        <div className="bk-field">
          <label>Discount code <span className="opt">(optional)</span></label>
          {coupon ? (
            <div className="coupon-applied-badge">
              <I.check size={13}/>
              <span><strong>{coupon.code}</strong> — {couponDiscountLabel(coupon)} applied</span>
              <button onClick={onRemoveCoupon} aria-label="Remove coupon"><I.x size={12}/></button>
            </div>
          ) : (
            <>
              <div className="coupon-input-row">
                <input
                  className={"bk-input " + (couponError ? "invalid" : "")}
                  style={{textTransform:"uppercase", letterSpacing:"0.06em", fontWeight:600}}
                  placeholder="Coupon code…"
                  value={couponInput}
                  onChange={e => { setCouponInput(e.target.value.toUpperCase()); }}
                  onKeyDown={e => e.key === "Enter" && onApplyCoupon()}
                />
                <button className="btn ghost" onClick={onApplyCoupon}
                  disabled={!couponInput.trim() || couponBusy}>
                  {couponBusy ? "…" : "Apply"}
                </button>
              </div>
              {couponError && <div className="coupon-error">{couponError}</div>}
            </>
          )}
        </div>

        {/* ── Gift voucher ── */}
        {hasPaidEvent && (
          <div className="bk-field">
            <label>Gift voucher <span className="opt">(optional)</span></label>
            {voucher ? (
              <div className="coupon-applied-badge">
                <I.check size={13}/>
                <span><strong>{voucher.code}</strong> — {formatMoney(voucher.amount, data.profile?.currency || "GBP")} credit applied</span>
                <button onClick={onRemoveVoucher} aria-label="Remove voucher"><I.x size={12}/></button>
              </div>
            ) : (
              <>
                <div className="coupon-input-row">
                  <input
                    className={"bk-input " + (voucherError ? "invalid" : "")}
                    style={{textTransform:"uppercase", letterSpacing:"0.06em", fontWeight:600}}
                    placeholder="Voucher code…"
                    value={voucherInput}
                    onChange={e => setVoucherInput(e.target.value.toUpperCase())}
                    onKeyDown={e => e.key === "Enter" && onApplyVoucher()}
                  />
                  <button className="btn ghost" onClick={onApplyVoucher}
                    disabled={!voucherInput.trim() || voucherBusy}>
                    {voucherBusy ? "…" : "Apply"}
                  </button>
                </div>
                {voucherError && <div className="coupon-error">{voucherError}</div>}
              </>
            )}
          </div>
        )}

        {/* ── Membership session ── */}
        {hasPaidEvent && (
          <div className="bk-field">
            <label>Use membership allowance <span className="opt">(optional)</span></label>
            {memberSub ? (
              <div className="coupon-applied-badge">
                <I.check size={13}/>
                <span><strong>{memberSub.membershipName}</strong> — {memberSub.sessionsRemaining} session{memberSub.sessionsRemaining !== 1 ? "s" : ""} remaining this month</span>
                <button onClick={onRemoveMembership} aria-label="Remove membership"><I.x size={12}/></button>
              </div>
            ) : (
              <>
                <div className="coupon-input-row">
                  <input
                    className="bk-input"
                    type="email"
                    placeholder="Email on your membership account…"
                    value={memberEmail}
                    onChange={e => setMemberEmail(e.target.value)}
                    onKeyDown={e => e.key === "Enter" && onLookupMembership()}
                  />
                  <button className="btn ghost" onClick={onLookupMembership}
                    disabled={!memberEmail.trim() || memberBusy}>
                    {memberBusy ? "…" : "Look up"}
                  </button>
                </div>
                {memberOptions && memberOptions.length === 0 && (
                  <div className="coupon-error">No active membership found for that email.</div>
                )}
                {memberOptions && memberOptions.length > 0 && (
                  <div className="package-options-list">
                    {memberOptions.map(s => (
                      <button key={s.id} className="package-option-btn" type="button"
                        onClick={() => onSelectMembership(s)}>
                        <span className="package-option-name">{s.membershipName}</span>
                        <span className="package-option-rem">{s.sessionsRemaining} session{s.sessionsRemaining !== 1 ? "s" : ""} left this month</span>
                      </button>
                    ))}
                  </div>
                )}
              </>
            )}
          </div>
        )}

        {/* ── Session package ── */}
        {hasPaidEvent && (
          <div className="bk-field">
            <label>Use a session package <span className="opt">(optional)</span></label>
            {packagePurchase ? (
              <div className="coupon-applied-badge">
                <I.check size={13}/>
                <span><strong>{packagePurchase.packageName}</strong> — {packagePurchase.sessionsRemaining} session{packagePurchase.sessionsRemaining !== 1 ? "s" : ""} remaining</span>
                <button onClick={onRemovePackage} aria-label="Remove package"><I.x size={12}/></button>
              </div>
            ) : (
              <>
                <div className="coupon-input-row">
                  <input
                    className="bk-input"
                    type="email"
                    placeholder="Email used when package was purchased…"
                    value={packageEmail}
                    onChange={e => setPackageEmail(e.target.value)}
                    onKeyDown={e => e.key === "Enter" && onLookupPackage()}
                  />
                  <button className="btn ghost" onClick={onLookupPackage}
                    disabled={!packageEmail.trim() || packageBusy}>
                    {packageBusy ? "…" : "Look up"}
                  </button>
                </div>
                {packageOptions && packageOptions.length === 0 && (
                  <div className="coupon-error">No active packages found for that email address.</div>
                )}
                {packageOptions && packageOptions.length > 0 && (
                  <div className="package-options-list">
                    {packageOptions.map(pp => (
                      <button key={pp.id} className="package-option-btn" type="button"
                        onClick={() => onSelectPackage(pp)}>
                        <span className="package-option-name">{pp.packageName}</span>
                        <span className="package-option-rem">{pp.sessionsRemaining} session{pp.sessionsRemaining !== 1 ? "s" : ""} left</span>
                      </button>
                    ))}
                  </div>
                )}
              </>
            )}
          </div>
        )}

        <PaymentSection data={data} event={event} form={form} setForm={setForm}
          errors={errors} amountOverride={hasPaidEvent ? effectiveAmount : undefined}/>

        {event?.cancellationPolicy === "none" ? (
          <div className="bk-policy-notice bk-policy-strict">
            <I.shield size={13}/>
            <span>No cancellations — all bookings are final</span>
          </div>
        ) : event?.cancellationHours > 0 ? (
          <div className="bk-policy-notice">
            <I.shield size={13}/>
            <span>Free cancellation up to {event.cancellationHours} hours before your appointment</span>
          </div>
        ) : null}

        <button className="bk-confirm-btn" onClick={onSubmit} disabled={!canSubmit}>
          Confirm booking
        </button>
        <div className="bk-secure">
          <I.shield size={13}/>
          <span>Your information is secure and will only be used for this booking.</span>
        </div>
      </div>
    </div>
  );
}

function dateLabel(d) {
  if (!d) return "—";
  const obj = new Date(d.year, d.month, d.day);
  return obj.toLocaleDateString("en-US", { weekday: "long", month: "long", day: "numeric", year: "numeric" });
}

const VIDEO_KEYWORDS = { google_meet: "Google Meet", zoom: "Zoom", teams: "Microsoft Teams" };

function friendlyLocation(location) {
  const v = String(location || "").trim();
  if (VIDEO_KEYWORDS[v]) return VIDEO_KEYWORDS[v];
  return v;
}

function isVideoKeyword(location) {
  return !!VIDEO_KEYWORDS[String(location || "").trim()];
}

function isPhysicalLocation(location) {
  const value = String(location || "").trim();
  if (!value) return false;
  if (isVideoKeyword(value)) return false;
  return !/(zoom|google meet|teams|meet|phone|online|video|call)/i.test(value);
}

function mapsUrl(location) {
  return `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(location)}`;
}

function LocationMap({ location }) {
  if (!isPhysicalLocation(location)) return null;
  return (
    <div className="location-map-card">
      <div className="location-map">
        <div className="map-grid-lines"/>
        <div className="map-road main"/>
        <div className="map-road cross"/>
        <div className="map-pin">
          <I.location size={18}/>
        </div>
      </div>
      <div className="location-map-info">
        <div>
          <div className="location-map-label">Location</div>
          <div className="location-map-address">{location}</div>
        </div>
        <a className="map-link" href={mapsUrl(location)} target="_blank" rel="noreferrer">
          Open in Maps
        </a>
      </div>
    </div>
  );
}

function EventSummary({ events, selectedEventId, selectedEvent, currency, onSelectEvent }) {
  return (
    <div className="booking-event-card">
      <label>Appointment type</label>
      <select className="bk-input" value={selectedEventId} onChange={(e) => onSelectEvent(e.target.value)}>
        {events.map((event) => (
          <option key={event.id} value={event.id}>
            {event.name} · {event.duration} min · {priceLabel(event, currency)}
          </option>
        ))}
      </select>
      {selectedEvent?.location && isVideoKeyword(selectedEvent.location) && (
        <div className="event-location-line"><I.video size={13}/>{friendlyLocation(selectedEvent.location)} · link sent after booking</div>
      )}
      {selectedEvent?.location && !isVideoKeyword(selectedEvent.location) && !isPhysicalLocation(selectedEvent.location) && (
        <div className="event-location-line"><I.location size={13}/>{selectedEvent.location}</div>
      )}
      <LocationMap location={selectedEvent?.location}/>
    </div>
  );
}

const STAFF_COLORS = ["#7aa7ff","#6ee7a3","#f59e9e","#a78bfa","#fbbf24","#34d399","#f472b6","#60a5fa"];

function StaffPicker({ staffList, selectedStaffId, onSelect }) {
  return (
    <div className="staff-picker-section">
      <h4 className="bk-section-h">Who would you like to book with?</h4>
      <div className="staff-picker-grid">
        {staffList.map(s => (
          <button key={s.id}
            className={"staff-pick-card " + (selectedStaffId === s.id ? "is-selected" : "")}
            onClick={() => onSelect(s.id)}>
            <div className="staff-pick-avatar" style={{background: s.color || "#7aa7ff"}}>
              {s.photoUrl ? <img src={s.photoUrl} alt={s.name}/> : s.name.slice(0, 2).toUpperCase()}
            </div>
            <div className="staff-pick-name">{s.name}</div>
            {s.bio && <div className="staff-pick-bio">{s.bio}</div>}
          </button>
        ))}
        {staffList.length > 1 && (
          <button
            className={"staff-pick-card is-any " + (selectedStaffId === "any" ? "is-selected" : "")}
            onClick={() => onSelect("any")}>
            <div className="staff-pick-avatar" style={{background:"var(--surface-3)"}}>
              <I.users size={18} style={{color:"var(--text-muted)"}}/>
            </div>
            <div className="staff-pick-name">No preference</div>
          </button>
        )}
      </div>
    </div>
  );
}

function WaitlistPanel({ slot, event, form, setForm, errors, busy, onSubmit, onCancel }) {
  const upd = (k, v) => setForm(f => ({ ...f, [k]: v }));
  return (
    <div>
      <div className="waitlist-panel-header">
        <div className="waitlist-panel-badge"><I.clock size={13}/> Joining waitlist</div>
        <div className="waitlist-slot-info">{event?.name} · {slot.time} · {formatIsoDate(slot.date, { weekday: "long" })}</div>
      </div>
      <p className="waitlist-panel-desc">This slot is full. Leave your details and we'll email you if a space opens up.</p>
      <h4 className="bk-section-h">Your details</h4>
      <div className="bk-form">
        <div className="bk-field">
          <label>Full name <span className="req">*</span></label>
          <input className={"bk-input " + (errors.name ? "invalid" : "")}
            placeholder="Enter your full name"
            value={form.name} onChange={e => upd("name", e.target.value)}/>
        </div>
        <div className="bk-field">
          <label>Email <span className="req">*</span></label>
          <input className={"bk-input " + (errors.email ? "invalid" : "")}
            type="text" inputMode="email" placeholder="Enter your email address"
            value={form.email} onChange={e => upd("email", e.target.value)}/>
        </div>
        <div className="bk-field">
          <label>Phone <span className="opt">(optional)</span></label>
          <input className="bk-input" type="tel" placeholder="+44 7700 900000"
            value={form.phone} onChange={e => upd("phone", e.target.value)}/>
        </div>
        <div className="bk-field">
          <label>Notes <span className="opt">(optional)</span></label>
          <textarea className="bk-textarea" maxLength={300}
            placeholder="Anything useful to know?"
            value={form.notes} onChange={e => upd("notes", e.target.value)}/>
        </div>
        <button className="bk-confirm-btn" onClick={onSubmit} disabled={busy}>
          {busy ? "Joining…" : <><I.clock size={13}/> Join waitlist</>}
        </button>
        <button className="btn ghost" style={{width:"100%", marginTop:8}} onClick={onCancel}>
          Cancel — see other times
        </button>
        <div className="bk-secure">
          <I.shield size={13}/>
          <span>We'll only contact you about this waitlist spot.</span>
        </div>
      </div>
    </div>
  );
}

function WaitlistConfirmation({ entry, profile, onAnother, onBack }) {
  if (!entry) return null;
  return (
    <div className="public">
      <div className="public-bar">
        <div className="brand">
          <div className="sb-mark" style={{width:24,height:24,fontSize:12}}>{profile.logoUrl ? <img src={profile.logoUrl} alt=""/> : getLogoText(profile)}</div>
          <span>{profile.brandName}</span>
        </div>
        <button className="public-back" onClick={onBack}><I.chevL size={13}/> Back to dashboard</button>
      </div>
      <div className="public-body" style={{display:"flex", alignItems:"center"}}>
        <div className="confirm-card">
          <div className="confirm-icon" style={{background:"rgba(239,164,3,0.14)"}}>
            <I.clock size={32} style={{color:"var(--accent)"}}/>
          </div>
          <h2>You're on the waitlist.</h2>
          <p className="sub">We'll email <strong style={{color:"var(--text)"}}>{entry.guestEmail}</strong> if a space opens up.</p>
          <dl className="confirm-detail">
            <dt>Service</dt><dd>{entry.eventName}</dd>
            <dt>Date</dt><dd>{formatIsoDate(entry.date, { weekday: "long", year: true })}</dd>
            <dt>Time</dt><dd>{entry.time}</dd>
            <dt>With</dt><dd>{profile.name}</dd>
          </dl>
          <div className="confirm-actions">
            <button onClick={onAnother}>Back to booking</button>
            <button className="primary" onClick={onBack}>Done</button>
          </div>
        </div>
      </div>
      {!profile.whiteLabel && (
        <div className="public-foot">
          <div>Powered by <strong style={{color:"var(--text)"}}>Nexus Booking</strong></div>
          <div className="links"><a>Privacy</a><a>Terms</a></div>
        </div>
      )}
    </div>
  );
}

// ── PayPal SDK dynamic loader ─────────────────────────────────
const _paypalLoaded = {};
function loadPaypalSdk(clientId, currency) {
  const key = clientId + "_" + (currency || "GBP").toUpperCase();
  if (_paypalLoaded[key] || (window.paypal && _paypalLoaded["any"])) return Promise.resolve();
  return new Promise((resolve, reject) => {
    const script = document.createElement("script");
    script.src = `https://www.paypal.com/sdk/js?client-id=${clientId}&currency=${(currency || "GBP").toUpperCase()}&intent=capture`;
    script.onload  = () => { _paypalLoaded[key] = true; _paypalLoaded["any"] = true; resolve(); };
    script.onerror = () => reject(new Error("Failed to load PayPal SDK"));
    document.head.appendChild(script);
  });
}

// Local helper — sort times chronologically (not exposed as global)
function timeToMins(t) {
  if (!t) return 0;
  const [hm, period] = t.split(" ");
  const [h, m] = hm.split(":").map(Number);
  let hours = h % 12; if (period === "PM") hours += 12;
  return hours * 60 + (m || 0);
}

function BookingPage({ onBack, data, onConfirmed, rightContent, initialForm }) {
  const today = new Date();
  const bookableEvents = data.events.filter((event) => event.enabled);
  const [selectedEventId, setSelectedEventId] = React.useState(bookableEvents[0]?.id || "");
  const [year, setYear] = React.useState(today.getFullYear());
  const [month, setMonth] = React.useState(today.getMonth());
  const [selectedDate, setSelectedDate] = React.useState(null);
  const [selectedTime, setSelectedTime] = React.useState(null);
  const [form, setForm] = React.useState({ name: "", email: "", phone: "", notes: "", answers: {}, acceptedTerms: false, paymentProvider: "", paymentComplete: false, paymentAmount: 0, ...(initialForm || {}) });
  const [errors, setErrors] = React.useState({});

  // Coupon state
  const [coupon,       setCoupon]       = React.useState(null);
  const [couponInput,  setCouponInput]  = React.useState("");
  const [couponError,  setCouponError]  = React.useState("");
  const [couponBusy,   setCouponBusy]   = React.useState(false);

  // Gift voucher state
  const [voucher,       setVoucher]       = React.useState(null);
  const [voucherInput,  setVoucherInput]  = React.useState("");
  const [voucherError,  setVoucherError]  = React.useState("");
  const [voucherBusy,   setVoucherBusy]   = React.useState(false);

  // Package session state
  const [packagePurchase, setPackagePurchase] = React.useState(null);
  const [packageEmail,    setPackageEmail]    = React.useState("");
  const [packageOptions,  setPackageOptions]  = React.useState(null); // null = not searched, [] = no results
  const [packageBusy,     setPackageBusy]     = React.useState(false);

  // Membership session state
  const [memberSub,         setMemberSub]         = React.useState(null);
  const [memberEmail,       setMemberEmail]       = React.useState("");
  const [memberOptions,     setMemberOptions]     = React.useState(null);
  const [memberBusy,        setMemberBusy]        = React.useState(false);

  // Waitlist state
  const [waitlistSlot,   setWaitlistSlot]   = React.useState(null);
  const [waitlistEntry,  setWaitlistEntry]  = React.useState(null);
  const [waitlistForm,   setWaitlistForm]   = React.useState({ name: "", email: "", phone: "", notes: "" });
  const [waitlistErrors, setWaitlistErrors] = React.useState({});
  const [waitlistBusy,   setWaitlistBusy]   = React.useState(false);

  // Staff routing state
  const [selectedStaffId, setSelectedStaffId] = React.useState(null);

  const eventStaff = React.useMemo(() =>
    (data.staff || []).filter(s => s.active && (s.serviceIds || []).includes(selectedEventId)),
    [data.staff, selectedEventId]
  );
  const hasStaffRouting = eventStaff.length > 0;
  const selectedStaff   = (selectedStaffId && selectedStaffId !== "any")
    ? eventStaff.find(s => s.id === selectedStaffId) || null
    : null;

  // Auto-select when there's only one staff member
  React.useEffect(() => {
    if (eventStaff.length === 1) setSelectedStaffId(eventStaff[0].id);
    else if (eventStaff.length === 0) setSelectedStaffId(null);
  }, [selectedEventId]);

  // ── Availability callback closures ───────────────────────────
  const getTimesForDate = React.useCallback((dateIso) => {
    if (!dateIso || !selectedEvent) return [];
    if (!hasStaffRouting || !selectedStaffId) {
      return getAvailableTimes({ data, event: selectedEvent, dateIso });
    }
    if (selectedStaffId === "any") {
      const all = new Set();
      eventStaff.forEach(s =>
        getAvailableTimes({ data, event: selectedEvent, dateIso, staff: s }).forEach(t => all.add(t))
      );
      return [...all].sort((a, b) => timeToMins(a) - timeToMins(b));
    }
    return getAvailableTimes({ data, event: selectedEvent, dateIso, staff: selectedStaff });
  }, [data, selectedEvent, hasStaffRouting, selectedStaffId, selectedStaff, eventStaff]);

  const getFullTimesForDate = React.useCallback((dateIso) => {
    if (!dateIso || !selectedEvent) return [];
    if (Number(selectedEvent.maxSpaces || 0) === 0) return []; // not a class
    if (!hasStaffRouting || !selectedStaffId) {
      return getFullTimes({ data, event: selectedEvent, dateIso });
    }
    if (selectedStaffId === "any") {
      const all = new Set();
      eventStaff.forEach(s =>
        getFullTimes({ data, event: selectedEvent, dateIso, staff: s }).forEach(t => all.add(t))
      );
      return [...all].sort((a, b) => timeToMins(a) - timeToMins(b));
    }
    return getFullTimes({ data, event: selectedEvent, dateIso, staff: selectedStaff });
  }, [data, selectedEvent, hasStaffRouting, selectedStaffId, selectedStaff, eventStaff]);

  const selectedEvent   = bookableEvents.find((event) => event.id === selectedEventId) || bookableEvents[0];
  const isClassMode     = Number(selectedEvent?.maxSpaces || 0) > 0;
  const dateIso         = selectedDate ? datePartsToIso(selectedDate) : null;
  const baseAmount      = getPaymentDue(selectedEvent);
  const discountAmount  = coupon ? computeDiscount(coupon, baseAmount) : 0;
  const voucherAmount   = voucher ? Math.min(voucher.amount, Math.max(0, baseAmount - discountAmount)) : 0;
  const packageCovers   = !!packagePurchase || !!memberSub; // pre-paid sessions — no charge
  const effectiveAmount = packageCovers ? 0 : Math.max(0, baseAmount - discountAmount - voucherAmount);

  const handlePrev = () => {
    setMonth(m => { if (m === 0) { setYear(y => y - 1); return 11; } return m - 1; });
  };
  const handleNext = () => {
    setMonth(m => { if (m === 11) { setYear(y => y + 1); return 0; } return m + 1; });
  };

  const handleApplyCoupon = async () => {
    if (!couponInput.trim()) return;
    setCouponBusy(true); setCouponError("");
    const result = await validateCoupon(couponInput, data.userId);
    if (result.valid) {
      setCoupon(result.coupon);
      setForm(f => ({ ...f, paymentComplete: false, paymentAmount: 0 }));
    } else {
      setCouponError(result.error);
    }
    setCouponBusy(false);
  };

  const handleRemoveCoupon = () => {
    setCoupon(null); setCouponInput(""); setCouponError("");
    setForm(f => ({ ...f, paymentComplete: false, paymentAmount: 0 }));
  };

  const handleApplyVoucher = async () => {
    if (!voucherInput.trim()) return;
    setVoucherBusy(true); setVoucherError("");
    const result = await validateGiftVoucherCode(voucherInput, data.userId);
    if (result.valid) {
      setVoucher(result.voucher);
      setForm(f => ({ ...f, paymentComplete: false, paymentAmount: 0 }));
    } else {
      setVoucherError(result.error);
    }
    setVoucherBusy(false);
  };

  const handleRemoveVoucher = () => {
    setVoucher(null); setVoucherInput(""); setVoucherError("");
    setForm(f => ({ ...f, paymentComplete: false, paymentAmount: 0 }));
  };

  const handleLookupPackage = async () => {
    if (!packageEmail.trim()) return;
    setPackageBusy(true);
    const results = await lookupPackagesByEmail(packageEmail, data.userId);
    setPackageOptions(results);
    setPackageBusy(false);
  };

  const handleSelectPackage = (pp) => {
    setPackagePurchase(pp);
    setPackageOptions(null);
    setForm(f => ({ ...f, paymentComplete: true, paymentAmount: 0, paymentProvider: "Package" }));
  };

  const handleRemovePackage = () => {
    setPackagePurchase(null);
    setPackageOptions(null);
    setPackageEmail("");
    setForm(f => ({ ...f, paymentComplete: false, paymentAmount: 0, paymentProvider: "" }));
  };

  const handleLookupMembership = async () => {
    if (!memberEmail.trim()) return;
    setMemberBusy(true);
    const results = await lookupMembershipsByEmail(memberEmail, data.userId);
    setMemberOptions(results);
    setMemberBusy(false);
  };

  const handleSelectMembership = (sub) => {
    setMemberSub(sub);
    setMemberOptions(null);
    setForm(f => ({ ...f, paymentComplete: true, paymentAmount: 0, paymentProvider: "Membership" }));
  };

  const handleRemoveMembership = () => {
    setMemberSub(null);
    setMemberOptions(null);
    setMemberEmail("");
    setForm(f => ({ ...f, paymentComplete: false, paymentAmount: 0, paymentProvider: "" }));
  };

  const validEmail          = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email.trim());
  const requiredAnswersDone = (selectedEvent?.questions || [])
    .filter((q) => q.required)
    .every((q) => String(form.answers?.[q.id] || "").trim().length > 0);
  const termsDone    = !selectedEvent?.requireTerms || !!form.acceptedTerms;
  const paymentDone  = !effectiveAmount || !!form.paymentComplete;
  const staffReady   = !hasStaffRouting || !!selectedStaffId;
  const canSubmit    = staffReady && !!selectedEvent && !!selectedDate && !!selectedTime
    && form.name.trim().length > 1 && validEmail && requiredAnswersDone && termsDone && paymentDone;

  const handleWaitlistSubmit = async () => {
    const errs = {};
    if (waitlistForm.name.trim().length < 2) errs.name = true;
    if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(waitlistForm.email.trim())) errs.email = true;
    setWaitlistErrors(errs);
    if (Object.keys(errs).length > 0) return;
    setWaitlistBusy(true);
    const entry = {
      id:         uid("wl"),
      userId:     data.userId,
      eventId:    selectedEvent?.id,
      eventName:  selectedEvent?.name,
      date:       waitlistSlot.date,
      time:       waitlistSlot.time,
      guestName:  waitlistForm.name.trim(),
      guestEmail: waitlistForm.email.trim(),
      guestPhone: waitlistForm.phone  || "",
      notes:      waitlistForm.notes  || "",
    };
    await joinWaitlist(entry);
    sendBookingEmail("waitlist_confirmation", entry, data.profile);
    setWaitlistBusy(false);
    setWaitlistEntry(entry);
  };

  const submit = () => {
    const errs = {};
    if (form.name.trim().length < 2) errs.name = true;
    if (!validEmail) errs.email = true;
    (selectedEvent?.questions || []).forEach((q) => {
      if (q.required && !String(form.answers?.[q.id] || "").trim()) errs[`answer-${q.id}`] = true;
    });
    if (selectedEvent?.requireTerms && !form.acceptedTerms) errs.terms = true;
    if (effectiveAmount && !form.paymentComplete) errs.payment = true;
    setErrors(errs);
    if (Object.keys(errs).length === 0 && selectedDate && selectedTime) {
      let paymentStatusLabel = "Not required";
      if (memberSub)             paymentStatusLabel = "Membership session";
      else if (packageCovers)   paymentStatusLabel = "Package session";
      else if (effectiveAmount > 0)    paymentStatusLabel = "Paid";
      else if (baseAmount > 0)         paymentStatusLabel = voucherAmount > 0 ? "Covered by voucher" : "Free (coupon)";
      onConfirmed({
        date: datePartsToIso(selectedDate), time: selectedTime,
        name: form.name, email: form.email, phone: form.phone, notes: form.notes, answers: form.answers,
        paymentStatus: paymentStatusLabel,
        paymentProvider: form.paymentProvider,
        paymentAmount: effectiveAmount,
        couponCode:  coupon?.code   || "",
        couponId:    coupon?.id     || "",
        discountApplied: discountAmount,
        voucherCode:   voucher?.code || "",
        voucherId:     voucher?.id   || "",
        voucherAmount: voucherAmount,
        packagePurchaseId:      packagePurchase?.id || "",
        packageName:            packagePurchase?.packageName || "",
        memberSubscriptionId:   memberSub?.id || "",
        membershipName:         memberSub?.membershipName || "",
        eventId:   selectedEvent.id,
        service:   selectedEvent.name,
        staffId:   selectedStaff?.id   || "",
        staffName: selectedStaff?.name || "",
      });
    }
  };

  // Waitlist confirmed → show confirmation screen
  if (waitlistEntry) {
    return <WaitlistConfirmation entry={waitlistEntry} profile={data.profile}
      onBack={onBack}
      onAnother={() => { setWaitlistEntry(null); setWaitlistSlot(null); setWaitlistForm({ name:"",email:"",phone:"",notes:"" }); }}/>;
  }

  return (
    <div className="public">
      <div className="public-bar">
        <div className="brand">
          <div className="sb-mark" style={{width:24, height:24, fontSize:12}}>{data.profile.logoUrl ? <img src={data.profile.logoUrl} alt=""/> : getLogoText(data.profile)}</div>
          <span>{data.profile.brandName}</span>
        </div>
        {rightContent != null ? rightContent : (
          <button className="public-back" onClick={onBack}>
            <I.chevL size={13}/> Back to dashboard
          </button>
        )}
      </div>

      <div className="public-body">
        <div className="bk-hero">
          <div className="avatar lg">{data.profile.initials}</div>
          <h2>Book with {data.profile.name}</h2>
          <p>{data.profile.bio}</p>
        </div>

        {bookableEvents.length > 0 ? (
          <EventSummary events={bookableEvents} selectedEventId={selectedEventId} selectedEvent={selectedEvent} currency={data.profile.currency}
            onSelectEvent={(id) => {
              setSelectedEventId(id);
              setSelectedDate(null);
              setSelectedTime(null);
              setForm((f) => ({ ...f, paymentProvider: "", paymentComplete: false, paymentAmount: 0 }));
            }}/>
        ) : (
          <div className="booking-event-card">No appointment types are currently available.</div>
        )}

        {hasStaffRouting && (
          <StaffPicker
            staffList={eventStaff}
            selectedStaffId={selectedStaffId}
            onSelect={(id) => {
              setSelectedStaffId(id);
              setSelectedDate(null);
              setSelectedTime(null);
              setWaitlistSlot(null);
            }}/>
        )}

        <div className={"bk-grid" + (hasStaffRouting && !selectedStaffId ? " is-locked" : "")}>
          <Calendar year={year} month={month}
            getTimesFn={getTimesForDate}
            getFullTimesFn={getFullTimesForDate}
            selectedDate={selectedDate}
            onSelectDate={(d) => { setSelectedDate(d); setSelectedTime(null); setWaitlistSlot(null); }}
            onPrev={handlePrev} onNext={handleNext}/>
          <TimesList
            selectedDate={selectedDate}
            getTimesFn={getTimesForDate}
            getFullTimesFn={getFullTimesForDate}
            isClassMode={isClassMode}
            data={data}
            event={selectedEvent}
            dateIso={dateIso}
            selectedTime={selectedTime}
            onSelectTime={(t) => { if (selectedDate) { setSelectedTime(t); setWaitlistSlot(null); } }}
            onJoinWaitlist={(slot) => { setWaitlistSlot(slot); setSelectedTime(null); }}/>
          {waitlistSlot ? (
            <WaitlistPanel
              slot={waitlistSlot}
              event={selectedEvent}
              form={waitlistForm}
              setForm={setWaitlistForm}
              errors={waitlistErrors}
              busy={waitlistBusy}
              onSubmit={handleWaitlistSubmit}
              onCancel={() => setWaitlistSlot(null)}/>
          ) : (
            <YourDetails form={form} setForm={setForm} event={selectedEvent} data={data} errors={errors}
              onSubmit={submit} canSubmit={canSubmit}
              coupon={coupon} couponInput={couponInput} setCouponInput={setCouponInput}
              couponError={couponError} setCouponError={setCouponError}
              couponBusy={couponBusy} onApplyCoupon={handleApplyCoupon} onRemoveCoupon={handleRemoveCoupon}
              voucher={voucher} voucherInput={voucherInput} setVoucherInput={setVoucherInput}
              voucherError={voucherError} voucherBusy={voucherBusy}
              onApplyVoucher={handleApplyVoucher} onRemoveVoucher={handleRemoveVoucher}
              packagePurchase={packagePurchase} packageEmail={packageEmail} setPackageEmail={setPackageEmail}
              packageOptions={packageOptions} packageBusy={packageBusy}
              onLookupPackage={handleLookupPackage} onSelectPackage={handleSelectPackage} onRemovePackage={handleRemovePackage}
              memberSub={memberSub} memberEmail={memberEmail} setMemberEmail={setMemberEmail}
              memberOptions={memberOptions} memberBusy={memberBusy}
              onLookupMembership={handleLookupMembership} onSelectMembership={handleSelectMembership} onRemoveMembership={handleRemoveMembership}
              effectiveAmount={effectiveAmount}/>
          )}
        </div>

        {hasStaffRouting && !selectedStaffId && (
          <div style={{maxWidth:600, margin:"12px auto 0", textAlign:"center"}}>
            <div className="muted" style={{fontSize:13}}>↑ Select who you'd like to book with to see available times.</div>
          </div>
        )}

        {(selectedDate || selectedTime) && (
          <div style={{maxWidth:600, margin:"24px auto 0", textAlign:"center"}}>
            <div className="muted" style={{fontSize:12, marginBottom:6}}>You're booking</div>
            <div style={{fontSize:14, fontWeight:500}}>
              {selectedEvent?.name}
              {selectedStaff ? ` · with ${selectedStaff.name}` : ""}
              {" · "}{selectedDate ? dateLabel(selectedDate) : "—"}
              {selectedTime ? ` · ${selectedTime}` : ""}
            </div>
          </div>
        )}
      </div>

      <PublicReviews reviews={data.reviews || []}/>

      {!data.profile.whiteLabel && (
        <div className="public-foot">
          <div>Powered by <strong style={{color:"var(--text)"}}>Nexus Booking</strong></div>
          <div className="links">
            <a>Privacy</a><a>Terms</a>
          </div>
        </div>
      )}
    </div>
  );
}

// ── Public reviews (shown on booking page when 3+ reviews exist) ───
function PublicReviews({ reviews }) {
  if (!reviews || reviews.length < 3) return null;

  const total = reviews.length;
  const avg   = Math.round((reviews.reduce((s, r) => s + r.rating, 0) / total) * 10) / 10;

  const StarRow = ({ rating, size = 14 }) => (
    <div className="public-reviews-stars">
      {[1,2,3,4,5].map(n => (
        <svg key={n} width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinejoin="round">
          <polygon points="12,2.5 15.09,8.75 22,9.75 17,14.6 18.18,21.5 12,18.25 5.82,21.5 7,14.6 2,9.75 8.91,8.75"
            style={{fill: n <= rating ? "#f59e0b" : "none", stroke: n <= rating ? "#f59e0b" : "var(--text-faint)"}}/>
        </svg>
      ))}
    </div>
  );

  return (
    <div className="public-reviews">
      <div className="public-reviews-h">
        <div className="public-reviews-avg-num">{avg.toFixed(1)}</div>
        <div className="public-reviews-meta">
          <StarRow rating={Math.round(avg)}/>
          <div className="public-reviews-count">{total} review{total !== 1 ? "s" : ""}</div>
        </div>
      </div>
      {reviews.slice(0, 6).map(r => (
        <div key={r.id} className="review-item">
          <div className="review-item-h">
            <div className="review-item-meta">
              <div className="review-item-name">{r.guestName}</div>
              {r.eventName && <div className="review-item-service">{r.eventName}</div>}
            </div>
            <StarRow rating={r.rating} size={12}/>
          </div>
          {r.comment && <div className="review-item-comment">"{r.comment}"</div>}
        </div>
      ))}
    </div>
  );
}

function Confirmation({ booking, profile, onAnother, onBack, rightContent }) {
  if (!booking) return null;
  return (
    <div className="public">
      <div className="public-bar">
        <div className="brand">
          <div className="sb-mark" style={{width:24, height:24, fontSize:12}}>{profile.logoUrl ? <img src={profile.logoUrl} alt=""/> : getLogoText(profile)}</div>
          <span>{profile.brandName}</span>
        </div>
        {rightContent != null ? rightContent : (
          <button className="public-back" onClick={onBack}>
            <I.chevL size={13}/> Back to dashboard
          </button>
        )}
      </div>
      <div className="public-body" style={{display:"flex", alignItems:"center"}}>
        <div className="confirm-card">
          <div className="confirm-icon"><I.check size={32}/></div>
          <h2>You're booked in.</h2>
          <p className="sub">A confirmation has been sent to <strong style={{color:"var(--text)"}}>{booking.guestEmail}</strong>.</p>
          <dl className="confirm-detail">
            <dt>Service</dt><dd>{booking.eventName}</dd>
            <dt>Date</dt><dd>{formatIsoDate(booking.date, { weekday: "long", year: true })}</dd>
            <dt>Time</dt><dd>{booking.time}</dd>
            <dt>With</dt><dd>{profile.name}</dd>
            {booking.location && !isVideoKeyword(booking.location) && <><dt>Location</dt><dd>{booking.location}</dd></>}
            {booking.location && isVideoKeyword(booking.location) && <><dt>Location</dt><dd>{friendlyLocation(booking.location)} · link sent to your email</dd></>}
            {booking.paymentStatus && <><dt>Payment</dt><dd>{booking.paymentStatus}{booking.paymentProvider ? ` · ${booking.paymentProvider}` : ""}{booking.paymentAmount ? ` · ${formatMoney(booking.paymentAmount, profile.currency)}` : ""}</dd></>}
          </dl>
          <LocationMap location={booking.location}/>
          {booking.manageToken && (
            <div className="manage-link-box">
              <I.cal size={14}/>
              <div>
                <div style={{fontWeight:600, marginBottom:2}}>Need to change your booking?</div>
                <a href={`${BOOK_ORIGIN}/?manage=${booking.manageToken}`} target="_blank" rel="noopener noreferrer" style={{color:"var(--accent)", fontSize:13}}>Reschedule or cancel →</a>
              </div>
            </div>
          )}
          <div className="confirm-actions">
            <button onClick={onAnother}>Book another</button>
            <button className="primary" onClick={onBack}>Done</button>
          </div>
        </div>
      </div>
      {!profile.whiteLabel && (
        <div className="public-foot">
          <div>Powered by <strong style={{color:"var(--text)"}}>Nexus Booking</strong></div>
          <div className="links"><a>Privacy</a><a>Terms</a></div>
        </div>
      )}
    </div>
  );
}

// Returns true if the guest is allowed to cancel based on the event's policy.
function isCancellationAllowed(booking, event) {
  if (!event) return true;
  const policy = event.cancellationPolicy || "flexible";
  if (policy === "none") return false;
  // "flexible" — check hours cutoff
  const hours = Number(event.cancellationHours ?? 24);
  if (!hours) return true; // 0 hours = always cancellable
  const m = (booking.time || "").match(/(\d+):(\d+)\s*(AM|PM)/i);
  if (!m) return true;
  let h = Number(m[1]);
  const min = Number(m[2]);
  if (m[3].toUpperCase() === "PM" && h !== 12) h += 12;
  if (m[3].toUpperCase() === "AM" && h === 12) h = 0;
  const bookingMs = new Date(`${booking.date}T${String(h).padStart(2,"0")}:${String(min).padStart(2,"0")}:00`).getTime();
  return Date.now() + hours * 3_600_000 < bookingMs;
}

// ── Customer manage page (?manage=TOKEN) ─────────────────────
function ManageBookingPage({ token }) {
  const [state, setState]   = React.useState("loading"); // loading | ready | rescheduling | done | error
  const [result, setResult] = React.useState(null);      // { booking, publicData }
  const [action, setAction] = React.useState(null);      // "cancel" | "reschedule"

  // Reschedule picker state
  const today = new Date();
  const [year, setYear]   = React.useState(today.getFullYear());
  const [month, setMonth] = React.useState(today.getMonth());
  const [newDate, setNewDate] = React.useState(null);
  const [newTime, setNewTime] = React.useState(null);
  const [saving, setSaving]   = React.useState(false);
  const [doneMsg, setDoneMsg] = React.useState("");

  React.useEffect(() => {
    fetchBookingByToken(token).then(r => {
      if (!r) setState("error");
      else { setResult(r); setState("ready"); }
    });
  }, [token]);

  if (state === "loading") return <div className="loading-shell"><div className="loading-dots"><span/><span/><span/></div></div>;
  if (state === "error")   return (
    <div className="login-shell">
      <div className="login-card" style={{textAlign:"center"}}>
        <img src="icon/nexus-bookings-icon.png" alt="" style={{width:56, height:56, display:"block", margin:"0 auto 16px"}}/>
        <h2>Booking not found</h2>
        <p style={{color:"var(--text-muted)", marginTop:8}}>This link may have expired or the booking no longer exists.</p>
      </div>
    </div>
  );

  const { booking, publicData } = result;
  const { profile } = publicData;
  const isCancelled = booking.status === "Cancelled";

  if (state === "done") return (
    <div style={getBrandStyle(profile)}>
      <div className="public">
        <div className="public-bar">
          <div className="brand">
            <div className="sb-mark" style={{width:24,height:24,fontSize:12}}>{profile.logoUrl ? <img src={profile.logoUrl} alt=""/> : getLogoText(profile)}</div>
            <span>{profile.brandName}</span>
          </div>
        </div>
        <div className="public-body" style={{display:"flex", alignItems:"center"}}>
          <div className="confirm-card">
            <div className="confirm-icon"><I.check size={32}/></div>
            <h2>{doneMsg}</h2>
            {action === "reschedule" && (
              <dl className="confirm-detail">
                <dt>Service</dt><dd>{booking.eventName}</dd>
                <dt>New date</dt><dd>{formatIsoDate(newDate, { weekday:"long", year:true })}</dd>
                <dt>New time</dt><dd>{newTime}</dd>
              </dl>
            )}
            <div className="confirm-actions" style={{marginTop:24}}>
              <button className="primary" onClick={() => window.location.href = getBookingUrl(profile.bookingUrl)}>Book again</button>
            </div>
          </div>
        </div>
      </div>
    </div>
  );

  // Cancellation policy check
  const event = publicData.events.find(e => e.id === booking.eventId) || publicData.events[0];
  const canCancel = !isCancelled && isCancellationAllowed(booking, event);
  const dataWithoutBooking = { ...publicData, bookings: publicData.bookings.filter(b => b.id !== booking.id) };

  const daysInMonth = new Date(year, month + 1, 0).getDate();
  const firstDay    = (new Date(year, month, 1).getDay() + 6) % 7; // Mon=0
  const MONTHS      = ["January","February","March","April","May","June","July","August","September","October","November","December"];
  const todayIsoStr = todayIso();

  const handleCancel = async () => {
    if (!confirm("Are you sure you want to cancel this booking?")) return;
    setSaving(true);
    await sb.from("bookings").update({ status: "Cancelled" }).eq("manage_token", token);
    setResult(r => ({ ...r, booking: { ...r.booking, status: "Cancelled" } }));
    setAction("cancel");
    setDoneMsg("Your booking has been cancelled.");
    setState("done");
    setSaving(false);
  };

  const handleReschedule = async () => {
    if (!newDate || !newTime) return;
    setSaving(true);
    await sb.from("bookings").update({ date: newDate, time: newTime, status: "Rescheduled" }).eq("manage_token", token);
    setAction("reschedule");
    setDoneMsg("Your booking has been rescheduled.");
    setState("done");
    setSaving(false);
  };

  const slots = React.useMemo(() => {
    if (!event || !newDate) return [];
    return getAvailableTimes({ data: dataWithoutBooking, event, dateIso: newDate });
  }, [newDate, event]);

  return (
    <div style={getBrandStyle(profile)}>
      <div className="public">
        <div className="public-bar">
          <div className="brand">
            <div className="sb-mark" style={{width:24,height:24,fontSize:12}}>{profile.logoUrl ? <img src={profile.logoUrl} alt=""/> : getLogoText(profile)}</div>
            <span>{profile.brandName}</span>
          </div>
        </div>
        <div className="public-body" style={{display:"flex", alignItems:"center"}}>
          <div className="confirm-card" style={{maxWidth:480}}>
            <h2 style={{marginBottom:4}}>Manage your booking</h2>
            <p className="sub" style={{marginBottom:20}}>with {profile.brandName}</p>

            <dl className="confirm-detail" style={{marginBottom:20}}>
              <dt>Service</dt><dd>{booking.eventName}</dd>
              <dt>Date</dt><dd>{formatIsoDate(booking.date, { weekday:"long", year:true })}</dd>
              <dt>Time</dt><dd>{booking.time}</dd>
              <dt>Status</dt><dd><span className={"pill " + booking.status.toLowerCase()}>{booking.status}</span></dd>
            </dl>

            {isCancelled ? (
              <p style={{color:"var(--text-muted)", textAlign:"center", padding:"12px 0"}}>This booking has already been cancelled.</p>
            ) : action !== "reschedule" ? (
              <>
                <div style={{display:"flex", gap:10, marginTop:8}}>
                  <button className="bk-confirm-btn" style={{background:"var(--surface-2)", color:"var(--text)", flex:1}} onClick={() => setAction("reschedule")}>
                    <I.cal size={14}/> Reschedule
                  </button>
                  {canCancel && (
                    <button className="bk-confirm-btn" style={{background:"#fee2e2", color:"#dc2626", flex:1}} onClick={handleCancel} disabled={saving}>
                      {saving ? "Cancelling…" : "Cancel booking"}
                    </button>
                  )}
                </div>
                {!canCancel && (
                  <div className="bk-policy-notice bk-policy-strict" style={{marginTop:12}}>
                    <I.shield size={13}/>
                    <span>
                      {event?.cancellationPolicy === "none"
                        ? "Cancellations are not permitted for this service."
                        : `Cancellations are no longer accepted within ${event?.cancellationHours || 24} hours of your appointment.`}
                    </span>
                  </div>
                )}
              </>
            ) : (
              <>
                <h3 style={{marginBottom:12}}>Pick a new date & time</h3>
                <div className="bk-calendar">
                  <div className="bk-cal-nav">
                    <button onClick={() => { if (month===0){setYear(y=>y-1);setMonth(11);}else setMonth(m=>m-1); }}><I.chevL size={14}/></button>
                    <span>{MONTHS[month]} {year}</span>
                    <button onClick={() => { if (month===11){setYear(y=>y+1);setMonth(0);}else setMonth(m=>m+1); }}><I.chev size={14}/></button>
                  </div>
                  <div className="bk-cal-grid">
                    {["Mo","Tu","We","Th","Fr","Sa","Su"].map(d=><div key={d} className="bk-cal-head">{d}</div>)}
                    {Array.from({length: firstDay}, (_,i) => <div key={"e"+i}/>)}
                    {Array.from({length: daysInMonth}, (_,i) => {
                      const d = i + 1;
                      const iso = `${year}-${String(month+1).padStart(2,"0")}-${String(d).padStart(2,"0")}`;
                      const isPast = iso < todayIsoStr;
                      const isSelected = iso === newDate;
                      const hasTimes = !isPast && event && getAvailableTimes({ data: dataWithoutBooking, event, dateIso: iso }).length > 0;
                      return (
                        <button key={d}
                          className={"bk-cal-day" + (isSelected?" is-selected":"") + (isPast||!hasTimes?" is-disabled":"")}
                          disabled={isPast || !hasTimes}
                          onClick={() => { setNewDate(iso); setNewTime(null); }}>
                          {d}
                        </button>
                      );
                    })}
                  </div>
                </div>
                {newDate && slots.length > 0 && (
                  <div className="bk-times" style={{marginTop:12}}>
                    {slots.map(s => (
                      <button key={s} className={"bk-time" + (newTime===s?" is-selected":"")} onClick={() => setNewTime(s)}>{s}</button>
                    ))}
                  </div>
                )}
                {newDate && slots.length === 0 && (
                  <p style={{color:"var(--text-muted)", textAlign:"center", padding:"12px 0", fontSize:13}}>No available times on this date. Try another day.</p>
                )}
                <div style={{display:"flex", gap:10, marginTop:16}}>
                  <button className="bk-confirm-btn" style={{background:"var(--surface-2)", color:"var(--text)", flex:1}} onClick={() => setAction(null)}>Back</button>
                  <button className="bk-confirm-btn" style={{flex:2}} onClick={handleReschedule} disabled={!newDate || !newTime || saving}>
                    {saving ? "Saving…" : "Confirm reschedule"}
                  </button>
                </div>
              </>
            )}
          </div>
        </div>
        {!profile.whiteLabel && (
          <div className="public-foot">
            <div>Powered by <strong style={{color:"var(--text)"}}>Nexus Booking</strong></div>
          </div>
        )}
      </div>
    </div>
  );
}

window.ManageBookingPage   = ManageBookingPage;
window.BookingPage         = BookingPage;
window.Confirmation        = Confirmation;
window.WaitlistConfirmation = WaitlistConfirmation;
