// ─────────────────────────────────────────────────────────────
// LifeWheel Coach — Calendar (week view + GCal connect state)
// ─────────────────────────────────────────────────────────────

const { useState: useS3, useEffect: useE3, useMemo: useM3, useRef: useR3 } = React;

// Helper: parse our loose ISO strings ("today 18:00", "wed 10:00", "mon 19:00")
// into a Date relative to a fixed "now" anchor (Tue May 5 16:12 local).
const NOW_ANCHOR = (() => {
  // Use today's actual date but pin to Tue 16:12 to keep mock-up coherent
  const d = new Date();
  d.setHours(16, 12, 0, 0);
  // shift so today is Tuesday
  const day = d.getDay(); // 0=Sun..6=Sat
  const delta = (2 - day + 7) % 7; // Tuesday
  d.setDate(d.getDate() + delta);
  return d;
})();

function parseLooseISO(s) {
  // returns a Date relative to NOW_ANCHOR (Tuesday)
  const m = s.toLowerCase().match(/^(today|yest|mon|tue|wed|thu|fri|sat|sun)\s+(\d{1,2}):(\d{2})$/);
  if (!m) return null;
  const [, kind, hh, mm] = m;
  const dayMap = { sun: 0, mon: 1, tue: 2, wed: 3, thu: 4, fri: 5, sat: 6 };
  const d = new Date(NOW_ANCHOR);
  d.setHours(parseInt(hh,10), parseInt(mm,10), 0, 0);
  if (kind === 'today') return d;
  if (kind === 'yest') { d.setDate(d.getDate() - 1); return d; }
  const targetDow = dayMap[kind];
  // current week's Monday
  const mondayOffset = (1 - NOW_ANCHOR.getDay() + 7) % 7 - 7; // Monday this week (relative)
  // simpler: find this week's Mon, then add target offset
  const monday = new Date(NOW_ANCHOR);
  monday.setDate(NOW_ANCHOR.getDate() - ((NOW_ANCHOR.getDay() + 6) % 7));
  const out = new Date(monday);
  out.setDate(monday.getDate() + ((targetDow + 6) % 7)); // Mon=0..Sun=6
  out.setHours(parseInt(hh,10), parseInt(mm,10), 0, 0);
  return out;
}

function startOfWeek(d) {
  const out = new Date(d);
  out.setHours(0,0,0,0);
  out.setDate(out.getDate() - ((out.getDay() + 6) % 7));
  return out;
}

function _calLocale() {
  return (window.LWLang && window.LWLang.lang() === 'ru') ? 'ru-RU' : 'en-US';
}
function fmtMonthDay(d) {
  return d.toLocaleString(_calLocale(), { month: 'short', day: 'numeric' });
}
function fmtDow(d) {
  return d.toLocaleString(_calLocale(), { weekday: 'short' }).toUpperCase();
}
function fmtHourLabel(hour) {
  // 7..21 (HOUR_START..HOUR_END-1). Russian → 24h ("07:00", "13:00"); EN → "7 am" / "1 pm".
  const lang = window.LWLang && window.LWLang.lang();
  if (lang === 'ru') {
    return String(hour).padStart(2, '0') + ':00';
  }
  return hour === 12 ? '12 pm' : hour > 12 ? `${hour - 12} pm` : `${hour} am`;
}
function fmtSessionTime(d) {
  // Block label time ("11:19 am" / "11:19").
  const lang = window.LWLang && window.LWLang.lang();
  if (lang === 'ru') {
    return d.toLocaleString('ru-RU', { hour: '2-digit', minute: '2-digit' });
  }
  return d.toLocaleString('en-US', { hour: 'numeric', minute: '2-digit' }).toLowerCase();
}
function fmtConflictWhen(d) {
  // Conflict card "Mon 11:19 am" / "пн 11:19".
  return d.toLocaleString(_calLocale(), { weekday: 'short', hour: 'numeric', minute: '2-digit' });
}
function fmtPrefillDate(d) {
  return d.toLocaleString(_calLocale(), { weekday: 'long', month: 'long', day: 'numeric' });
}
function sameDay(a, b) {
  return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
}

// External (non-coaching) blocks the user has on GCal — to show conflict awareness
const EXTERNAL_EVENTS = [
  { title: 'Yoga',          dayOffset: 1, start: '07:00', end: '08:00', color: '#8C5DE8' }, // Tue
  { title: 'Therapy (mine)',dayOffset: 2, start: '12:00', end: '13:00', color: '#8C5DE8' }, // Wed
  { title: 'Lunch w/ Eli',  dayOffset: 3, start: '13:00', end: '14:30', color: '#E8943A' }, // Thu
  { title: 'School pickup', dayOffset: 4, start: '15:00', end: '15:45', color: '#E8943A' }, // Fri
];

// realSessions: array from RTDB /sessions filtered by coachId.
// rosterById: { userUid: { displayName, avatarHue, ... } } from /coach_clients/{coachId}.
// If `realSessions` is null, fall back to LWDATA fixtures (demo mode).
function buildWeekEvents(weekStart, realSessions, rosterById) {
  let sessions;
  if (realSessions) {
    sessions = realSessions
      .filter(s => s.startedAt)
      .map(s => {
        const start = new Date(Number(s.startedAt));
        const end = s.endedAt ? new Date(Number(s.endedAt)) : new Date(start.getTime() + (s.scheduledMinutes || 50) * 60000);
        const display = (rosterById && rosterById[s.userUid]) || {};
        const initials = display.initials || (window.LWAuth ? window.LWAuth.initialsOf(display.displayName || 'Client') : '··');
        return {
          ...s,
          start, end, kind: 'session',
          status: s.endedAt ? 'completed' : 'upcoming',
          minutes: Math.max(1, Math.round((end - start) / 60000)),
          mode: 'video',
          client: {
            id: s.userUid,
            userUid: s.userUid,
            name: display.displayName || 'Client',
            initials,
            avatarHue: display.avatarHue ?? 140,
          },
        };
      });
  } else {
    sessions = LWDATA.SESSIONS.map(s => {
      const start = parseLooseISO(s.startISO);
      if (!start) return null;
      const end = new Date(start);
      end.setMinutes(end.getMinutes() + s.minutes);
      return { ...s, start, end, kind: 'session', client: LWDATA.byId(s.clientId) };
    }).filter(Boolean);
  }

  // External events are demo-only (no Google Calendar wiring yet).
  const ext = realSessions ? [] : EXTERNAL_EVENTS.map((e, i) => {
    const d = new Date(weekStart);
    d.setDate(d.getDate() + e.dayOffset);
    const [sh, sm] = e.start.split(':').map(Number);
    const [eh, em] = e.end.split(':').map(Number);
    const start = new Date(d); start.setHours(sh, sm, 0, 0);
    const end = new Date(d); end.setHours(eh, em, 0, 0);
    return { id: 'ext-' + i, title: e.title, start, end, color: e.color, kind: 'external' };
  });

  return [...sessions, ...ext].filter(ev => {
    const wkEnd = new Date(weekStart); wkEnd.setDate(wkEnd.getDate() + 7);
    return ev.start >= weekStart && ev.start < wkEnd;
  });
}

