// scenarios.jsx — universal scenario layer applied across every flow.
// Components defined here are referenced by AppointmentFlow / ClassFlow /
// EventFlow / PackFlow / MembershipFlow / ConfirmationFlow.
//
// Each component is intentionally thin so the simplest case (new client books a
// class for themselves) remains zero-friction. Sections only appear when the
// universal scenario actually applies (e.g. logged_in tweak, waitlist, etc).

const { useState: useStateS, useEffect: useEffectS, useRef: useRefS } = React;

// ────────────────────────────────────────────────────────────────
// 1. WhoFor — "Who's this for?" attendee card picker.
//    Cards are visible up front (self + family-on-account). Tap to toggle
//    selection. Below the cards, "+ family member" and "+ bring a guest"
//    add new attendees inline. A running summary line confirms exactly who
//    is being reserved before payment — the verification moment that prevents
//    parents from accidentally booking themselves into kids' classes.
// ────────────────────────────────────────────────────────────────
// Parse a free-text date-of-birth and return the participant's age in years,
// or null if the string isn't a recognizable date. Used to gate adult-only
// fields (email/phone, SMS consent) on family member rows.
function ageFromDob(dob) {
  if (!dob || !dob.trim()) return null;
  const d = new Date(dob);
  if (isNaN(d.getTime())) return null;
  const today = new Date();
  let age = today.getFullYear() - d.getFullYear();
  const m = today.getMonth() - d.getMonth();
  if (m < 0 || (m === 0 && today.getDate() < d.getDate())) age--;
  return age;
}
function isAdultDob(dob) {
  const a = ageFromDob(dob);
  return a !== null && a >= 18;
}

