/* global React, Icon, DualRangeSlider, useUserSearch, usePaymentMethod, PaymentGateSheet, createGroupBooking, respondGroupSeat, claimGroupSeat, shuffleGroupTeams, cancelGroupBooking, getSeatInvite, seatShareUrl, shareSeatLink, sbx */
// Group bookings (Hub phase 5) — UI.
//
//   GroupBookingSheet : course page → pick window, 3 people (or open /
//                       link seats), teams, create. Booker is card-gated.
//   PendingGroupCard  : Home card while the 3h clock runs — live seat
//                       states, accept/decline for invitees, shuffle /
//                       cancel / share-link for the booker.
//   SeatInviteSheet   : landing for ?seat=<token> links (post-auth).
//
// The one rule repeated everywhere: NOTHING IS HELD. The group locks —
// and everyone is charged, once — only when all four are in.

const G_MIN = 6 * 60, G_MAX = 21 * 60, G_STEP = 30;
const gTime = (m) => new Date(2000, 0, 1, Math.floor(m / 60), m % 60)
  .toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' });
const gDay = (dateStr) => new Date(`${dateStr}T00:00:00`)
  .toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric' });
const gName = (p) => p ? (p.first_name || (p.handle || '').replace(/^@/, '')) : '';

function SeatAvatar({ p, size = 30 }) {
  const initial = (gName(p) || '?').charAt(0).toUpperCase();
  return (
    <div style={{
      width: size, height: size, borderRadius: 999, overflow: 'hidden', flexShrink: 0,
      background: '#5A7B4A', color: 'var(--cream)', display: 'flex', alignItems: 'center', justifyContent: 'center',
      fontFamily: 'var(--font-display)', fontSize: size * 0.42,
    }}>
      {p && p.avatar_url ? <img src={p.avatar_url} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover' }}/> : initial}
    </div>
  );
}

// ─── Create sheet ─────────────────────────────────────────────────────
function GroupBookingSheet({ course, date, profile, onClose, onCreated }) {
  const p2 = (n) => String(n).padStart(2, '0');
  const dateStr = `${date.getFullYear()}-${p2(date.getMonth() + 1)}-${p2(date.getDate())}`;
  const [win, setWin] = React.useState([16 * 60, 19 * 60]);
  // Three picks: partner (team A with you) + two opponents (team B).
  const empty = { mode: 'open', user: null };
  const [picks, setPicks] = React.useState({ partner: { ...empty, mode: 'user' }, opp1: { ...empty, mode: 'user' }, opp2: { ...empty, mode: 'user' } });
  const [searching, setSearching] = React.useState(null); // 'partner' | 'opp1' | 'opp2' | null
  const [q, setQ] = React.useState('');
  const [results] = useUserSearch(searching ? q : '');
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState('');
  const [createdId, setCreatedId] = React.useState(null);
  const [linkSeats, setLinkSeats] = React.useState([]);
  const [sharedTok, setSharedTok] = React.useState('');
  const [method, refreshMethod] = usePaymentMethod(profile && profile.id);
  const [gateOpen, setGateOpen] = React.useState(false);

  const roleLabel = { partner: 'Your partner', opp1: 'Opponent 1', opp2: 'Opponent 2' };
  const roleTeam = { partner: 'A', opp1: 'B', opp2: 'B' };
  const chosenIds = Object.values(picks).map(x => x.user && x.user.id).filter(Boolean);

  function setPick(key, next) { setPicks(prev => ({ ...prev, [key]: next })); setSearching(null); setQ(''); }

  function shuffleTeams() {
    // Re-deal the three picks across the roles; you always stay on team A.
    const pool = ['partner', 'opp1', 'opp2'].map(k => picks[k]).sort(() => Math.random() - 0.5);
    setPicks({ partner: pool[0], opp1: pool[1], opp2: pool[2] });
  }

  async function doCreate() {
    setBusy(true); setErr('');
    try {
      const seats = ['partner', 'opp1', 'opp2'].map(k => ({
        team: roleTeam[k],
        mode: picks[k].user ? 'user' : picks[k].mode === 'user' ? 'open' : picks[k].mode,
        userId: picks[k].user ? picks[k].user.id : null,
      }));
      const gid = await createGroupBooking({ courseId: course.id, dateStr, startMin: win[0], endMin: win[1], seats });
      // Fetch link-seat tokens for the share buttons.
      const { data } = await sbx.from('group_seats')
        .select('id, team, status, user_id, invite_token')
        .eq('group_id', gid);
      setLinkSeats((data || []).filter(s => !s.user_id && s.status === 'invited'));
      setCreatedId(gid);
      onCreated && onCreated();
    } catch (e) {
      setErr(e.message || 'Could not create the group.');
    }
    setBusy(false);
  }

  async function create() {
    if (busy) return;
    if (!method) { setGateOpen(true); return; }
    await doCreate();
  }

  async function shareLink(tok) {
    const res = await shareSeatLink({
      url: seatShareUrl(tok, profile && profile.handle),
      courseName: course.shortName, dayLabel: gDay(dateStr),
    });
    if (res) { setSharedTok(tok); window.setTimeout(() => setSharedTok(''), 2500); }
  }

  const seatRow = (key) => {
    const pick = picks[key];
    return (
      <div key={key} style={{ marginTop: 10 }}>
        <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--forest)', opacity: 0.55, letterSpacing: '0.1em', textTransform: 'uppercase' }}>
          {roleLabel[key]} · Team {roleTeam[key]}
        </div>
        {pick.user ? (
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginTop: 6, padding: '8px 10px', borderRadius: 12, background: 'rgba(28,73,42,0.07)', border: '1px solid rgba(28,73,42,0.16)' }}>
            <SeatAvatar p={pick.user}/>
            <span style={{ flex: 1, fontSize: 13, fontWeight: 700, color: 'var(--forest)' }}>
              {gName(pick.user)} <span style={{ fontWeight: 400, opacity: 0.6 }}>{pick.user.handle}</span>
            </span>
            <button onClick={() => setPick(key, { mode: 'user', user: null })} style={{ background: 'transparent', border: 'none', color: 'var(--forest)', opacity: 0.5, cursor: 'pointer', fontSize: 14 }}>✕</button>
          </div>
        ) : searching === key ? (
          <div style={{ marginTop: 6 }}>
            <input autoFocus value={q} onChange={e => setQ(e.target.value)} placeholder="Search name or @handle"
              style={{ width: '100%', boxSizing: 'border-box', padding: '10px 12px', borderRadius: 12, border: '1px solid rgba(28,73,42,0.25)', fontSize: 14, background: 'var(--paper)', color: 'var(--ink)' }}/>
            <div style={{ maxHeight: 168, overflowY: 'auto', marginTop: 6 }}>
              {(results || []).filter(r => r.id !== (profile && profile.id) && !chosenIds.includes(r.id)).map(r => (
                <button key={r.id} onClick={() => setPick(key, { mode: 'user', user: r })} style={{
                  width: '100%', display: 'flex', alignItems: 'center', gap: 10, padding: '8px 6px',
                  background: 'transparent', border: 'none', cursor: 'pointer', textAlign: 'left',
                }}>
                  <SeatAvatar p={r} size={26}/>
                  <span style={{ fontSize: 13, color: 'var(--ink)' }}>{gName(r)} <span style={{ opacity: 0.55 }}>{r.handle}</span></span>
                </button>
              ))}
            </div>
            <button onClick={() => { setSearching(null); setQ(''); }} style={{ background: 'transparent', border: 'none', color: 'var(--forest)', fontSize: 11, fontWeight: 700, opacity: 0.6, cursor: 'pointer', padding: '4px 0' }}>Cancel</button>
          </div>
        ) : (
          <div style={{ display: 'flex', gap: 6, marginTop: 6 }}>
            {[
              { label: '🔍 Find player', on: () => { setSearching(key); setQ(''); }, active: false },
              { label: 'Leave open', on: () => setPick(key, { mode: 'open', user: null }), active: pick.mode === 'open' },
              { label: '🔗 Invite by link', on: () => setPick(key, { mode: 'link', user: null }), active: pick.mode === 'link' },
            ].map(b => (
              <button key={b.label} onClick={b.on} style={{
                flex: 1, padding: '9px 4px', borderRadius: 11, fontSize: 11.5, fontWeight: 700, cursor: 'pointer',
                background: b.active ? 'var(--forest)' : 'transparent',
                color: b.active ? 'var(--cream)' : 'var(--forest)',
                border: b.active ? '1px solid var(--forest)' : '1px solid rgba(28,73,42,0.3)',
              }}>{b.label}</button>
            ))}
          </div>
        )}
      </div>
    );
  };

  return (
    <div style={{ position: 'fixed', inset: 0, zIndex: 850, background: 'rgba(14,28,19,0.55)', backdropFilter: 'blur(6px)', WebkitBackdropFilter: 'blur(6px)', display: 'flex', alignItems: 'flex-end', justifyContent: 'center' }} onClick={onClose}>
      <div onClick={e => e.stopPropagation()} style={{ width: '100%', maxWidth: 480, maxHeight: '92%', overflowY: 'auto', background: 'var(--paper)', borderRadius: '24px 24px 0 0', padding: '22px 20px 30px', boxShadow: '0 -12px 40px rgba(14,28,19,0.3)' }}>

        {createdId ? (
          <div>
            <div style={{ fontSize: 34, lineHeight: 1 }}>🔒</div>
            <div style={{ fontFamily: 'var(--font-display)', fontSize: 24, color: 'var(--forest)', lineHeight: 1.05, marginTop: 10 }}>
              Group created — the 3-hour clock is running
            </div>
            <div className="caption-serif" style={{ fontSize: 14, opacity: 0.7, marginTop: 8, lineHeight: 1.5 }}>
              Invites are out. Spots aren't held — your group locks (and everyone is charged, once) the moment all four are in.
            </div>
            {linkSeats.map(s => (
              <button key={s.id} onClick={() => shareLink(s.invite_token)} style={{
                marginTop: 10, width: '100%', padding: '12px 14px', borderRadius: 13,
                background: 'var(--forest)', border: 'none', color: 'var(--cream)', fontSize: 13, fontWeight: 800, cursor: 'pointer',
              }}>
                {sharedTok === s.invite_token ? 'Link ready!' : `🔗 Share the Team ${s.team} seat link`}
              </button>
            ))}
            <button onClick={onClose} style={{ marginTop: 10, width: '100%', padding: '11px 0', borderRadius: 13, background: 'transparent', border: '1px solid var(--forest)', color: 'var(--forest)', fontSize: 13, fontWeight: 800, cursor: 'pointer' }}>
              Done — track it on Home
            </button>
          </div>
        ) : (
          <div>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
              <div>
                <div style={{ fontSize: 11, fontFamily: 'var(--font-mono)', color: 'var(--forest)', opacity: 0.55, letterSpacing: '0.12em', textTransform: 'uppercase' }}>Book as a group</div>
                <div style={{ fontFamily: 'var(--font-display)', fontSize: 24, color: 'var(--forest)', lineHeight: 1.05, marginTop: 6 }}>
                  {course.shortName} · {gDay(dateStr)}
                </div>
              </div>
              <button onClick={onClose} style={{ background: 'transparent', border: 'none', color: 'var(--forest)', opacity: 0.5, fontSize: 16, cursor: 'pointer' }}>✕</button>
            </div>

            <div style={{ marginTop: 16 }}>
              <div style={{ display: 'grid', gridTemplateColumns: '1fr auto 1fr', alignItems: 'baseline' }}>
                <span style={{ fontFamily: 'var(--font-display)', fontSize: 22, color: 'var(--forest)', justifySelf: 'start' }}>{gTime(win[0])}</span>
                <span style={{ fontSize: 11, fontFamily: 'var(--font-mono)', opacity: 0.5 }}>to</span>
                <span style={{ fontFamily: 'var(--font-display)', fontSize: 22, color: 'var(--forest)', justifySelf: 'end' }}>{gTime(win[1])}</span>
              </div>
              <DualRangeSlider min={G_MIN} max={G_MAX} step={G_STEP} value={win} onChange={setWin}/>
              <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', opacity: 0.5, marginTop: 2, lineHeight: 1.5 }}>
                Your group's tee-off window — the exact time is assigned when all four are in.
              </div>
            </div>

            {seatRow('partner')}
            {seatRow('opp1')}
            {seatRow('opp2')}

            <button onClick={shuffleTeams} style={{ marginTop: 12, background: 'transparent', border: 'none', color: 'var(--forest)', fontSize: 12, fontWeight: 800, cursor: 'pointer', padding: 0 }}>
              🎲 Shuffle teams
            </button>

            <div style={{ marginTop: 14, padding: '12px 14px', borderRadius: 14, background: 'rgba(28,73,42,0.06)', fontSize: 12, lineHeight: 1.55, color: 'var(--ink)' }}>
              <strong style={{ color: 'var(--forest)' }}>Each player pays their own round + $3.49 fee</strong> (waived for Sandbox+), charged <strong>only</strong> when all four are in and your time locks.
              <span style={{ display: 'block', marginTop: 4, opacity: 0.75 }}>
                Nothing is held: the window stays on sale until your group completes — everyone has <strong>3 hours</strong> to accept.
              </span>
            </div>

            {err && <div style={{ fontSize: 12, color: 'var(--loss, #B23B2B)', fontWeight: 600, marginTop: 10 }}>{err}</div>}

            <button onClick={create} disabled={busy} style={{
              marginTop: 14, width: '100%', padding: '14px 14px', borderRadius: 13,
              background: 'var(--forest)', border: 'none', color: 'var(--cream)',
              fontSize: 14, fontWeight: 800, cursor: 'pointer', opacity: busy ? 0.6 : 1,
            }}>
              {busy ? 'Creating…' : 'Send the invites'}
            </button>

            <PaymentGateSheet
              open={gateOpen}
              userId={profile && profile.id}
              onClose={() => setGateOpen(false)}
              context="A card on file is required to organize a group round — nobody is charged until all four are in."
              onAdded={() => { setGateOpen(false); refreshMethod(); doCreate(); }}
            />
          </div>
        )}
      </div>
    </div>
  );
}