// Subscribe to all sessions where coachId matches the signed-in coach,
// plus the coach's roster (for client display data).
function useRealCalendarData() {
  const [state, setState] = useS3({ status: 'loading', sessions: null, rosterById: {} });
  useE3(() => {
    if (!window.LWAuth || !window.LWFB) return;
    let cleanups = [];
    const unsubAuth = window.LWAuth.onAuthChanged((u) => {
      cleanups.forEach(fn => { try { fn(); } catch {} });
      cleanups = [];
      if (!u) { setState({ status: 'demo', sessions: null, rosterById: {} }); return; }
      const coachUid = u.uid;
      let sessionsAcc = [];
      let rosterAcc = {};
      const apply = () => setState({ status: 'ready', sessions: sessionsAcc, rosterById: rosterAcc });

      const sessQ = window.LWFB.db.ref('/sessions').orderByChild('coachId').equalTo(coachUid);
      sessQ.on('value', (s) => {
        const v = s.exists() ? s.val() : {};
        sessionsAcc = Object.entries(v).map(([id, x]) => ({ id, ...x }));
        apply();
      }, () => { sessionsAcc = []; apply(); });
      cleanups.push(() => sessQ.off());

      const rosterRef = window.LWFB.db.ref(`/coach_clients/${coachUid}`);
      rosterRef.on('value', (s) => {
        rosterAcc = s.exists() ? s.val() : {};
        apply();
      }, () => { rosterAcc = {}; apply(); });
      cleanups.push(() => rosterRef.off());
    });
    return () => { if (unsubAuth) unsubAuth(); cleanups.forEach(fn => { try { fn(); } catch {} }); };
  }, []);
  return state;
}

// ── GCal connect banner ──
function GCalBanner({ connected, onDisconnect }) {
  const { c, fonts, t } = useLW();
  if (connected) {
    return (
      <div style={{
        display: 'flex', alignItems: 'center', gap: 14,
        padding: '14px 18px', borderRadius: 12,
        background: c.cardHover, border: `1px solid ${c.borderSubtle}`,
        marginBottom: 20,
      }}>
        <div style={{
          width: 36, height: 36, borderRadius: 8,
          background: '#fff', display: 'flex', alignItems: 'center', justifyContent: 'center',
          boxShadow: '0 1px 3px rgba(0,0,0,0.15)',
        }}>
          <svg width="22" height="22" viewBox="0 0 48 48" aria-hidden>
            <rect x="6" y="6" width="36" height="36" rx="4" fill="#fff" stroke="#dadce0" strokeWidth="1"/>
            <text x="24" y="32" textAnchor="middle" style={{ fontFamily: 'Roboto, Arial, sans-serif', fontSize: 16, fontWeight: 700, fill: '#1a73e8' }}>5</text>
          </svg>
        </div>
        <div style={{ flex: 1 }}>
          <div style={{ fontFamily: fonts.body, fontSize: 14, fontWeight: 600, color: c.textPrimary }}>
            Connected to Google Calendar
            <span style={{ marginLeft: 10, fontFamily: fonts.body, fontSize: 11, fontWeight: 700, letterSpacing: '0.14em', textTransform: 'uppercase', color: c.accent }}>read-only</span>
          </div>
          <div style={{ fontFamily: fonts.body, fontSize: 12, color: c.textSecondary, marginTop: 2 }}>
            sarah@chencoaching.com · We pull events to detect conflicts. We never write to your calendar.
          </div>
        </div>
        <Button variant="ghost" size="sm" onClick={onDisconnect}>Disconnect</Button>
      </div>
    );
  }
  return (
    <div style={{
      display: 'flex', alignItems: 'center', gap: 14,
      padding: '14px 18px', borderRadius: 12,
      background: hexToRgba(c.accent, 0.08), border: `1px dashed ${hexToRgba(c.accent, 0.4)}`,
      marginBottom: 20,
    }}>
      <div style={{ flex: 1 }}>
        <div style={{ fontFamily: fonts.body, fontSize: 14, fontWeight: 600, color: c.textPrimary }}>
          {t('cal.connect_title')}
        </div>
        <div style={{ fontFamily: fonts.body, fontSize: 12, color: c.textSecondary, marginTop: 2 }}>
          {t('cal.connect_sub')}
        </div>
      </div>
      <Button variant="primary" size="sm">{t('cal.connect_google')}</Button>
      <Button variant="ghost" size="sm">{t('cal.connect_apple')}</Button>
    </div>
  );
}