function WhoFor({
  brand, value, onChange,
  allowFamily = true, allowGuest = true,
  loggedIn, accountFamily,
  customer, setCustomer,
  smsTransactional, setSmsTransactional,
  smsMarketing, setSmsMarketing,
}) {
  const v = value || { self: true, family: [], guests: [] };
  const update = patch => onChange({ ...v, ...patch });

  // Family on file — only surfaced for logged-in clients. Override per-account
  // when an `accountFamily` array is passed in; fall back to a generic example.
  const familyOnFile = loggedIn
    ? (accountFamily && accountFamily.length
        ? accountFamily.map(f => ({
            id: f.id,
            name: f.name.split(' ')[0],
            rel: f.relation || f.rel || 'Family',
            initials: (f.initials || f.name.split(' ').map(s => s[0]).join('')).slice(0, 2).toUpperCase(),
          }))
        : [
            { id: 'partner', name: 'Alex',  rel: 'Partner',     initials: 'A' },
            { id: 'lily',    name: 'Lily',  rel: 'Daughter, 8', initials: 'L' },
          ])
    : [];

  const [showAddFam, setShowAddFam]   = useStateS(false);
  const [showGuest, setShowGuest]     = useStateS(false);
  // Per-row shape now includes the per-person waiver state. Family members
  // need a signed waiver before booking; guests sign at check-in.
  const [addFams, setAddFams]         = useStateS([{ name: '', dob: '', email: '', phone: '', waiverChecked: false, waiverOpen: false, smsTransactional: true, smsMarketing: true }]);
  const [guestList, setGuestList]     = useStateS([{ name: '', dob: '', email: '', phone: '' }]);

  // Self-attendee details. In NLI flow we also capture the account holder's
  // birthdate + waiver here, so every attendee's waiver lives in one place.
  const [selfDob, setSelfDob]                   = useStateS('');
  const [selfWaiverChecked, setSelfWaiverChecked] = useStateS(loggedIn);  // returning clients have it on file
  const [selfWaiverOpen, setSelfWaiverOpen]     = useStateS(false);

  const updateFamRow  = (i, patch) => setAddFams(rows =>
    rows.map((r, idx) => idx === i ? { ...r, ...patch } : r));
  const removeFamRow  = (i)        => setAddFams(rows => rows.length === 1
    ? [{ name: '', dob: '', email: '', phone: '', waiverChecked: false, waiverOpen: false, smsTransactional: true, smsMarketing: true }]
    : rows.filter((_, idx) => idx !== i));
  const addFamRow     = ()         => setAddFams(rows => [...rows, { name: '', dob: '', email: '', phone: '', waiverChecked: false, waiverOpen: false, smsTransactional: true, smsMarketing: true }]);

  // Propagate filled-in inline family rows up to whoFor.addedFamily so the
  // parent step (and the order summary) knows about them. Done in an effect
  // so we never call setWhoFor inside an updater function for setAddFams.
  useEffectS(() => {
    const filtered = (showAddFam ? addFams : []).filter(r => r.name.trim()).map(r => ({ name: r.name, dob: r.dob }));
    onChange(prev => {
      const cur = prev.addedFamily || [];
      const same = cur.length === filtered.length
        && cur.every((r, i) => r.name === filtered[i].name && r.dob === filtered[i].dob);
      return same ? prev : { ...prev, addedFamily: filtered };
    });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [addFams, showAddFam]);

  const propagateGuests = (rows) => update({
    guests: rows.filter(g => g.name.trim()).map(g => ({ name: g.name, dob: g.dob, email: g.email, phone: g.phone })),
  });
  const updateGuestRow  = (i, patch) => {
    const next = guestList.map((r, idx) => idx === i ? { ...r, ...patch } : r);
    setGuestList(next);
    propagateGuests(next);
  };
  const removeGuestRow  = (i) => {
    const next = guestList.length === 1
      ? [{ name: '', dob: '', email: '', phone: '' }]
      : guestList.filter((_, idx) => idx !== i);
    setGuestList(next);
    propagateGuests(next);
  };
  const addGuestRow     = () => setGuestList(rows => [...rows, { name: '', dob: '', email: '', phone: '' }]);

  const toggleFam = (id) => {
    const has = v.family.includes(id);
    update({ family: has ? v.family.filter(x => x !== id) : [...v.family, id] });
  };
  const toggleSelf = () => update({ self: !v.self });

  // Surface "all required waivers signed" to the parent so the payment step
  // can gate the Book button. Logged-in attendees have waivers on file, so
  // they don't add to the required set. Guests sign at check-in.
  const filledFamRows = showAddFam ? addFams.filter(r => r.name.trim()) : [];
  const allWaiversSigned =
    (!v.self || loggedIn || selfWaiverChecked)
    && filledFamRows.every(r => r.waiverChecked);
  useEffectS(() => {
    onChange(prev => prev.allWaiversSigned !== allWaiversSigned
      ? { ...prev, allWaiversSigned }
      : prev);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [allWaiversSigned]);

  // Build the running summary — first names only, in the order they appear.
  const names = [];
  if (v.self) names.push('You');
  v.family.forEach(id => {
    const p = familyOnFile.find(f => f.id === id);
    if (p) names.push(p.name);
  });
  if (showAddFam) addFams.forEach(f => { if (f.name.trim()) names.push(f.name.trim().split(' ')[0]); });
  if (showGuest)  guestList.forEach(g => { if (g.name.trim()) names.push(g.name.trim().split(' ')[0]); });
  const count = names.length;

  return (
    <div style={{
      background: brand.surface, border: `1px solid ${brand.border}`,
      borderRadius: 12, padding: '12px 12px 14px',
    }}>
      {/* Card row — self always first, then family on file */}
      <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
        <AttendeeCard
          brand={brand}
          kind="self"
          label="You"
          sub="Account holder"
          selected={v.self}
          onToggle={toggleSelf}
        />
        {familyOnFile.map(p => (
          <AttendeeCard
            key={p.id}
            brand={brand}
            kind="family"
            label={p.name}
            sub={p.rel}
            initials={p.initials}
            selected={v.family.includes(p.id)}
            onToggle={() => toggleFam(p.id)}
          />
        ))}
      </div>

      {/* Running summary — verification moment */}
      <SummaryLine brand={brand} count={count} names={names} />

      {/* +family / +guest add-row */}
      {(allowFamily || allowGuest) && (
        <div style={{
          display: 'flex', flexWrap: 'wrap', gap: 16,
          marginTop: 10, paddingTop: 10, borderTop: `1px solid ${brand.border}`,
        }}>
          {allowFamily && (
            <MiniCheck brand={brand} checked={showAddFam} onChange={() => {
              const next = !showAddFam;
              setShowAddFam(next);
              if (!next) {
                setAddFams([{ name: '', dob: '', email: '', phone: '', waiverChecked: false, waiverOpen: false, smsTransactional: true, smsMarketing: true }]);
                update({ addedFamily: [] });
              }
            }}>
              {loggedIn ? '+ new family member' : '+ family member'}
            </MiniCheck>
          )}
          {allowGuest && (
            <MiniCheck brand={brand} checked={showGuest} onChange={() => {
              const next = !showGuest;
              setShowGuest(next);
              if (!next) { setGuestList([{ name: '', email: '' }]); update({ guests: [] }); }
            }}>
              + bring a guest
            </MiniCheck>
          )}
        </div>
      )}

      {/* Self details — booker contact info + birthdate + waiver, shown when
          the account holder is booking for themselves and they aren't already
          on file. Name/email/phone live here (not in the payment step) so
          they're tied to the participant rather than the cardholder — useful
          when someone else is paying. */}
      {v.self && !loggedIn && (
        <div style={{ marginTop: 12, paddingTop: 12, borderTop: `1px solid ${brand.border}` }}>
          <PersonHeader brand={brand} label="Your details" />
          <div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginTop: 8 }}>
            {customer && setCustomer && (
              <>
                <input
                  placeholder="Your full name"
                  value={customer.name || ''}
                  onChange={e => setCustomer({ ...customer, name: e.target.value })}
                  autoComplete="name"
                  style={inputStyle(brand)}
                />
                <input
                  placeholder="Your email"
                  type="email"
                  value={customer.email || ''}
                  onChange={e => setCustomer({ ...customer, email: e.target.value })}
                  autoComplete="email"
                  style={inputStyle(brand)}
                />
                <input
                  placeholder="Your phone"
                  type="tel"
                  value={customer.phone || ''}
                  onChange={e => setCustomer({ ...customer, phone: e.target.value })}
                  autoComplete="tel"
                  style={inputStyle(brand)}
                />
                {setSmsTransactional && setSmsMarketing && (
                  <SmsConsentRows
                    brand={brand}
                    transactional={smsTransactional} setTransactional={setSmsTransactional}
                    marketing={smsMarketing} setMarketing={setSmsMarketing}
                  />
                )}
              </>
            )}
            <input
              placeholder="Your date of birth"
              value={selfDob}
              onChange={e => setSelfDob(e.target.value)}
              style={inputStyle(brand)}
            />
          </div>
          <PersonWaiver
            brand={brand} brandName={brand.name}
            label={`Liability waiver — yourself`}
            checked={selfWaiverChecked}
            onChange={setSelfWaiverChecked}
            open={selfWaiverOpen}
            setOpen={setSelfWaiverOpen}
          />
        </div>
      )}

      {/* Family-add inline form */}
      {showAddFam && allowFamily && (
        <div style={{ marginTop: 12, display: 'flex', flexDirection: 'column', gap: 14 }}>
          {addFams.map((row, i) => (
            <div key={i} style={{
              display: 'flex', flexDirection: 'column', gap: 8, position: 'relative',
              paddingTop: 12, borderTop: `1px solid ${brand.border}`,
            }}>
              <PersonHeader
                brand={brand}
                label={`Family member ${addFams.length > 1 ? i + 1 : ''}`.trim()}
                onRemove={addFams.length > 1 ? () => removeFamRow(i) : null}
              />
              <input
                placeholder="Family member's full name"
                value={row.name}
                onChange={e => updateFamRow(i, { name: e.target.value })}
                style={inputStyle(brand)}
              />
              <input
                placeholder="Date of birth"
                value={row.dob}
                onChange={e => updateFamRow(i, { dob: e.target.value })}
                style={inputStyle(brand)}
              />
              {isAdultDob(row.dob) && (
                <>
                  <input
                    placeholder="Email (optional)"
                    type="email"
                    value={row.email || ''}
                    onChange={e => updateFamRow(i, { email: e.target.value })}
                    style={inputStyle(brand)}
                  />
                  <input
                    placeholder="Phone (optional)"
                    type="tel"
                    value={row.phone || ''}
                    onChange={e => updateFamRow(i, { phone: e.target.value })}
                    style={inputStyle(brand)}
                  />
                  <SmsConsentRows
                    brand={brand}
                    transactional={row.smsTransactional !== false}
                    setTransactional={(v) => updateFamRow(i, { smsTransactional: v })}
                    marketing={row.smsMarketing !== false}
                    setMarketing={(v) => updateFamRow(i, { smsMarketing: v })}
                  />
                </>
              )}
              <PersonWaiver
                brand={brand} brandName={brand.name}
                label={`Liability waiver${row.name.trim() ? ` — ${row.name.trim().split(' ')[0]}` : ''}`}
                checked={row.waiverChecked}
                onChange={(checked) => updateFamRow(i, { waiverChecked: checked })}
                open={row.waiverOpen}
                setOpen={(o) => updateFamRow(i, { waiverOpen: typeof o === 'function' ? o(row.waiverOpen) : o })}
              />
            </div>
          ))}
          <div style={{ fontSize: 11, color: brand.inkSoft, lineHeight: 1.5 }}>
            We'll save them — and their signed waiver — to your account so you can book them again with one tap next time.
          </div>
          <AddMoreLink brand={brand} onClick={addFamRow}>+ add another family member</AddMoreLink>
        </div>
      )}

      {/* Guest inline form — guests sign their waiver at check-in, so we
          collect birthdate + contact info but not a waiver signature. */}
      {showGuest && allowGuest && (
        <div style={{ marginTop: 12, display: 'flex', flexDirection: 'column', gap: 14 }}>
          {guestList.map((row, i) => (
            <div key={i} style={{
              display: 'flex', flexDirection: 'column', gap: 8,
              paddingTop: 12, borderTop: `1px solid ${brand.border}`,
            }}>
              <PersonHeader
                brand={brand}
                label={`Guest ${guestList.length > 1 ? i + 1 : ''}`.trim()}
                onRemove={guestList.length > 1 ? () => removeGuestRow(i) : null}
              />
              <input
                placeholder="Guest's full name"
                value={row.name}
                onChange={e => updateGuestRow(i, { name: e.target.value })}
                style={inputStyle(brand)}
              />
              <input
                placeholder="Date of birth"
                value={row.dob}
                onChange={e => updateGuestRow(i, { dob: e.target.value })}
                style={inputStyle(brand)}
              />
              <input
                placeholder="Guest's email (optional)"
                type="email"
                value={row.email}
                onChange={e => updateGuestRow(i, { email: e.target.value })}
                style={inputStyle(brand)}
              />
              <input
                placeholder="Guest's phone (optional)"
                type="tel"
                value={row.phone}
                onChange={e => updateGuestRow(i, { phone: e.target.value })}
                style={inputStyle(brand)}
              />
            </div>
          ))}
          <div style={{ fontSize: 11, color: brand.inkSoft, lineHeight: 1.5 }}>
            Their waiver will be requested at check-in. They won't be added to your account, but will have their own account for future bookings.
          </div>
          <AddMoreLink brand={brand} onClick={addGuestRow}>+ add another guest</AddMoreLink>
        </div>
      )}
    </div>
  );
}