// ─── Pending group card (Home) ────────────────────────────────────────
function PendingGroupCard({ group, profile, go, onChanged }) {
  const me = profile && profile.id;
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState('');
  const [sharedTok, setSharedTok] = React.useState('');
  const [method, refreshMethod] = usePaymentMethod(me);
  const [gateOpen, setGateOpen] = React.useState(false);
  const [, tick] = React.useState(0);
  React.useEffect(() => { const iv = setInterval(() => tick(t => t + 1), 30000); return () => clearInterval(iv); }, []);

  const isBooker = group.booker_id === me;
  const mySeat = (group.seats || []).find(s => s.user_id === me && s.status === 'invited');
  const minsLeft = Math.max(0, Math.round((new Date(group.expires_at).getTime() - Date.now()) / 60000));
  const nIn = (group.seats || []).filter(s => s.user_id && (s.status === 'booker' || s.status === 'accepted')).length;
  const courseName = (group.course && group.course.short_name) || 'Course';

  async function doAccept() {
    setBusy(true); setErr('');
    try { await respondGroupSeat(mySeat.id, true); onChanged && onChanged(); }
    catch (e) {
      if ((e.message || '').toLowerCase().includes('card')) setGateOpen(true);
      else setErr(e.message || 'Could not accept.');
    }
    setBusy(false);
  }
  async function accept() {
    if (busy || !mySeat) return;
    if (!method) { setGateOpen(true); return; }
    await doAccept();
  }
  async function decline() {
    if (busy || !mySeat) return;
    setBusy(true); setErr('');
    try { await respondGroupSeat(mySeat.id, false); onChanged && onChanged(); }
    catch (e) { setErr(e.message || 'Could not decline.'); }
    setBusy(false);
  }
  async function shuffle() {
    setBusy(true); setErr('');
    try { await shuffleGroupTeams(group.id); onChanged && onChanged(); }
    catch (e) { setErr(e.message || 'Could not shuffle.'); }
    setBusy(false);
  }
  async function cancelIt() {
    setBusy(true); setErr('');
    try { await cancelGroupBooking(group.id); onChanged && onChanged(); }
    catch (e) { setErr(e.message || 'Could not cancel.'); }
    setBusy(false);
  }
  async function shareLink(tok) {
    const res = await shareSeatLink({
      url: seatShareUrl(tok, profile && profile.handle),
      courseName, dayLabel: gDay(group.play_date),
    });
    if (res) { setSharedTok(tok); window.setTimeout(() => setSharedTok(''), 2500); }
  }

  const seatLine = (s) => {
    const label = s.user_id
      ? `${gName(s.profile) || 'Player'}${s.profile && s.profile.handle ? ` ${s.profile.handle}` : ''}`
      : s.status === 'open' ? 'Open seat — mutuals can grab it' : 'Link invite — waiting';
    const mark = s.status === 'booker' || s.status === 'accepted' ? '✓'
      : s.status === 'open' ? '○' : s.user_id ? '⏳' : '🔗';
    return (
      <div key={s.id} style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 6 }}>
        <span style={{ width: 18, textAlign: 'center', fontSize: 12 }}>{mark}</span>
        <span style={{ fontSize: 12.5, opacity: s.user_id ? 1 : 0.7, flex: 1 }}>{label}</span>
        {isBooker && !s.user_id && s.status === 'invited' && (
          <button onClick={() => shareLink(s.invite_token)} style={{
            fontSize: 10.5, fontWeight: 800, padding: '5px 10px', borderRadius: 999, cursor: 'pointer',
            background: 'var(--cream)', color: 'var(--forest)', border: 'none',
          }}>{sharedTok === s.invite_token ? 'Ready!' : 'Share link'}</button>
        )}
      </div>
    );
  };

  return (
    <div style={{
      borderRadius: 'var(--radius-card-lg)', overflow: 'hidden', position: 'relative',
      background: 'linear-gradient(135deg, var(--forest-dark) 0%, var(--forest) 55%, var(--moss) 100%)',
      color: 'var(--cream)', padding: 18, boxShadow: 'var(--shadow-md)',
    }}>
      <div className="grain" style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}/>
      <div style={{ position: 'relative' }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8 }}>
          <span style={{ fontSize: 10, fontFamily: 'var(--font-mono)', letterSpacing: '0.14em', textTransform: 'uppercase', opacity: 0.75, fontWeight: 700 }}>
            {mySeat ? 'Group invite · answer fast' : 'Group round forming'}
          </span>
          <span style={{ fontSize: 10, fontFamily: 'var(--font-mono)', fontWeight: 800, padding: '3px 9px', borderRadius: 999, background: 'rgba(234,226,206,0.14)', border: '1px solid rgba(234,226,206,0.25)' }}>
            {nIn}/4 · {minsLeft}m left
          </span>
        </div>
        <div style={{ fontFamily: 'var(--font-display)', fontSize: 22, lineHeight: 1, marginTop: 10, letterSpacing: '-0.01em' }}>
          {gTime(group.start_min)}–{gTime(group.end_min)} · {courseName}
        </div>
        <div style={{ fontSize: 11, fontFamily: 'var(--font-mono)', opacity: 0.75, marginTop: 5, letterSpacing: '0.04em' }}>
          {gDay(group.play_date).toUpperCase()} · EXACT TIME ASSIGNED WHEN ALL FOUR ARE IN
        </div>

        {['A', 'B'].map(team => (
          <div key={team} style={{ marginTop: 10 }}>
            <div style={{ fontSize: 9.5, fontFamily: 'var(--font-mono)', letterSpacing: '0.14em', textTransform: 'uppercase', opacity: 0.6 }}>Team {team}</div>
            {(group.seats || []).filter(s => s.team === team).map(seatLine)}
          </div>
        ))}

        {err && <div style={{ fontSize: 12, color: '#E7B8A7', fontWeight: 600, marginTop: 10 }}>{err}</div>}

        {mySeat ? (
          <div style={{ display: 'flex', gap: 8, marginTop: 14 }}>
            <button onClick={accept} disabled={busy} style={{ flex: 1, padding: '11px 0', borderRadius: 999, background: 'var(--cream)', color: 'var(--forest)', border: 'none', fontWeight: 800, fontSize: 13, cursor: 'pointer', opacity: busy ? 0.6 : 1 }}>
              {busy ? '…' : 'Accept seat'}
            </button>
            <button onClick={decline} disabled={busy} style={{ padding: '11px 16px', borderRadius: 999, background: 'transparent', color: 'var(--cream)', border: '1px solid rgba(255,255,255,0.4)', fontWeight: 700, fontSize: 13, cursor: 'pointer', opacity: busy ? 0.6 : 1 }}>
              Decline
            </button>
          </div>
        ) : isBooker ? (
          <div style={{ display: 'flex', gap: 8, marginTop: 14 }}>
            <button onClick={shuffle} disabled={busy} style={{ flex: 1, padding: '10px 0', borderRadius: 999, background: 'rgba(234,226,206,0.14)', color: 'var(--cream)', border: '1px solid rgba(234,226,206,0.25)', fontWeight: 700, fontSize: 12, cursor: 'pointer' }}>
              🎲 Shuffle teams
            </button>
            <button onClick={cancelIt} disabled={busy} style={{ padding: '10px 14px', borderRadius: 999, background: 'transparent', color: 'var(--cream)', border: '1px solid rgba(255,255,255,0.35)', fontWeight: 700, fontSize: 12, cursor: 'pointer' }}>
              Call it off
            </button>
          </div>
        ) : (
          <div style={{ fontSize: 11, opacity: 0.75, marginTop: 12 }}>You're in — waiting on the rest. Locks & charges when all four confirm.</div>
        )}

        <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', opacity: 0.6, marginTop: 10, lineHeight: 1.5 }}>
          NOTHING IS HELD — SPOTS STAY ON SALE UNTIL ALL FOUR ARE IN. NOBODY IS CHARGED UNTIL THE GROUP LOCKS.
        </div>
      </div>

      <PaymentGateSheet
        open={gateOpen}
        userId={me}
        onClose={() => setGateOpen(false)}
        context="A card on file is required to take your seat — you're only charged when all four are in."
        onAdded={() => { setGateOpen(false); refreshMethod(); doAccept(); }}
      />
    </div>
  );
}