// ── Week toolbar ──
function WeekToolbar({ weekStart, onPrev, onNext, onToday, onAdd, scope, setScope }) {
  const { c: cBase, fonts: fontsBase, t } = useLW();
  const isBrut = !!(window.__brutMode && window.__brutC);
  const c = isBrut ? window.__brutC : cBase;
  const fonts = isBrut ? (window.__brutFonts || fontsBase) : fontsBase;
  const weekEnd = new Date(weekStart); weekEnd.setDate(weekEnd.getDate() + 6);
  const sameMonth = weekStart.getMonth() === weekEnd.getMonth();
  const loc = _calLocale();
  const label = sameMonth
    ? `${weekStart.toLocaleString(loc, { month: 'long' })} ${weekStart.getDate()}–${weekEnd.getDate()}, ${weekEnd.getFullYear()}`
    : `${weekStart.toLocaleString(loc, { month: 'short' })} ${weekStart.getDate()} – ${weekEnd.toLocaleString(loc, { month: 'short' })} ${weekEnd.getDate()}, ${weekEnd.getFullYear()}`;

  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap', marginBottom: 18 }}>
      <div style={{
        display: 'inline-flex', gap: 0,
        border: isBrut ? `1.5px solid ${c.ink}` : `1px solid ${c.borderDefault}`,
        borderRadius: isBrut ? 2 : 10,
        boxShadow: isBrut ? `2px 2px 0 0 ${c.ink}` : 'none',
        overflow: 'hidden', background: c.bg,
      }}>
        <button onClick={onPrev} aria-label={t('cal.previous')} style={navBtn(c, fonts, 'left', isBrut)}>‹</button>
        <button onClick={onToday} style={navBtn(c, fonts, 'mid', isBrut)}>{t('cal.today')}</button>
        <button onClick={onNext} aria-label={t('cal.next')} style={navBtn(c, fonts, 'right', isBrut)}>›</button>
      </div>
      <h2 style={{
        margin: 0,
        fontFamily: isBrut ? fonts.mono : fonts.display,
        fontWeight: isBrut ? 700 : 600,
        fontSize: isBrut ? 18 : 22,
        letterSpacing: '-0.01em',
        color: c.textPrimary,
        textTransform: isBrut ? 'uppercase' : 'none',
      }}>{label}</h2>
      <div style={{ flex: 1 }} />
      <div style={{
        display: 'inline-flex', background: c.bg,
        border: isBrut ? `1.5px solid ${c.ink}` : `1px solid ${c.borderSubtle}`,
        boxShadow: isBrut ? `2px 2px 0 0 ${c.ink}` : 'none',
        padding: 0, borderRadius: isBrut ? 2 : 9, gap: 0,
      }}>
        {[
          { v: 'all',     label: t('cal.scope_all') },
          { v: 'sessions', label: t('cal.scope_sessions') },
        ].map((o, i) => {
          const active = scope === o.v;
          return (
            <button key={o.v} onClick={() => setScope(o.v)} style={{
              padding: isBrut ? '8px 14px' : '6px 12px',
              border: 'none',
              borderLeft: isBrut && i > 0 ? `1.5px solid ${c.ink}` : 'none',
              background: active ? (isBrut ? c.ink : c.accentMuted) : 'transparent',
              color: active ? (isBrut ? c.bg : c.accent) : c.textSecondary,
              borderRadius: 0,
              fontFamily: isBrut ? fonts.mono : fonts.body,
              fontSize: isBrut ? 11 : 12,
              fontWeight: 700,
              letterSpacing: isBrut ? '0.10em' : 'normal',
              textTransform: isBrut ? 'uppercase' : 'none',
              cursor: 'pointer',
            }}>{o.label}</button>
          );
        })}
      </div>
      <Button variant="primary" size="sm" icon={<span style={{ fontSize: 14 }}>+</span>} onClick={onAdd}>{t('cal.new_session')}</Button>
    </div>
  );
}
function navBtn(c, fonts, pos, isBrut) {
  return {
    padding: pos === 'mid' ? '8px 14px' : '8px 12px',
    background: 'transparent',
    color: c.textPrimary,
    border: 'none',
    borderRight: pos !== 'right' ? (isBrut ? `1.5px solid ${c.ink}` : `1px solid ${c.borderSubtle}`) : 'none',
    fontFamily: isBrut ? fonts.mono : fonts.body,
    fontSize: isBrut ? 12 : 13,
    fontWeight: isBrut ? 700 : 600,
    letterSpacing: isBrut ? '0.10em' : 'normal',
    textTransform: isBrut && pos === 'mid' ? 'uppercase' : 'none',
    cursor: 'pointer', minWidth: pos === 'mid' ? 0 : 36,
  };
}

// ── Week grid ──
function WeekGrid({ weekStart, events, scope, onEventClick, onSlotClick }) {
  const { c: cBase, fonts: fontsBase, mode } = useLW();
  const isBrut = !!(window.__brutMode && window.__brutC);
  const c = isBrut ? window.__brutC : cBase;
  const fonts = isBrut ? (window.__brutFonts || fontsBase) : fontsBase;
  const HOUR_START = 7, HOUR_END = 22;
  const HOURS = HOUR_END - HOUR_START;
  const ROW_H = 44; // px per hour
  const days = Array.from({length: 7}, (_, i) => {
    const d = new Date(weekStart); d.setDate(d.getDate() + i); return d;
  });
  const filtered = events.filter(ev => scope === 'all' ? true : ev.kind === 'session');
  const now = NOW_ANCHOR;
  const inWeek = now >= weekStart && now < new Date(weekStart.getTime() + 7*86400000);
  const nowTop = inWeek ? ((now.getHours() + now.getMinutes()/60) - HOUR_START) * ROW_H : -1;
  const nowDayIdx = inWeek ? (now.getDay() + 6) % 7 : -1;

  return (
    <div style={{
      background: c.card,
      border: isBrut ? `1.5px solid ${c.ink}` : `1px solid ${c.borderSubtle}`,
      borderRadius: isBrut ? 2 : 14,
      boxShadow: isBrut ? `4px 4px 0 0 ${c.ink}` : 'none',
      overflow: 'hidden',
    }}>
      {/* Day headers */}
      <div style={{
        display: 'grid', gridTemplateColumns: '60px repeat(7, 1fr)',
        borderBottom: isBrut ? `1.5px solid ${c.ink}` : `1px solid ${c.borderSubtle}`,
      }}>
        <div />
        {days.map((d, i) => {
          const isToday = sameDay(d, NOW_ANCHOR);
          return (
            <div key={i} style={{
              padding: '10px 12px',
              borderLeft: isBrut ? `1.5px solid ${c.ink}` : `1px solid ${c.borderSubtle}`,
              background: isToday ? (isBrut ? c.ink : c.accentMuted) : 'transparent',
              color: isToday && isBrut ? c.bg : 'inherit',
            }}>
              <div style={{
                fontFamily: isBrut ? fonts.mono : fonts.body,
                fontSize: 10, fontWeight: 700, letterSpacing: '0.14em',
                textTransform: 'uppercase',
                color: isToday ? (isBrut ? c.bg : c.accent) : c.textTertiary,
              }}>
                {fmtDow(d)}
              </div>
              <div style={{
                fontFamily: isBrut ? fonts.mono : fonts.display,
                fontSize: 20, fontWeight: 700,
                color: isToday ? (isBrut ? c.bg : c.accent) : c.textPrimary,
                marginTop: 2,
                fontVariantNumeric: 'tabular-nums',
              }}>
                {d.getDate()}
              </div>
            </div>
          );
        })}
      </div>
      {/* Body grid */}
      <div style={{ position: 'relative', display: 'grid', gridTemplateColumns: '60px repeat(7, 1fr)', height: HOURS * ROW_H }}>
        {/* Hour gutter */}
        <div style={{ position: 'relative' }}>
          {Array.from({length: HOURS}, (_, h) => {
            const hour = HOUR_START + h;
            return (
              <div key={h} style={{ height: ROW_H, position: 'relative' }}>
                <span style={{
                  position: 'absolute', top: -7, right: 8,
                  fontFamily: fonts.body, fontSize: 10, fontWeight: 600,
                  color: c.textTertiary, letterSpacing: '0.04em',
                }}>{fmtHourLabel(hour)}</span>
              </div>
            );
          })}
        </div>
        {/* 7 day columns */}
        {days.map((d, di) => (
          <div key={di} style={{
            position: 'relative',
            borderLeft: `1px solid ${c.borderSubtle}`,
            background: sameDay(d, NOW_ANCHOR) ? hexToRgba(c.accent, 0.04) : 'transparent',
          }}>
            {/* hour rows + click slot */}
            {Array.from({length: HOURS}, (_, h) => (
              <div key={h}
                onClick={() => {
                  const start = new Date(d); start.setHours(HOUR_START + h, 0, 0, 0);
                  onSlotClick && onSlotClick(start);
                }}
                style={{
                  height: ROW_H,
                  borderTop: `1px solid ${c.borderSubtle}`,
                  cursor: 'pointer',
                }} />
            ))}
            {/* events for this day */}
            {filtered.filter(ev => sameDay(ev.start, d)).map(ev => {
              const startH = ev.start.getHours() + ev.start.getMinutes()/60;
              const endH = ev.end.getHours() + ev.end.getMinutes()/60;
              const top = (startH - HOUR_START) * ROW_H;
              const height = Math.max(28, (endH - startH) * ROW_H - 2);
              return ev.kind === 'session'
                ? <SessionBlock key={ev.id} ev={ev} top={top} height={height} onClick={() => onEventClick && onEventClick(ev)} />
                : <ExternalBlock key={ev.id} ev={ev} top={top} height={height} />;
            })}
            {/* now line */}
            {nowDayIdx === di && (
              <div style={{
                position: 'absolute', left: -2, right: 2, top: nowTop,
                height: 2, background: c.flame, zIndex: 5,
                boxShadow: `0 0 8px ${c.flame}`,
              }}>
                <div style={{ position: 'absolute', left: -4, top: -3, width: 8, height: 8, borderRadius: '50%', background: c.flame }} />
              </div>
            )}
          </div>
        ))}
      </div>
    </div>
  );
}