// Small section header used above the per-person field groups inside WhoFor.
function PersonHeader({ brand, label, onRemove }) {
  return (
    <div style={{
      fontSize: 12.5, fontWeight: 700, letterSpacing: '0.06em', textTransform: 'uppercase',
      color: brand.inkSoft, display: 'flex', alignItems: 'center', justifyContent: 'space-between',
    }}>
      <span>{label}</span>
      {onRemove && (
        <button
          type="button"
          onClick={onRemove}
          style={{
            background: 'transparent', border: 0, padding: 0,
            color: brand.inkSoft, fontSize: 11, fontWeight: 600,
            cursor: 'pointer', textDecoration: 'underline',
          }}>
          Remove
        </button>
      )}
    </div>
  );
}

// Compact per-person waiver row. Header + checkbox row are always visible;
// the legal copy expands in a small scrolling area when the user taps "Read".
// Designed to stack — multiple attendees collapse cleanly side by side.
function PersonWaiver({ brand, brandName, label, checked, onChange, open, setOpen }) {
  return (
    <div style={{
      marginTop: 10,
      border: `1px solid ${brand.border}`,
      borderRadius: 10,
      overflow: 'hidden',
      background: '#FAFAF7',
    }}>
      <button
        type="button"
        onClick={() => setOpen(!open)}
        style={{
          width: '100%',
          display: 'flex', alignItems: 'center', gap: 10,
          padding: '10px 12px',
          background: 'transparent', border: 0,
          textAlign: 'left', cursor: 'pointer',
        }}>
        <div style={{
          width: 24, height: 24, borderRadius: 7,
          background: brand.accentSoft, color: brand.accentDeep,
          display: 'grid', placeItems: 'center', flex: '0 0 auto',
        }}>
          <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="9" y1="15" x2="15" y2="15"/></svg>
        </div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 12.5, fontWeight: 700, color: brand.ink }}>{label}</div>
          <div style={{ fontSize: 11, color: brand.inkSoft, marginTop: 1 }}>
            {open ? 'Tap to collapse' : 'Tap to read full waiver'}
          </div>
        </div>
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke={brand.inkSoft} strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" style={{ transform: open ? 'rotate(180deg)' : 'rotate(0deg)', transition: 'transform .2s ease' }}><polyline points="6 9 12 15 18 9"/></svg>
      </button>
      {open && (
        <div style={{
          maxHeight: 160, overflow: 'auto',
          padding: '0 12px 12px',
          fontSize: 11.5, lineHeight: 1.55,
          color: brand.inkSoft,
          borderTop: `1px solid ${brand.border}`,
        }}>
          <p style={{ marginTop: 10 }}>I acknowledge that participation in the activities offered by {brandName} carries inherent risks of physical injury. I confirm that the participant is physically able to participate and have disclosed any conditions that may affect safe practice.</p>
          <p>I voluntarily assume all risk of injury, loss, or damage to person or property arising from participation. I release {brandName}, its instructors, employees, and contractors from any and all claims arising out of participation, except where caused by gross negligence.</p>
          <p>This waiver is binding upon the participant, their heirs, and their legal representatives.</p>
        </div>
      )}
      <div style={{ padding: '10px 12px', borderTop: `1px solid ${brand.border}`, background: '#fff' }}>
        <Checkbox brand={brand} checked={checked} onChange={onChange}>
          I've read and agree to the liability waiver on this participant's behalf.
        </Checkbox>
      </div>
    </div>
  );
}