// ─── Seat-invite landing (?seat=<token>) ──────────────────────────────
function SeatInviteSheet({ token, profile, onClose }) {
  const [inv, setInv] = React.useState(undefined); // undefined loading · null bad token
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState('');
  const [done, setDone] = React.useState(false);
  const [method, refreshMethod] = usePaymentMethod(profile && profile.id);
  const [gateOpen, setGateOpen] = React.useState(false);

  React.useEffect(() => {
    let on = true;
    (async () => { const d = await getSeatInvite(token); if (on) setInv(d || null); })();
    return () => { on = false; };
  }, [token]);

  async function doClaim() {
    setBusy(true); setErr('');
    try { await claimGroupSeat(token); setDone(true); }
    catch (e) {
      if ((e.message || '').toLowerCase().includes('card')) setGateOpen(true);
      else setErr(e.message || 'Could not claim the seat.');
    }
    setBusy(false);
  }
  async function claim() {
    if (busy) return;
    if (!method) { setGateOpen(true); return; }
    await doClaim();
  }

  const claimable = inv && inv.group_status === 'pending' && !inv.seat_status.match(/accepted|declined|booker/)
    && new Date(inv.expires_at).getTime() > Date.now();

  return (
    <div style={{ position: 'fixed', inset: 0, zIndex: 860, background: 'rgba(14,28,19,0.55)', backdropFilter: 'blur(6px)', WebkitBackdropFilter: 'blur(6px)', display: 'flex', alignItems: 'flex-end', justifyContent: 'center' }} onClick={onClose}>
      <div onClick={e => e.stopPropagation()} style={{ width: '100%', maxWidth: 480, background: 'var(--paper)', borderRadius: '24px 24px 0 0', padding: '22px 20px 30px', boxShadow: '0 -12px 40px rgba(14,28,19,0.3)' }}>
        {inv === undefined ? (
          <div style={{ textAlign: 'center', padding: 20, color: 'var(--forest)' }}>Loading your invite…</div>
        ) : inv === null || (!claimable && !done) ? (
          <div>
            <div style={{ fontFamily: 'var(--font-display)', fontSize: 22, color: 'var(--forest)', lineHeight: 1.1 }}>
              This seat isn't available anymore.
            </div>
            <div className="caption-serif" style={{ fontSize: 14, opacity: 0.7, marginTop: 8, lineHeight: 1.5 }}>
              The group may have filled, dissolved, or expired. Ask for a fresh link — or start your own group round.
            </div>
            <button onClick={onClose} style={{ marginTop: 14, width: '100%', padding: '12px 0', borderRadius: 13, background: 'var(--forest)', border: 'none', color: 'var(--cream)', fontSize: 13, fontWeight: 800, cursor: 'pointer' }}>Got it</button>
          </div>
        ) : done ? (
          <div>
            <div style={{ fontSize: 34, lineHeight: 1 }}>⛳</div>
            <div style={{ fontFamily: 'var(--font-display)', fontSize: 24, color: 'var(--forest)', lineHeight: 1.05, marginTop: 10 }}>
              Seat claimed — you're on Team {inv.team}
            </div>
            <div className="caption-serif" style={{ fontSize: 14, opacity: 0.7, marginTop: 8, lineHeight: 1.5 }}>
              Track it on Home. The round locks (and charges) when all four are in.
            </div>
            <button onClick={onClose} style={{ marginTop: 14, width: '100%', padding: '12px 0', borderRadius: 13, background: 'var(--forest)', border: 'none', color: 'var(--cream)', fontSize: 13, fontWeight: 800, cursor: 'pointer' }}>Done</button>
          </div>
        ) : (
          <div>
            <div style={{ fontSize: 11, fontFamily: 'var(--font-mono)', color: 'var(--forest)', opacity: 0.55, letterSpacing: '0.12em', textTransform: 'uppercase' }}>Group round invite</div>
            <div style={{ fontFamily: 'var(--font-display)', fontSize: 24, color: 'var(--forest)', lineHeight: 1.05, marginTop: 8 }}>
              {inv.booker_handle || 'A friend'} saved you a Team {inv.team} seat
            </div>
            <div className="caption-serif" style={{ fontSize: 14, opacity: 0.7, marginTop: 8, lineHeight: 1.5 }}>
              {inv.course_short} · {gDay(inv.play_date)} · tee-off window {gTime(inv.start_min)}–{gTime(inv.end_min)}. The exact time is assigned once all four are in.
            </div>
            <div style={{ marginTop: 12 }}>
              {(inv.seats || []).map((s, i) => (
                <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 4, fontSize: 12.5, color: 'var(--ink)' }}>
                  <span style={{ fontFamily: 'var(--font-mono)', fontSize: 10, opacity: 0.55 }}>TEAM {s.team}</span>
                  <span style={{ opacity: s.handle ? 1 : 0.6 }}>
                    {s.handle ? `${s.name || ''} ${s.handle}` : s.status === 'open' ? 'Open seat' : 'Waiting on invite'}
                  </span>
                  {s.sbx != null && <span style={{ fontFamily: 'var(--font-mono)', fontSize: 10, opacity: 0.5 }}>SBX {Number(s.sbx).toFixed(3)}</span>}
                </div>
              ))}
            </div>
            {err && <div style={{ fontSize: 12, color: 'var(--loss, #B23B2B)', fontWeight: 600, marginTop: 10 }}>{err}</div>}
            <button onClick={claim} disabled={busy} style={{ marginTop: 14, width: '100%', padding: '14px 0', borderRadius: 13, background: 'var(--forest)', border: 'none', color: 'var(--cream)', fontSize: 14, fontWeight: 800, cursor: 'pointer', opacity: busy ? 0.6 : 1 }}>
              {busy ? 'Claiming…' : 'Claim my seat'}
            </button>
            <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', opacity: 0.5, marginTop: 10, lineHeight: 1.5 }}>
              You'll never be charged until the whole group locks in.
            </div>
          </div>
        )}

        <PaymentGateSheet
          open={gateOpen}
          userId={profile && profile.id}
          onClose={() => setGateOpen(false)}
          context="A card on file is required to take your seat — you're only charged when all four are in."
          onAdded={() => { setGateOpen(false); refreshMethod(); doClaim(); }}
        />
      </div>
    </div>
  );
}

Object.assign(window, { GroupBookingSheet, PendingGroupCard, SeatInviteSheet });