function SessionBlock({ ev, top, height, onClick }) {
  const { c: cBase, fonts: fontsBase, t } = useLW();
  const isBrut = !!(window.__brutMode && window.__brutC);
  const c = isBrut ? window.__brutC : cBase;
  const fonts = isBrut ? (window.__brutFonts || fontsBase) : fontsBase;
  const cli = ev.client;
  const accentColor = c.accent;
  const isPast = ev.status === 'completed';
  const startStr = fmtSessionTime(ev.start);
  const modeLabel = ev.mode === 'phone' ? t('cal.mode_phone')
    : ev.mode === 'in-person' ? t('cal.mode_inperson')
    : t('cal.mode_video');
  return (
    <div onClick={onClick} style={{
      position: 'absolute', left: 4, right: 4, top, height,
      background: isPast ? c.cardHover : (isBrut ? c.accent : hexToRgba(accentColor, 0.18)),
      border: isBrut
        ? `1.5px solid ${c.ink}`
        : `1px solid ${isPast ? c.borderSubtle : hexToRgba(accentColor, 0.55)}`,
      borderLeft: isBrut
        ? `1.5px solid ${c.ink}`
        : `3px solid ${isPast ? c.textTertiary : accentColor}`,
      borderRadius: isBrut ? 2 : 8,
      boxShadow: isBrut ? `2px 2px 0 0 ${c.ink}` : 'none',
      padding: '6px 8px',
      overflow: 'hidden', cursor: 'pointer',
      opacity: isPast ? 0.7 : 1,
      color: isBrut && !isPast ? c.textOnAccent : 'inherit',
    }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 2 }}>
        <Avatar initials={cli.initials} hue={cli.avatarHue} size={18} />
        <span style={{
          fontFamily: isBrut ? fonts.mono : fonts.body,
          fontSize: 12, fontWeight: 700,
          color: isBrut && !isPast ? c.textOnAccent : c.textPrimary,
          overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
          letterSpacing: isBrut ? '0.02em' : 'normal',
          textTransform: isBrut ? 'uppercase' : 'none',
        }}>
          {cli.name}
        </span>
      </div>
      <div style={{
        fontFamily: isBrut ? fonts.mono : fonts.body,
        fontSize: 10,
        color: isBrut && !isPast ? c.textOnAccent : c.textSecondary,
        opacity: isBrut && !isPast ? 0.85 : 1,
        fontWeight: 600, letterSpacing: '0.04em',
      }}>
        {startStr} · {ev.minutes}{t('cal.minute_short')} · {modeLabel}
      </div>
      {ev.client.attentionReason && height > 60 && (
        <div style={{
          fontFamily: isBrut ? fonts.mono : fonts.body,
          fontSize: 10.5,
          color: isBrut && !isPast ? c.textOnAccent : c.textSecondary,
          opacity: isBrut && !isPast ? 0.85 : 1,
          marginTop: 4, fontStyle: 'italic',
          overflow: 'hidden', textOverflow: 'ellipsis',
          display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical',
        }}>
          {cli.lastNote}
        </div>
      )}
    </div>
  );
}