// Two-row SMS opt-in matching the look used in the booking stepper's payment
// step. Rendered under the phone number of any participant whose contact
// info we're collecting (self always, family rows only when the dob makes
// them an adult).
function SmsConsentRows({ brand, transactional, setTransactional, marketing, setMarketing }) {
  const row = (checked, onChange, label) => (
    <label style={{
      display: 'flex', alignItems: 'flex-start', gap: 8,
      cursor: 'pointer', padding: '4px 0',
    }}>
      <span
        onClick={e => { e.preventDefault(); onChange(!checked); }}
        style={{
          flex: '0 0 14px', width: 14, height: 14, marginTop: 2,
          borderRadius: 4,
          border: `1.5px solid ${checked ? brand.accent : brand.border}`,
          background: checked ? brand.accent : '#fff',
          display: 'grid', placeItems: 'center',
          transition: 'all .15s ease',
        }}>
        {checked && (
          <svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="4" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
        )}
      </span>
      <span style={{ fontSize: 11.5, color: brand.inkSoft, lineHeight: 1.45 }}>{label}</span>
      <input type="checkbox" checked={checked} onChange={e => onChange(e.target.checked)} style={{ position: 'absolute', opacity: 0, pointerEvents: 'none' }} />
    </label>
  );
  return (
    <div style={{ marginTop: 4, paddingLeft: 2 }}>
      {row(transactional, setTransactional, 'I agree to receive transactional SMS messages (class/appt reminders + confirmations).')}
      {row(marketing, setMarketing, 'I agree to receive other SMS communication from the studio (news, promotions) and can opt-out at any time.')}
    </div>
  );
}

// Re-uses the same checkmark icon used by other compact controls.
function Checkbox({ brand, checked, onChange, children }) {
  return (
    <label style={{
      display: 'flex', alignItems: 'flex-start', gap: 10,
      cursor: 'pointer', userSelect: 'none',
    }}>
      <span style={{
        width: 18, height: 18, borderRadius: 5, marginTop: 1,
        border: `1.5px solid ${checked ? brand.accent : brand.border}`,
        background: checked ? brand.accent : '#fff',
        display: 'grid', placeItems: 'center', flex: '0 0 auto',
      }}>{checked && <Check />}</span>
      <input type="checkbox" checked={checked} onChange={e => onChange(e.target.checked)} style={{ display: 'none' }} />
      <span style={{ fontSize: 12, color: brand.inkSoft, lineHeight: 1.45 }}>{children}</span>
    </label>
  );
}

// Selectable attendee card — same shape for self + family for visual consistency.
// Self uses a person icon; family uses initials. Both use checkmark + accent
// border + accentSoft fill when selected; muted state when not.
function AttendeeCard({ brand, kind, label, sub, initials, selected, onToggle, disabled }) {
  return (
    <button
      onClick={disabled ? undefined : onToggle}
      disabled={disabled}
      style={{
        flex: '1 1 130px',
        minWidth: 110,
        display: 'flex', alignItems: 'center', gap: 10,
        background: selected ? brand.accentSoft : '#fff',
        border: `1.5px solid ${selected ? brand.accent : brand.border}`,
        borderRadius: 10, padding: '10px 12px',
        textAlign: 'left', cursor: disabled ? 'not-allowed' : 'pointer',
        opacity: disabled ? 0.45 : (selected ? 1 : 0.78),
        transition: 'opacity .15s ease, background .15s ease, border-color .15s ease',
        position: 'relative', minHeight: 56,
      }}
    >
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{
          fontSize: 13.5, fontWeight: 600, color: brand.ink,
          whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
        }}>{label}</div>
        <div style={{
          fontSize: 11, color: brand.inkSoft, marginTop: 1,
          whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
        }}>{sub}</div>
      </div>
      <div style={{
        width: 18, height: 18, borderRadius: 5,
        border: `1.5px solid ${selected ? brand.accent : brand.border}`,
        background: selected ? brand.accent : '#fff',
        display: 'grid', placeItems: 'center', flex: '0 0 auto',
      }}>{selected && <Check />}</div>
    </button>
  );
}

// Verification line that updates live as cards toggle.
function SummaryLine({ brand, count, names }) {
  const isEmpty = count === 0;
  const warnFg = '#9A4326';
  const warnBg = '#FFEFE5';
  return (
    <div style={{
      marginTop: 10,
      padding: '8px 10px',
      background: isEmpty ? warnBg : brand.accentSoft,
      borderRadius: 8,
      display: 'flex', alignItems: 'center', gap: 8,
    }}>
      <span style={{
        minWidth: 20, height: 20, padding: '0 6px',
        borderRadius: 999,
        background: isEmpty ? warnFg : brand.accent,
        color: '#fff',
        display: 'grid', placeItems: 'center',
        fontSize: 11, fontWeight: 800,
        flex: '0 0 auto',
      }}>{count}</span>
      <div style={{
        fontSize: 12.5,
        color: isEmpty ? warnFg : brand.accentDeep,
        fontWeight: 600, lineHeight: 1.4,
      }}>
        {isEmpty ? (
          <>No one selected — select or add someone to reserve a spot.</>
        ) : (
          <>
            {count} spot{count > 1 ? 's' : ''} reserved
            <span style={{ fontWeight: 500, marginLeft: 6, opacity: 0.85 }}>— {names.join(' + ')}</span>
          </>
        )}
      </div>
    </div>
  );
}