function ExternalBlock({ ev, top, height }) {
  const { c: cBase, fonts: fontsBase, t } = useLW();
  const isBrut = !!(window.__brutMode && window.__brutC);
  const c = isBrut ? window.__brutC : cBase;
  const fonts = isBrut ? (window.__brutFonts || fontsBase) : fontsBase;
  return (
    <div style={{
      position: 'absolute', left: 4, right: 4, top, height,
      background: isBrut
        ? c.bgSubtle
        : `repeating-linear-gradient(135deg, ${hexToRgba(ev.color, 0.10)} 0 6px, ${hexToRgba(ev.color, 0.04)} 6px 12px)`,
      border: isBrut ? `1.5px dashed ${c.ink}` : `1px dashed ${hexToRgba(ev.color, 0.5)}`,
      borderRadius: isBrut ? 2 : 8,
      padding: '4px 8px',
      overflow: 'hidden',
    }}>
      <div style={{
        fontFamily: isBrut ? fonts.mono : fonts.body,
        fontSize: 11, fontWeight: 700,
        color: isBrut ? c.ink : ev.color,
        overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
        letterSpacing: isBrut ? '0.02em' : 'normal',
        textTransform: isBrut ? 'uppercase' : 'none',
      }}>
        {ev.title}
      </div>
      <div style={{
        fontFamily: isBrut ? fonts.mono : fonts.body,
        fontSize: 9.5, color: c.textTertiary,
        fontWeight: 600, letterSpacing: '0.04em',
        textTransform: isBrut ? 'uppercase' : 'none',
      }}>
        {t('cal.from_calendar')}
      </div>
    </div>
  );
}

// ── Side rail: This week summary ──
function WeekSummary({ events, weekStart, showBooking = true }) {
  const { c: cBase, fonts: fontsBase, t } = useLW();
  const isBrut = !!(window.__brutMode && window.__brutC);
  const c = isBrut ? window.__brutC : cBase;
  const fonts = isBrut ? (window.__brutFonts || fontsBase) : fontsBase;
  const [editAvail, setEditAvail] = React.useState(false);
  const [copied, setCopied] = React.useState(false);
  const bookingUrl = 'lifewheel.app/book/sarah_chen';
  const handleCopy = () => {
    if (navigator.clipboard) navigator.clipboard.writeText('https://' + bookingUrl).catch(() => {});
    setCopied(true);
    setTimeout(() => setCopied(false), 1600);
  };
  const sessions = events.filter(e => e.kind === 'session' && e.status !== 'completed');
  const totalMin = sessions.reduce((s, e) => s + e.minutes, 0);
  const days = Array.from(new Set(sessions.map(e => e.start.toDateString()))).length;
  // Detect conflicts: a session within 30 min of an external event
  const conflicts = [];
  sessions.forEach(s => {
    events.filter(e => e.kind === 'external').forEach(ext => {
      const overlap = s.start < ext.end && s.end > ext.start;
      const tight = Math.abs(s.start - ext.end) < 30 * 60000 || Math.abs(ext.start - s.end) < 30 * 60000;
      if (overlap || tight) {
        conflicts.push({ s, ext, overlap });
      }
    });
  });

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
      <Card padding={20}>
        <div style={{ fontFamily: isBrut ? fonts.mono : fonts.body, fontSize: 11, fontWeight: isBrut ? 600 : 700, letterSpacing: '0.16em', textTransform: 'uppercase', color: c.textTertiary, marginBottom: 10 }}>{t('cal.this_week')}</div>
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
          <Stat label={t('cal.sessions')}   value={sessions.length} />
          <Stat label={t('cal.hours')}      value={(totalMin / 60).toFixed(1)} />
          <Stat label={t('cal.days_busy')}  value={days} />
          <Stat label={t('cal.open_slots')} value={`${5 - Math.min(5, days)}`} />
        </div>
      </Card>

      <Card padding={20}>
        <div style={{ fontFamily: isBrut ? fonts.mono : fonts.body, fontSize: 11, fontWeight: isBrut ? 600 : 700, letterSpacing: '0.16em', textTransform: 'uppercase', color: c.textTertiary, marginBottom: 10 }}>{t('cal.conflicts_eyebrow')}</div>
        {conflicts.length === 0 ? (
          <div style={{ fontFamily: fonts.body, fontSize: 13, color: c.textSecondary, fontStyle: 'italic' }}>
            {t('cal.conflicts_none')}
          </div>
        ) : (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
            {conflicts.slice(0, 3).map((cf, i) => (
              <div key={i} style={{
                padding: 10,
                borderRadius: isBrut ? 2 : 9,
                background: isBrut ? c.bg : hexToRgba(c.flame, 0.08),
                border: isBrut ? `1.5px solid ${c.flame}` : `1px solid ${hexToRgba(c.flame, 0.3)}`,
                boxShadow: isBrut ? `2px 2px 0 0 ${c.flame}` : 'none',
              }}>
                <div style={{ fontFamily: fonts.body, fontSize: 12, fontWeight: 700, color: c.flame, marginBottom: 2 }}>
                  {cf.overlap ? t('cal.conflict_overlap') : t('cal.conflict_tight')}
                </div>
                <div style={{ fontFamily: fonts.body, fontSize: 12, color: c.textPrimary }}>
                  <strong>{cf.s.client.name}</strong> · {fmtConflictWhen(cf.s.start)}
                </div>
                <div style={{ fontFamily: fonts.body, fontSize: 11, color: c.textSecondary, fontStyle: 'italic', marginTop: 1 }}>
                  ↔ {cf.ext.title}
                </div>
              </div>
            ))}
          </div>
        )}
      </Card>

      {showBooking ? (
        <Card padding={20}>
          <div style={{ fontFamily: isBrut ? fonts.mono : fonts.body, fontSize: 11, fontWeight: isBrut ? 600 : 700, letterSpacing: '0.16em', textTransform: 'uppercase', color: c.textTertiary, marginBottom: 10 }}>{t('cal.booking_eyebrow')}</div>
          <div style={{ fontFamily: fonts.body, fontSize: 13, color: c.textSecondary, marginBottom: 10 }}>
            {t('cal.booking_blurb')}
          </div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '10px 12px', background: c.bgSubtle, border: `1px solid ${c.borderSubtle}`, borderRadius: 9, marginBottom: 10 }}>
            <span style={{ fontFamily: fonts.body, fontSize: 12, color: c.textPrimary, flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
              {bookingUrl}
            </span>
            <Button variant="ghost" size="sm" onClick={handleCopy}>{copied ? t('cal.booking_copied') : t('cal.booking_copy')}</Button>
          </div>
          <Button variant="quiet" size="sm" onClick={() => setEditAvail(true)}>{t('cal.booking_edit_avail')}</Button>
        </Card>
      ) : null}
      <AvailabilitySheet open={editAvail} onClose={() => setEditAvail(false)} />
    </div>
  );
}