function inputStyle(brand) {
  return {
    width: '100%', boxSizing: 'border-box',
    padding: '10px 12px', borderRadius: 8,
    border: `1px solid ${brand.border}`, background: '#fff',
    fontSize: 13.5, color: brand.ink, fontFamily: 'inherit',
    outline: 'none',
  };
}

function Check() { return <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="3.5" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"/></svg>; }

// Inline text-link "+ add another …" used under the family / guest forms.
function AddMoreLink({ brand, onClick, children }) {
  return (
    <button
      type="button"
      onClick={onClick}
      style={{
        alignSelf: 'flex-start',
        background: 'transparent', border: 0, padding: 0,
        color: brand.accent, fontSize: 12.5, fontWeight: 700,
        letterSpacing: '0.01em', cursor: 'pointer',
      }}>
      {children}
    </button>
  );
}

function MiniCheck({ brand, checked, onChange, children }) {
  return (
    <button onClick={onChange} style={{
      display: 'inline-flex', alignItems: 'center', gap: 7,
      background: 'transparent', border: 0, padding: 0, cursor: 'pointer',
      fontSize: 12.5, color: brand.inkSoft, fontWeight: 600,
    }}>
      <span style={{
        width: 16, height: 16, borderRadius: 4,
        border: `1.5px solid ${checked ? brand.accent : brand.border}`,
        background: checked ? brand.accent : '#fff',
        display: 'grid', placeItems: 'center',
      }}>{checked && <Check />}</span>
      {children}
    </button>
  );
}

// ────────────────────────────────────────────────────────────────
// 2. PromoAndGiftCard — a single collapsing row with two affordances
//    "Apply gift card" and "Promo code". Both stay collapsed by default
//    so the body of the form isn't crowded.
// ────────────────────────────────────────────────────────────────
function PromoAndGiftCard({ brand, giftBalance = 0, onApplyPromo, onApplyGift }) {
  const [openPromo, setOpenPromo] = useStateS(false);
  const [openGift, setOpenGift]   = useStateS(false);
  const [promo, setPromo]         = useStateS('');
  const [gift, setGift]           = useStateS('');
  const [appliedPromo, setAppliedPromo] = useStateS(null);
  const [appliedGift, setAppliedGift]   = useStateS(null);

  const apply = (kind) => {
    if (kind === 'promo' && promo.trim()) {
      setAppliedPromo({ code: promo.trim().toUpperCase(), amt: 10 });
      onApplyPromo && onApplyPromo({ code: promo.trim(), amt: 10 });
    }
    if (kind === 'gift' && gift.trim()) {
      setAppliedGift({ code: gift.trim().toUpperCase(), amt: 50 });
      onApplyGift && onApplyGift({ code: gift.trim(), amt: 50 });
    }
  };

  const linkBtn = {
    background: 'transparent', border: 0, padding: 0,
    color: brand.accent, fontSize: 12.5, fontWeight: 600, cursor: 'pointer',
    textDecoration: 'underline',
  };

  return (
    <div style={{
      display: 'flex', flexDirection: 'column', gap: 10,
      padding: '10px 0', marginTop: 4,
    }}>
      <div style={{ display: 'flex', gap: 14, flexWrap: 'wrap' }}>
        {!appliedPromo && <button style={linkBtn} onClick={() => setOpenPromo(o => !o)}>Promo code</button>}
        {!appliedGift && giftBalance > 0 && (
          <button style={linkBtn} onClick={() => onApplyGift && onApplyGift({ code: 'WALLET', amt: giftBalance })}>
            Apply ${giftBalance} gift balance
          </button>
        )}
        {!appliedGift && giftBalance === 0 && (
          <button style={linkBtn} onClick={() => setOpenGift(o => !o)}>Have a gift card?</button>
        )}
      </div>

      {openPromo && !appliedPromo && (
        <div style={{ display: 'flex', gap: 8 }}>
          <input value={promo} onChange={e => setPromo(e.target.value)} placeholder="Enter promo code"
            style={{ ...inputStyle(brand), flex: 1, textTransform: 'uppercase' }} />
          <button onClick={() => apply('promo')} style={chipBtn(brand)}>Apply</button>
        </div>
      )}
      {openGift && !appliedGift && (
        <div style={{ display: 'flex', gap: 8 }}>
          <input value={gift} onChange={e => setGift(e.target.value)} placeholder="Gift card code"
            style={{ ...inputStyle(brand), flex: 1, textTransform: 'uppercase' }} />
          <button onClick={() => apply('gift')} style={chipBtn(brand)}>Apply</button>
        </div>
      )}

      {appliedPromo && (
        <AppliedRow brand={brand} label={`Promo ${appliedPromo.code}`} amount={`-$${appliedPromo.amt}`} onRemove={() => setAppliedPromo(null)} />
      )}
      {appliedGift && (
        <AppliedRow brand={brand} label={`Gift balance ${appliedGift.code}`} amount={`-$${appliedGift.amt}`} onRemove={() => setAppliedGift(null)} />
      )}
    </div>
  );
}

function chipBtn(brand) {
  return {
    background: brand.ink, color: '#fff', border: 0,
    borderRadius: 8, padding: '0 14px',
    fontSize: 13, fontWeight: 600, cursor: 'pointer',
  };
}
function AppliedRow({ brand, label, amount, onRemove }) {
  return (
    <div style={{
      display: 'flex', alignItems: 'center', gap: 8,
      background: brand.accentSoft, color: brand.accentDeep,
      borderRadius: 8, padding: '8px 12px', fontSize: 12.5,
    }}>
      <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
      <div style={{ flex: 1, fontWeight: 600 }}>{label}</div>
      <div style={{ fontWeight: 700 }}>{amount}</div>
      <button onClick={onRemove} style={{ background: 'transparent', border: 0, color: brand.accentDeep, fontSize: 11, fontWeight: 600, cursor: 'pointer' }}>Remove</button>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────
// 3. SavePayment — opt-in for new clients, auto-on (with toggle) for returning
// ────────────────────────────────────────────────────────────────
function SavePayment({ brand, returning, value, onChange }) {
  return (
    <label style={{
      display: 'flex', alignItems: 'center', gap: 10,
      padding: '8px 0', cursor: 'pointer', userSelect: 'none',
    }}>
      <span style={{
        width: 18, height: 18, borderRadius: 5,
        border: `1.5px solid ${value ? brand.accent : brand.border}`,
        background: value ? brand.accent : '#fff',
        display: 'grid', placeItems: 'center', flex: '0 0 auto',
      }}>{value && <Check />}</span>
      <input type="checkbox" checked={value} onChange={e => onChange(e.target.checked)} style={{ display: 'none' }} />
      <span style={{ fontSize: 12.5, color: brand.inkSoft, lineHeight: 1.4 }}>
        {returning ? 'Save this card on file (replace default)' : 'Save my card for next time — skip the form on the next booking'}
      </span>
    </label>
  );
}

// ────────────────────────────────────────────────────────────────
// 4. CancelPolicyAck — 24-hour cancellation acknowledgment.
//    Shown only when the studio configures fees (tweaks.cancelFee).
// ────────────────────────────────────────────────────────────────
function CancelPolicyAck({ brand, checked, onChange }) {
  return (
    <label style={{
      display: 'flex', alignItems: 'flex-start', gap: 10,
      padding: '10px 12px',
      background: brand.surface, border: `1px solid ${brand.border}`,
      borderRadius: 10, cursor: 'pointer',
    }}>
      <span style={{
        width: 18, height: 18, borderRadius: 5, marginTop: 1,
        border: `1.5px solid ${checked ? brand.accent : brand.border}`,
        background: checked ? brand.accent : '#fff',
        display: 'grid', placeItems: 'center', flex: '0 0 auto',
      }}>{checked && <Check />}</span>
      <input type="checkbox" checked={checked} onChange={e => onChange(e.target.checked)} style={{ display: 'none' }} />
      <span style={{ fontSize: 12, color: brand.inkSoft, lineHeight: 1.5 }}>
        I understand the <span style={{ color: brand.accent, textDecoration: 'underline' }}>24-hour cancellation policy</span>: late cancels and no-shows are billed $15 or use 1 credit.
      </span>
    </label>
  );
}

// ────────────────────────────────────────────────────────────────
// 5. WaitlistBanner — when class is full, show waitlist position.
// ────────────────────────────────────────────────────────────────
function WaitlistBanner({ brand, position = 3 }) {
  return (
    <div style={{
      marginTop: 14,
      padding: '14px 16px',
      background: brand.accentSoft, color: brand.accentDeep,
      borderRadius: 12, fontSize: 13, lineHeight: 1.5,
    }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }}>
        <span style={{
          width: 22, height: 22, borderRadius: 999, background: brand.accent, color: '#fff',
          display: 'grid', placeItems: 'center', fontSize: 11, fontWeight: 800,
        }}>{position}</span>
        <strong style={{ fontWeight: 700 }}>You'll be #{position} on the waitlist.</strong>
      </div>
      We'll text you the moment a spot opens. Your card isn't charged until then — you have 2 hours to confirm.
    </div>
  );
}

// ────────────────────────────────────────────────────────────────
// 6. LoggedInWalletStrip — the strip a returning, logged-in client sees at
//    the top of any flow: their balance + active membership + alt-payment hint.
// ────────────────────────────────────────────────────────────────
function LoggedInWalletStrip({ brand, credits = 6, membership, giftBalance = 0 }) {
  return (
    <div style={{
      display: 'flex', gap: 8,
      padding: '10px 0',
    }}>
      <WalletStat brand={brand} label="Credits"       value={credits}       sub={`expire ${credits > 0 ? 'Aug 22' : '—'}`} />
      {membership && <WalletStat brand={brand} label="Membership"   value={membership.name} sub={`renews ${membership.renews}`} highlight />}
      {giftBalance > 0 && <WalletStat brand={brand} label="Gift balance" value={`$${giftBalance}`} sub="usable anywhere" />}
    </div>
  );
}
function WalletStat({ brand, label, value, sub, highlight }) {
  return (
    <div style={{
      flex: 1, minWidth: 0,
      background: highlight ? brand.accentSoft : brand.surface,
      border: `1px solid ${highlight ? brand.accent : brand.border}`,
      borderRadius: 10, padding: '8px 10px',
    }}>
      <div style={{
        fontSize: 9.5, fontWeight: 700, letterSpacing: '0.08em', textTransform: 'uppercase',
        color: highlight ? brand.accentDeep : brand.inkSoft,
      }}>{label}</div>
      <div style={{
        fontFamily: brand.displayFont, fontWeight: 700, fontSize: 16, color: brand.ink,
        marginTop: 1, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
      }}>{value}</div>
      <div style={{ fontSize: 10.5, color: brand.inkSoft, marginTop: 1 }}>{sub}</div>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────
// 7. PaymentSourceChoice — for logged-in clients, choose what to use:
//    "Apply 1 credit" / "Use membership" / "Pay with card on file" /
//    "Use a different payment method this time"
// ────────────────────────────────────────────────────────────────
function PaymentSourceChoice({ brand, options, value, onChange }) {
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
      {options.map(opt => {
        const active = value === opt.id;
        return (
          <button key={opt.id} onClick={() => onChange(opt.id)} disabled={opt.disabled} style={{
            display: 'flex', alignItems: 'center', gap: 10,
            background: active ? brand.accentSoft : brand.surface,
            border: `1.5px solid ${active ? brand.accent : brand.border}`,
            borderRadius: 10, padding: '10px 12px',
            textAlign: 'left',
            opacity: opt.disabled ? 0.5 : 1,
            cursor: opt.disabled ? 'not-allowed' : 'pointer',
          }}>
            <span style={{
              width: 16, height: 16, borderRadius: 999,
              border: `2px solid ${active ? brand.accent : brand.border}`,
              background: active ? brand.accent : '#fff',
              display: 'grid', placeItems: 'center', flex: '0 0 auto',
            }}>{active && <span style={{ width: 6, height: 6, borderRadius: 999, background: '#fff' }}></span>}</span>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontSize: 13.5, fontWeight: 600, color: brand.ink }}>{opt.label}</div>
              <div style={{ fontSize: 11.5, color: brand.inkSoft, marginTop: 1 }}>{opt.sub}</div>
            </div>
            <div style={{ fontSize: 13, fontWeight: 700, color: brand.ink }}>{opt.cost}</div>
          </button>
        );
      })}
    </div>
  );
}

// ────────────────────────────────────────────────────────────────
// 8. ConfirmActions — calendar + share row on confirmation screens.
// ────────────────────────────────────────────────────────────────
function ConfirmActions({ brand, label, when, where, shareText }) {
  const [calOpen, setCalOpen] = useStateS(false);
  const [shareDone, setShareDone] = useStateS(false);
  const onShare = () => {
    if (navigator.share) {
      navigator.share({ title: label, text: shareText }).then(() => setShareDone(true)).catch(() => {});
    } else if (navigator.clipboard) {
      navigator.clipboard.writeText(shareText || label).then(() => setShareDone(true));
    } else { setShareDone(true); }
    setTimeout(() => setShareDone(false), 1800);
  };
  const btn = {
    flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
    padding: '12px 14px',
    background: brand.surface, border: `1px solid ${brand.border}`,
    borderRadius: 12, color: brand.ink,
    fontSize: 13, fontWeight: 600, cursor: 'pointer',
    position: 'relative',
  };
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginTop: 14 }}>
      <div style={{ display: 'flex', gap: 8, position: 'relative' }}>
        <button style={btn} onClick={() => setCalOpen(o => !o)}>
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>
          Add to calendar
        </button>
        <button style={btn} onClick={onShare}>
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/></svg>
          {shareDone ? 'Link copied' : 'Share'}
        </button>
      </div>
      {calOpen && (
        <div style={{
          background: brand.surface, border: `1px solid ${brand.border}`,
          borderRadius: 10, padding: '6px',
          display: 'flex', gap: 4,
        }}>
          {['Google', 'Apple', 'Outlook'].map(p => (
            <button key={p} style={{
              flex: 1, background: 'transparent', border: 0,
              padding: '8px 6px', borderRadius: 6,
              fontSize: 12.5, fontWeight: 600, color: brand.ink, cursor: 'pointer',
            }}>{p}</button>
          ))}
        </div>
      )}
    </div>
  );
}

// Expose for cross-script access
Object.assign(window, {
  WhoFor, PromoAndGiftCard, SavePayment, CancelPolicyAck, WaitlistBanner,
  LoggedInWalletStrip, PaymentSourceChoice, ConfirmActions,
});