// ── Availability editor sheet ──
const DAY_LABELS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
const DEFAULT_AVAIL = [
  { enabled: true,  windows: [{ start: '09:00', end: '12:00' }, { start: '14:00', end: '17:00' }] },
  { enabled: true,  windows: [{ start: '09:00', end: '12:00' }, { start: '14:00', end: '17:00' }] },
  { enabled: true,  windows: [{ start: '09:00', end: '12:00' }, { start: '14:00', end: '17:00' }] },
  { enabled: true,  windows: [{ start: '09:00', end: '17:00' }] },
  { enabled: true,  windows: [{ start: '09:00', end: '14:00' }] },
  { enabled: false, windows: [] },
  { enabled: false, windows: [] },
];

function AvailabilitySheet({ open, onClose }) {
  const { c, fonts } = useLW();
  const [days, setDays] = React.useState(DEFAULT_AVAIL);
  const [bufferMin, setBufferMin] = React.useState(15);
  const [noticeHr, setNoticeHr] = React.useState(12);
  if (!open) return null;

  const toggleDay = (i) => setDays(d => d.map((day, j) => j === i ? { ...day, enabled: !day.enabled, windows: !day.enabled && day.windows.length === 0 ? [{ start: '09:00', end: '17:00' }] : day.windows } : day));
  const updateWindow = (i, wi, key, val) => setDays(d => d.map((day, j) => j === i ? { ...day, windows: day.windows.map((w, k) => k === wi ? { ...w, [key]: val } : w) } : day));
  const addWindow = (i) => setDays(d => d.map((day, j) => j === i ? { ...day, windows: [...day.windows, { start: '13:00', end: '17:00' }] } : day));
  const removeWindow = (i, wi) => setDays(d => d.map((day, j) => j === i ? { ...day, windows: day.windows.filter((_, k) => k !== wi) } : day));

  return (
    <div onClick={onClose} style={{
      position: 'fixed', inset: 0, zIndex: 100,
      background: 'rgba(0,0,0,0.45)', backdropFilter: 'blur(6px)',
      display: 'flex', alignItems: 'flex-end', justifyContent: 'center',
    }}>
      <div onClick={e => e.stopPropagation()} style={{
        width: '100%', maxWidth: 640, maxHeight: '92vh',
        background: c.bg, borderRadius: '20px 20px 0 0',
        boxShadow: '0 -10px 40px rgba(0,0,0,0.18)',
        display: 'flex', flexDirection: 'column',
      }}>
        <div style={{ padding: '20px 24px 16px', borderBottom: `1px solid ${c.borderSubtle}`, display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 12 }}>
          <div>
            <div style={{ fontFamily: fonts.display, fontSize: 22, fontWeight: 600, color: c.textPrimary, letterSpacing: '-0.01em' }}>
              Edit <span style={{ fontStyle: 'italic' }}>availability</span>
            </div>
            <div style={{ fontFamily: fonts.body, fontSize: 13, color: c.textSecondary, marginTop: 4 }}>
              Windows clients can pick from on your booking link.
            </div>
          </div>
          <button onClick={onClose} style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 22, color: c.textTertiary, padding: 4, lineHeight: 1 }}>×</button>
        </div>

        <div style={{ padding: '16px 24px', overflowY: 'auto', flex: 1 }}>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
            {days.map((day, i) => (
              <div key={i} style={{
                display: 'grid', gridTemplateColumns: '88px 1fr', gap: 14,
                padding: '12px 14px', borderRadius: 10,
                background: day.enabled ? c.bgSubtle : 'transparent',
                border: `1px solid ${day.enabled ? c.borderSubtle : c.borderSubtle}`,
                opacity: day.enabled ? 1 : 0.55,
              }}>
                <label style={{ display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer', fontFamily: fonts.body, fontSize: 13, fontWeight: 600, color: c.textPrimary }}>
                  <input type="checkbox" checked={day.enabled} onChange={() => toggleDay(i)} style={{ accentColor: c.accent }} />
                  {DAY_LABELS[i]}
                </label>
                <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
                  {day.enabled && day.windows.length > 0 ? day.windows.map((w, wi) => (
                    <div key={wi} style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                      <input type="time" value={w.start} onChange={e => updateWindow(i, wi, 'start', e.target.value)} style={timeInputStyle(c, fonts)} />
                      <span style={{ color: c.textTertiary, fontFamily: fonts.body, fontSize: 12 }}>–</span>
                      <input type="time" value={w.end} onChange={e => updateWindow(i, wi, 'end', e.target.value)} style={timeInputStyle(c, fonts)} />
                      {day.windows.length > 1 && (
                        <button onClick={() => removeWindow(i, wi)} style={{ background: 'none', border: 'none', cursor: 'pointer', color: c.textTertiary, fontSize: 16, padding: '0 4px' }}>×</button>
                      )}
                      {wi === day.windows.length - 1 && (
                        <button onClick={() => addWindow(i)} style={{ marginLeft: 'auto', background: 'none', border: `1px dashed ${c.borderSubtle}`, borderRadius: 6, padding: '3px 8px', cursor: 'pointer', color: c.textSecondary, fontFamily: fonts.body, fontSize: 11, fontWeight: 600 }}>+ window</button>
                      )}
                    </div>
                  )) : (
                    <div style={{ fontFamily: fonts.body, fontSize: 12, color: c.textTertiary, fontStyle: 'italic', padding: '4px 0' }}>
                      Unavailable
                    </div>
                  )}
                </div>
              </div>
            ))}
          </div>

          <div style={{ marginTop: 18, padding: '14px 16px', borderRadius: 10, border: `1px solid ${c.borderSubtle}`, display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 14 }}>
            <div>
              <div style={{ fontFamily: fonts.body, fontSize: 11, fontWeight: 700, letterSpacing: '0.10em', textTransform: 'uppercase', color: c.textTertiary, marginBottom: 6 }}>Buffer between sessions</div>
              <select value={bufferMin} onChange={e => setBufferMin(+e.target.value)} style={selectStyle(c, fonts)}>
                <option value={0}>None</option>
                <option value={10}>10 min</option>
                <option value={15}>15 min</option>
                <option value={30}>30 min</option>
              </select>
            </div>
            <div>
              <div style={{ fontFamily: fonts.body, fontSize: 11, fontWeight: 700, letterSpacing: '0.10em', textTransform: 'uppercase', color: c.textTertiary, marginBottom: 6 }}>Minimum notice</div>
              <select value={noticeHr} onChange={e => setNoticeHr(+e.target.value)} style={selectStyle(c, fonts)}>
                <option value={2}>2 hours</option>
                <option value={12}>12 hours</option>
                <option value={24}>1 day</option>
                <option value={48}>2 days</option>
              </select>
            </div>
          </div>
        </div>

        <div style={{ padding: '14px 24px', borderTop: `1px solid ${c.borderSubtle}`, display: 'flex', justifyContent: 'flex-end', gap: 10 }}>
          <Button variant="ghost" onClick={onClose}>Cancel</Button>
          <Button variant="primary" onClick={onClose}>Save windows</Button>
        </div>
      </div>
    </div>
  );
}

function timeInputStyle(c, fonts) {
  return {
    fontFamily: fonts.body, fontSize: 13, color: c.textPrimary,
    padding: '5px 8px', borderRadius: 6, border: `1px solid ${c.borderSubtle}`,
    background: c.bg, width: 100,
  };
}
function selectStyle(c, fonts) {
  return {
    fontFamily: fonts.body, fontSize: 13, color: c.textPrimary,
    padding: '7px 10px', borderRadius: 7, border: `1px solid ${c.borderSubtle}`,
    background: c.bg, width: '100%', cursor: 'pointer',
  };
}

function Stat({ label, value }) {
  const { c, fonts } = useLW();
  return (
    <div>
      <div style={{ fontFamily: fonts.display, fontSize: 28, fontWeight: 600, color: c.textPrimary, lineHeight: 1, letterSpacing: '-0.01em' }}>{value}</div>
      <div style={{ fontFamily: fonts.body, fontSize: 11, fontWeight: 700, letterSpacing: '0.10em', textTransform: 'uppercase', color: c.textTertiary, marginTop: 4 }}>{label}</div>
    </div>
  );
}

// ── New session sheet (very light — just for the click-through flow) ──
function NewSessionSheet({ open, onClose, prefill, roster, onCreated }) {
  const { c, fonts, t } = useLW();
  const [clientUid, setClientUid] = useS3('');
  const [length, setLength] = useS3(50);
  const [mode, setMode] = useS3('video');
  const [note, setNote] = useS3('');
  const [submitting, setSubmitting] = useS3(false);
  const [err, setErr] = useS3(null);

  // Reset form whenever the sheet (re)opens
  useE3(() => {
    if (open) {
      setClientUid(''); setLength(50); setMode('video'); setNote(''); setErr(null); setSubmitting(false);
    }
  }, [open]);

  if (!open) return null;
  const dateStr = prefill ? fmtPrefillDate(prefill) : '';
  const timeStr = prefill ? fmtSessionTime(prefill) : '';
  const rosterEntries = roster
    ? Object.entries(roster).map(([uid, info]) => ({
        uid,
        name: (info && info.displayName) || uid.slice(0, 6),
      }))
    : [];
  const canSubmit = !!prefill && !!clientUid && !submitting && rosterEntries.length > 0;

  const handleSubmit = async () => {
    if (!canSubmit) return;
    const coach = window.LWAuth && window.LWAuth.currentCoach && window.LWAuth.currentCoach();
    if (!coach || !window.LWFB) { setErr(t('cal.sheet_err_create')); return; }
    setSubmitting(true); setErr(null);
    try {
      const startedAt = prefill.getTime();
      const sessionId = `${coach.uid}_${clientUid}_${startedAt}`;
      await window.LWFB.db.ref(`/sessions/${sessionId}`).update({
        coachId: coach.uid,
        userUid: clientUid,
        startedAt,
        scheduledMinutes: Number(length),
        mode,
      });
      const trimmed = note.trim();
      if (trimmed) {
        await window.LWFB.db.ref(`/coach_private/${coach.uid}/${sessionId}`).update({
          privateNotes: trimmed,
        });
      }
      if (onCreated) onCreated({ sessionId, userUid: clientUid });
      onClose();
    } catch (e) {
      console.error('[create-session]', e);
      setErr(t('cal.sheet_err_create'));
      setSubmitting(false);
    }
  };

  return (
    <div onClick={submitting ? undefined : onClose} style={{
      position: 'fixed', inset: 0, zIndex: 100,
      background: 'rgba(0,0,0,0.4)', backdropFilter: 'blur(6px)',
      display: 'flex', alignItems: 'flex-end', justifyContent: 'center',
    }}>
      <div onClick={e => e.stopPropagation()} style={{
        background: c.card, border: `1px solid ${c.borderDefault}`,
        borderRadius: '16px 16px 0 0',
        width: '100%', maxWidth: 520, padding: 24,
        boxShadow: '0 -20px 60px rgba(0,0,0,0.4)',
      }}>
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 18 }}>
          <h3 style={{ margin: 0, fontFamily: fonts.display, fontSize: 22, fontWeight: 600, color: c.textPrimary }}>
            {t('cal.sheet_title')}
          </h3>
          <button onClick={onClose} disabled={submitting} style={{ background: 'transparent', border: 'none', color: c.textSecondary, fontSize: 22, cursor: submitting ? 'progress' : 'pointer' }}>×</button>
        </div>
        <div style={{ fontFamily: fonts.body, fontSize: 14, color: c.textSecondary, marginBottom: 18 }}>
          {prefill ? <>{t('cal.sheet_booking_for')} <strong style={{ color: c.textPrimary }}>{dateStr}, {timeStr}</strong></> : t('cal.sheet_pick_time')}
        </div>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 12, marginBottom: 18 }}>
          <Field label={t('cal.sheet_client')}>
            <select
              style={fieldInput(c, fonts)}
              value={clientUid}
              onChange={(e) => setClientUid(e.target.value)}
              disabled={rosterEntries.length === 0 || submitting}
            >
              <option value="">{rosterEntries.length === 0 ? t('cal.sheet_no_clients') : t('cal.sheet_pick_client')}</option>
              {rosterEntries.map(cli => <option key={cli.uid} value={cli.uid}>{cli.name}</option>)}
            </select>
          </Field>
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
            <Field label={t('cal.sheet_length')}>
              <select
                style={fieldInput(c, fonts)}
                value={length}
                onChange={(e) => setLength(Number(e.target.value))}
                disabled={submitting}
              >
                <option value="30">{t('cal.sheet_min', { n: 30 })}</option>
                <option value="50">{t('cal.sheet_min', { n: 50 })}</option>
                <option value="60">{t('cal.sheet_min', { n: 60 })}</option>
                <option value="90">{t('cal.sheet_min', { n: 90 })}</option>
              </select>
            </Field>
            <Field label={t('cal.sheet_mode')}>
              <select
                style={fieldInput(c, fonts)}
                value={mode}
                onChange={(e) => setMode(e.target.value)}
                disabled={submitting}
              >
                <option value="video">{t('cal.sheet_mode_video')}</option>
                <option value="phone">{t('cal.sheet_mode_phone')}</option>
                <option value="in-person">{t('cal.sheet_mode_inperson')}</option>
              </select>
            </Field>
          </div>
          <Field label={t('cal.sheet_note')} hint={t('cal.sheet_note_hint')}>
            <textarea
              rows={3}
              style={{ ...fieldInput(c, fonts), resize: 'vertical', minHeight: 60, fontFamily: fonts.body }}
              placeholder={t('cal.sheet_note_placeholder')}
              value={note}
              onChange={(e) => setNote(e.target.value)}
              disabled={submitting}
            />
          </Field>
          {err && (
            <div style={{
              padding: '10px 12px', borderRadius: 9,
              background: hexToRgba(c.flame, 0.1),
              border: `1px solid ${hexToRgba(c.flame, 0.3)}`,
              color: c.flame, fontFamily: fonts.body, fontSize: 13,
            }}>{err}</div>
          )}
        </div>
        <div style={{ display: 'flex', gap: 10, justifyContent: 'flex-end' }}>
          <Button variant="ghost" size="md" onClick={onClose} disabled={submitting}>{t('cal.sheet_cancel')}</Button>
          <Button variant="primary" size="md" onClick={handleSubmit} disabled={!canSubmit}>
            {submitting ? t('cal.sheet_creating') : t('cal.sheet_add')}
          </Button>
        </div>
      </div>
    </div>
  );
}
function Field({ label, hint, children }) {
  const { c, fonts } = useLW();
  return (
    <label style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
      <span style={{ fontFamily: fonts.body, fontSize: 11, fontWeight: 700, letterSpacing: '0.12em', textTransform: 'uppercase', color: c.textTertiary }}>{label}</span>
      {children}
      {hint && <span style={{ fontFamily: fonts.body, fontSize: 12, color: c.textTertiary, fontStyle: 'italic' }}>{hint}</span>}
    </label>
  );
}
function fieldInput(c, fonts) {
  return {
    background: c.bgSubtle,
    border: `1px solid ${c.borderSubtle}`,
    borderRadius: 10,
    padding: '11px 14px',
    fontFamily: fonts.body, fontSize: 15, color: c.textPrimary,
    outline: 'none', width: '100%',
  };
}

// ── Page ──
function CalendarPage() {
  const { c: cBase, fonts: fontsBase } = useLW();
  // Brutalism theme override
  const [brutMode, setBrutMode] = window.LWBrutal.useBrutMode();
  const c = window.LWBrutal.makeC(cBase, brutMode);
  const fonts = { ...fontsBase, ...window.LWBrutal.fonts };
  window.__brutMode = brutMode; window.__brutSetMode = setBrutMode;
  window.__brutC = c; window.__brutFonts = fonts;
  const { isMobile, isTablet } = useViewport();
  const [weekOffset, setWeekOffset] = useS3(0);
  const [scope, setScope] = useS3('all');
  const [connected, setConnected] = useS3(true);
  const [sheet, setSheet] = useS3(null); // { prefill }

  const realData = useRealCalendarData();
  // Calendar is auth-gated, so we never render true demo mode here.
  // 'loading' = real mode, no events yet (no LWDATA flash).
  // 'demo' status only happens if auth ever flips to null mid-session — also no demo flash.
  const isAuthed = realData.status !== 'demo';
  const weekStart = useM3(() => {
    const anchor = new Date();
    const d = startOfWeek(anchor);
    d.setDate(d.getDate() + weekOffset * 7);
    return d;
  }, [weekOffset]);
  const events = useM3(() => buildWeekEvents(
    weekStart,
    isAuthed ? (realData.sessions || []) : null,
    isAuthed ? realData.rosterById : null,
  ), [weekStart, realData.status, realData.sessions, realData.rosterById]);

  return (
    <PageShell active="calendar" maxWidth={1400}>
      {/* GCal banner depends on a real OAuth integration we don't have yet — keep hidden for authed coaches. */}
      {!isAuthed ? <GCalBanner connected={connected} onDisconnect={() => setConnected(false)} /> : null}
      <WeekToolbar
        weekStart={weekStart}
        onPrev={() => setWeekOffset(o => o - 1)}
        onNext={() => setWeekOffset(o => o + 1)}
        onToday={() => setWeekOffset(0)}
        onAdd={() => setSheet({ prefill: null })}
        scope={scope}
        setScope={setScope}
      />
      <div style={{ display: 'grid', gridTemplateColumns: isMobile || isTablet ? '1fr' : '1fr 320px', gap: 20, alignItems: 'flex-start' }}>
        <WeekGrid
          weekStart={weekStart}
          events={events}
          scope={scope}
          onEventClick={(ev) => {
            if (ev.kind !== 'session') return;
            // Real session → open the session itself; demo → open client page.
            if (isAuthed && ev.id && ev.client && ev.client.userUid) {
              (window.LWRouter ? window.LWRouter.go(`Session.html?clientId=${encodeURIComponent(ev.client.userUid)}&sessionId=${encodeURIComponent(ev.id)}`) : (window.location.href = `Session.html?clientId=${encodeURIComponent(ev.client.userUid)}&sessionId=${encodeURIComponent(ev.id)}`));
            } else {
              (window.LWRouter ? window.LWRouter.go('Client.html#') : (window.location.href = 'Client.html#')) + ev.client.id;
            }
          }}
          onSlotClick={(t) => setSheet({ prefill: t })}
        />
        <WeekSummary events={events} weekStart={weekStart} showBooking={!isAuthed} />
      </div>
      <NewSessionSheet open={!!sheet} prefill={sheet?.prefill} onClose={() => setSheet(null)} roster={isAuthed ? realData.rosterById : null} />
    </PageShell>
  );
}

window.CalendarPage = CalendarPage;
// Expose NewSessionSheet so other surfaces (Client.html "Book session") can
// reuse the exact same flow without duplicating the form.
window.NewSessionSheet = NewSessionSheet;
