// src/features/admin/ui/admin-calendar.jsx
//
// Admin "Calendar" tab — a Google-Calendar-style week/day grid showing EVERY
// teacher's lessons in one view, color-coded per teacher. The in-platform
// mirror of the admin's Google Calendar (the bookings table is now a true
// delta-mirror of Google). Read-only overview: click a lesson for details;
// the Schedule tab stays the place to ACT on bookings.
//
// Mounted by admin-dashboard.jsx as <window.AdminCalendar />.
// Self-contained — own date/format/layout helpers (acal* prefix) so it does
// not depend on the instructor-calendar file's load order. Reads
// window.useAdminData() (same source as the Schedule tab).
//
// Timezone: a picker lets the admin render every time in any zone (defaults to
// the device zone). All day-bucketing and positioning go through acalZoned()
// so a late-evening NY lesson correctly lands on the next day in, say, Beirut.

const ACAL_DAYS   = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'];
const ACAL_DAYLONG= ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
const ACAL_MONTHS = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
const ACAL_HOUR_PX = 50;

// Curated, well-separated hues. Assigned stably per instructor id (hash) so a
// teacher keeps the same color across weeks — like a Google Calendar color.
const ACAL_HUES = [264, 232, 200, 168, 145, 122, 86, 60, 36, 16, 354, 328, 300, 282];
const acalColor = (hue) => ({
  bg:     `oklch(96% 0.04 ${hue})`,
  border: `oklch(60% 0.16 ${hue})`,
  text:   `oklch(40% 0.15 ${hue})`,
  dot:    `oklch(62% 0.17 ${hue})`,
});
const acalHashHue = (id) => {
  const s = String(id || '');
  let h = 0;
  for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) >>> 0;
  return ACAL_HUES[h % ACAL_HUES.length];
};

// Timezone options offered in the picker. '__local__' = the device's own zone.
const ACAL_TZS = [
  { id: '__local__',          label: 'My device' },
  { id: 'America/Los_Angeles',label: 'Los Angeles' },
  { id: 'America/Denver',     label: 'Denver' },
  { id: 'America/Chicago',    label: 'Chicago' },
  { id: 'America/New_York',   label: 'New York' },
  { id: 'America/Toronto',    label: 'Toronto' },
  { id: 'America/Sao_Paulo',  label: 'São Paulo' },
  { id: 'Europe/London',      label: 'London' },
  { id: 'Europe/Paris',       label: 'Paris / Berlin' },
  { id: 'Asia/Beirut',        label: 'Beirut' },
  { id: 'Asia/Dubai',         label: 'Dubai' },
  { id: 'Asia/Kolkata',       label: 'India' },
  { id: 'Asia/Singapore',     label: 'Singapore / HK' },
  { id: 'Asia/Tokyo',         label: 'Tokyo' },
  { id: 'Australia/Sydney',   label: 'Sydney' },
  { id: 'UTC',                label: 'UTC' },
];
const acalLocalTz = () => { try { return Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC'; } catch (_) { return 'UTC'; } };

// Wall-clock parts of an instant in a target zone. Returns {y,m,d,hour,minute}.
// The DateTimeFormat is cached per zone — inRange runs this across every
// booking on each render, so reusing the formatter keeps it cheap at scale.
const _acalDtfCache = {};
const acalZoned = (date, tz) => {
  let dtf = _acalDtfCache[tz];
  if (!dtf) {
    dtf = new Intl.DateTimeFormat('en-US', {
      timeZone: tz, year: 'numeric', month: '2-digit', day: '2-digit',
      hour: '2-digit', minute: '2-digit', hour12: false,
    });
    _acalDtfCache[tz] = dtf;
  }
  const p = {};
  for (const part of dtf.formatToParts(date)) if (part.type !== 'literal') p[part.type] = part.value;
  let hour = parseInt(p.hour, 10); if (hour === 24) hour = 0;   // Intl midnight quirk
  return { y: +p.year, m: +p.month, d: +p.day, hour, minute: +p.minute };
};
const acalKey = (y, m, d) => `${y}-${String(m).padStart(2, '0')}-${String(d).padStart(2, '0')}`;
// Short GMT-offset label for a zone right now, e.g. "GMT-4".
const acalTzAbbr = (tz) => {
  try {
    const part = new Intl.DateTimeFormat('en-US', { timeZone: tz, timeZoneName: 'shortOffset' })
      .formatToParts(new Date()).find(x => x.type === 'timeZoneName');
    return part ? part.value : '';
  } catch (_) { return ''; }
};
const acalFmtHour = (h) => `${(h % 12) || 12} ${h < 12 || h === 24 ? 'AM' : 'PM'}`;
const acalFmtClock = (hour, minute) => {
  const hr12 = (hour % 12) || 12, ap = hour < 12 ? 'AM' : 'PM';
  return minute === 0 ? `${hr12} ${ap}` : `${hr12}:${String(minute).padStart(2, '0')} ${ap}`;
};

// Lane-pack a day's events so overlapping lessons sit side by side. Mutates
// each event with {lane, lanes}. Overlap is timezone-independent (uses the
// absolute instant), so this works regardless of the display zone.
const acalLayout = (events) => {
  events.sort((a, b) => a.start - b.start || a.end - b.end);
  let group = [], groupEnd = -Infinity;
  const flush = () => {
    const cols = [];
    for (const ev of group) {
      let c = 0;
      for (; c < cols.length; c++) if (cols[c] <= ev.start) break;
      ev.lane = c; cols[c] = ev.end;
    }
    for (const ev of group) ev.lanes = cols.length || 1;
    group = []; groupEnd = -Infinity;
  };
  for (const ev of events) {
    if (group.length && ev.start >= groupEnd) flush();
    group.push(ev); groupEnd = Math.max(groupEnd, ev.end);
  }
  flush();
  return events;
};

const AdminCalendar = () => {
  const data = window.useAdminData();
  const isMobile = window.useIsMobile ? window.useIsMobile(820) : false;

  const [view,   setView]   = React.useState(isMobile ? 'day' : 'week');   // 'week' | 'day'
  // anchor = a UTC-midnight Date standing for the anchored CALENDAR date
  // (timezone-independent). Week/day math walks it with setUTCDate.
  const [tzChoice, setTzChoice] = React.useState(() => {
    try { return localStorage.getItem('mastery_admin_cal_tz') || '__local__'; } catch (_) { return '__local__'; }
  });
  const tz = tzChoice === '__local__' ? acalLocalTz() : tzChoice;

  const [anchor, setAnchor] = React.useState(() => {
    const z = acalZoned(new Date(), tz);
    return new Date(Date.UTC(z.y, z.m - 1, z.d));
  });
  const [activeId, setActiveId] = React.useState(null);
  const [hidden, setHidden] = React.useState(() => {
    try { return new Set(JSON.parse(localStorage.getItem('mastery_admin_cal_hidden') || '[]')); }
    catch (_) { return new Set(); }
  });
  React.useEffect(() => { if (isMobile && view === 'week') setView('day'); }, [isMobile]);

  const persistHidden = (next) => {
    setHidden(next);
    try { localStorage.setItem('mastery_admin_cal_hidden', JSON.stringify([...next])); } catch (_) {}
  };
  const toggleTeacher = (id) => { const n = new Set(hidden); n.has(id) ? n.delete(id) : n.add(id); persistHidden(n); };
  const changeTz = (id) => {
    setTzChoice(id);
    try { localStorage.setItem('mastery_admin_cal_tz', id); } catch (_) {}
  };

  const now = new Date();
  const nowZ = acalZoned(now, tz);
  const todayKey = acalKey(nowZ.y, nowZ.m, nowZ.d);

  // Day columns (UTC-midnight calendar dates).
  const days = (() => {
    if (view === 'day') return [new Date(anchor)];
    const wd = anchor.getUTCDay();                       // 0 = Sun
    const monday = new Date(anchor); monday.setUTCDate(monday.getUTCDate() - (wd === 0 ? 6 : wd - 1));
    return Array.from({ length: 7 }, (_, i) => { const d = new Date(monday); d.setUTCDate(d.getUTCDate() + i); return d; });
  })();
  const dayKeys = days.map(d => acalKey(d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate()));
  const dayKeySet = new Set(dayKeys);

  const allBookings = data.bookings || [];
  // Active bookings whose START lands (in the display zone) on a visible day.
  const inRange = allBookings.filter(b => {
    if (!b.scheduledAt || b.status === 'cancelled') return false;
    const z = acalZoned(new Date(b.scheduledAt), tz);
    return dayKeySet.has(acalKey(z.y, z.m, z.d));
  });

  const teacherMap = new Map();
  for (const b of inRange) {
    const id = b.instructorId; if (!id) continue;
    if (!teacherMap.has(id)) teacherMap.set(id, { id, name: b.instructor?.name || 'Unknown', hue: acalHashHue(id) });
  }
  const teachers = [...teacherMap.values()].sort((a, b) => a.name.localeCompare(b.name));
  const visible = inRange.filter(b => !hidden.has(b.instructorId));

  // Hour window — adapts to fit early/late lessons (in the display zone), but
  // always shows at least 8a–9p.
  let minH = 8, maxH = 21;
  for (const b of visible) {
    const z = acalZoned(new Date(b.scheduledAt), tz);
    minH = Math.min(minH, z.hour);
    const endMin = z.hour * 60 + z.minute + (b.durationMinutes || 60);
    maxH = Math.max(maxH, Math.ceil(endMin / 60));
  }
  minH = Math.max(0, Math.min(minH, 8));
  maxH = Math.min(24, Math.max(maxH, 21));
  const hours = []; for (let h = minH; h < maxH; h++) hours.push(h);
  const gridHeight = (maxH - minH) * ACAL_HOUR_PX;

  // Events for a given day-key, laid out + positioned in the display zone.
  const dayEvents = (key) => acalLayout(
    visible.filter(b => {
      const z = acalZoned(new Date(b.scheduledAt), tz);
      return acalKey(z.y, z.m, z.d) === key;
    }).map(b => {
      const s = new Date(b.scheduledAt);
      const ms = s.getTime();
      return { b, z: acalZoned(s, tz), start: ms, end: ms + (b.durationMinutes || 60) * 60000 };
    })
  );

  const active = activeId ? allBookings.find(b => b.id === activeId) : null;

  const navLabel = view === 'week'
    ? (() => {
        const s = days[0], e = days[6];
        const left = `${ACAL_MONTHS[s.getUTCMonth()]} ${s.getUTCDate()}`;
        const right = s.getUTCMonth() !== e.getUTCMonth() ? `${ACAL_MONTHS[e.getUTCMonth()]} ${e.getUTCDate()}` : `${e.getUTCDate()}`;
        return `${left} – ${right}, ${e.getUTCFullYear()}`;
      })()
    : `${ACAL_DAYLONG[days[0].getUTCDay()]}, ${ACAL_MONTHS[days[0].getUTCMonth()]} ${days[0].getUTCDate()}`;
  const step = (dir) => setAnchor(a => { const d = new Date(a); d.setUTCDate(d.getUTCDate() + dir * (view === 'week' ? 7 : 1)); return d; });
  const goToday = () => { const z = acalZoned(new Date(), tz); setAnchor(new Date(Date.UTC(z.y, z.m - 1, z.d))); };

  const navBtn = { width: 30, height: 30, borderRadius: 8, border: '1px solid oklch(89% 0.015 265)', background: '#fff', cursor: 'pointer', fontSize: 15, color: 'oklch(40% 0.04 265)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 };
  const segBtn = (on) => ({ padding: '6px 14px', fontSize: 12, fontWeight: 700, cursor: 'pointer', border: 'none', background: on ? 'oklch(22% 0.06 265)' : 'transparent', color: on ? '#fff' : 'oklch(44% 0.04 265)', fontFamily: "'Plus Jakarta Sans', sans-serif" });

  const colMinW = view === 'week' ? 96 : 280;
  const gridMinW = 52 + days.length * colMinW;

  return (
    <div style={{ fontFamily: "'Plus Jakarta Sans', sans-serif" }}>

      {/* ── Controls ── */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap', marginBottom: 12 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
          <button onClick={() => step(-1)} style={navBtn} aria-label="Previous">‹</button>
          <button onClick={goToday} style={{ ...navBtn, width: 'auto', padding: '0 12px', fontSize: 12, fontWeight: 700 }}>Today</button>
          <button onClick={() => step(1)} style={navBtn} aria-label="Next">›</button>
        </div>
        <div style={{ fontSize: 16, fontWeight: 700, color: 'oklch(22% 0.06 265)', minWidth: 0, flex: 1 }}>{navLabel}</div>
        <div style={{ display: 'flex', border: '1px solid oklch(89% 0.015 265)', borderRadius: 9, overflow: 'hidden' }}>
          <button onClick={() => setView('week')} style={segBtn(view === 'week')}>Week</button>
          <button onClick={() => setView('day')}  style={segBtn(view === 'day')}>Day</button>
        </div>
      </div>

      {/* Timezone picker */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12, fontSize: 12, color: 'oklch(48% 0.04 265)', flexWrap: 'wrap' }}>
        <span style={{ fontWeight: 700, color: 'oklch(40% 0.04 265)' }}>Timezone</span>
        <div style={{ position: 'relative', display: 'inline-flex', alignItems: 'center' }}>
          <select value={tzChoice} onChange={e => changeTz(e.target.value)}
            style={{ appearance: 'none', WebkitAppearance: 'none', background: 'oklch(96% 0.02 265)', border: '1px solid oklch(89% 0.02 265)', borderRadius: 20, padding: '4px 26px 4px 12px', fontSize: 12, fontWeight: 700, color: 'oklch(28% 0.06 265)', fontFamily: "'Plus Jakarta Sans', sans-serif", cursor: 'pointer' }}>
            {ACAL_TZS.map(o => {
              const z = o.id === '__local__' ? acalLocalTz() : o.id;
              return <option key={o.id} value={o.id}>{o.label} ({acalTzAbbr(z)}){o.id === '__local__' ? ' · device' : ''}</option>;
            })}
          </select>
          <span style={{ position: 'absolute', right: 10, pointerEvents: 'none', fontSize: 9, color: 'oklch(45% 0.04 265)' }}>▼</span>
        </div>
        <span style={{ color: 'oklch(56% 0.03 265)' }}>{tz} · {acalTzAbbr(tz)}</span>
      </div>

      {/* ── Teacher filter chips ── */}
      {teachers.length > 0 && (
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 7, marginBottom: 16, alignItems: 'center' }}>
          {teachers.map(t => {
            const on = !hidden.has(t.id);
            const c = acalColor(t.hue);
            return (
              <button key={t.id} onClick={() => toggleTeacher(t.id)} title={on ? 'Hide ' + t.name : 'Show ' + t.name}
                style={{
                  display: 'flex', alignItems: 'center', gap: 7, padding: '5px 11px', borderRadius: 20,
                  border: `1.5px solid ${on ? c.border : 'oklch(88% 0.015 265)'}`,
                  background: on ? c.bg : '#fff', cursor: 'pointer',
                  fontSize: 12, fontWeight: 600, color: on ? c.text : 'oklch(64% 0.03 265)',
                  fontFamily: "'Plus Jakarta Sans', sans-serif", opacity: on ? 1 : 0.7,
                }}>
                <span style={{ width: 9, height: 9, borderRadius: '50%', background: on ? c.dot : 'oklch(82% 0.02 265)', flexShrink: 0 }} />
                {t.name}
              </button>
            );
          })}
          {hidden.size > 0 && (
            <button onClick={() => persistHidden(new Set())}
              style={{ padding: '5px 11px', borderRadius: 20, border: '1px dashed oklch(80% 0.02 265)', background: '#fff', cursor: 'pointer', fontSize: 12, fontWeight: 600, color: 'oklch(48% 0.04 265)', fontFamily: "'Plus Jakarta Sans', sans-serif" }}>
              Show all
            </button>
          )}
        </div>
      )}

      {/* ── Grid ── */}
      {teachers.length === 0 ? (
        <div style={{ background: '#fff', border: '1px solid oklch(91% 0.01 265)', borderRadius: 14, padding: '56px 24px', textAlign: 'center', color: 'oklch(56% 0.03 265)', fontSize: 14 }}>
          No lessons scheduled {view === 'week' ? 'this week' : 'on this day'}.
        </div>
      ) : (
        <div style={{ border: '1px solid oklch(91% 0.01 265)', borderRadius: 12, overflow: 'auto', background: '#fff' }}>
          <div style={{ minWidth: gridMinW }}>

            {/* Day headers (sticky) */}
            <div style={{ display: 'flex', position: 'sticky', top: 0, zIndex: 3, background: '#fff', borderBottom: '1px solid oklch(91% 0.01 265)' }}>
              <div style={{ width: 52, flexShrink: 0 }} />
              {days.map((d, i) => {
                const today = dayKeys[i] === todayKey;
                return (
                  <div key={i} style={{ flex: 1, minWidth: colMinW, textAlign: 'center', padding: '8px 4px', borderLeft: '1px solid oklch(95% 0.006 60)', background: today ? 'oklch(98% 0.02 265)' : '#fff' }}>
                    <div style={{ fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.08em', color: today ? 'oklch(40% 0.13 265)' : 'oklch(58% 0.03 265)', fontWeight: 700 }}>
                      {view === 'week' ? ACAL_DAYS[i] : ACAL_DAYLONG[d.getUTCDay()]}
                    </div>
                    <div style={{ fontFamily: "'Cormorant Garamond', serif", fontSize: 20, fontWeight: 700, lineHeight: 1.1, color: today ? 'oklch(35% 0.13 265)' : 'oklch(34% 0.06 265)' }}>
                      {d.getUTCDate()}
                    </div>
                  </div>
                );
              })}
            </div>

            {/* Body */}
            <div style={{ display: 'flex', position: 'relative' }}>
              {/* Hour gutter */}
              <div style={{ width: 52, flexShrink: 0 }}>
                {hours.map(h => (
                  <div key={h} style={{ height: ACAL_HOUR_PX, fontSize: 10, color: 'oklch(64% 0.02 265)', textAlign: 'right', paddingRight: 7, paddingTop: 3, boxSizing: 'border-box' }}>
                    {acalFmtHour(h)}
                  </div>
                ))}
              </div>
              {/* Day columns */}
              {days.map((d, di) => {
                const key = dayKeys[di];
                const today = key === todayKey;
                const evs = dayEvents(key);
                const nowTop = today && nowZ.hour >= minH && nowZ.hour < maxH
                  ? ((nowZ.hour - minH) * 60 + nowZ.minute) / 60 * ACAL_HOUR_PX
                  : null;
                return (
                  <div key={di} style={{ flex: 1, minWidth: colMinW, position: 'relative', borderLeft: '1px solid oklch(95% 0.006 60)', background: today ? 'oklch(99.3% 0.006 265)' : '#fff', height: gridHeight }}>
                    {hours.map((h, hi) => (
                      <div key={h} style={{ position: 'absolute', top: hi * ACAL_HOUR_PX, left: 0, right: 0, height: ACAL_HOUR_PX, borderBottom: '1px solid oklch(95.5% 0.005 60)', boxSizing: 'border-box' }} />
                    ))}
                    {evs.map(ev => {
                      const top = ((ev.z.hour - minH) * 60 + ev.z.minute) / 60 * ACAL_HOUR_PX;
                      const hgt = Math.max((ev.b.durationMinutes || 60) / 60 * ACAL_HOUR_PX, 17);
                      const w = 100 / ev.lanes;
                      const c = acalColor(acalHashHue(ev.b.instructorId));
                      const past = ev.end < now.getTime();
                      const tall = hgt >= 30;
                      const first = (ev.b.student?.name || '?').split(' ')[0];
                      const tip = `${ev.b.student?.name || '?'} with ${ev.b.instructor?.name || '?'} · ${acalFmtClock(ev.z.hour, ev.z.minute)} · ${ev.b.status}`;
                      return (
                        <div key={ev.b.id} onClick={() => setActiveId(ev.b.id)} title={tip}
                          style={{
                            position: 'absolute', top, height: hgt,
                            left: `calc(${ev.lane * w}% + 1px)`, width: `calc(${w}% - 2px)`,
                            background: c.bg, borderLeft: `3px solid ${c.border}`, borderRadius: 4,
                            padding: '2px 5px', boxSizing: 'border-box', cursor: 'pointer', overflow: 'hidden',
                            opacity: past ? 0.62 : 1, boxShadow: '0 1px 2px oklch(20% 0.04 265 / 0.06)',
                          }}>
                          <div style={{ fontSize: 11, fontWeight: 700, color: c.text, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', lineHeight: 1.2 }}>
                            {first}{ev.b.rescheduleRequestedAt ? ' ↻' : ''}
                          </div>
                          {tall && (
                            <div style={{ fontSize: 9.5, color: c.text, opacity: 0.8, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                              {acalFmtClock(ev.z.hour, ev.z.minute)}{view === 'day' ? ` · ${ev.b.instructor?.name || ''}` : ''}
                            </div>
                          )}
                        </div>
                      );
                    })}
                    {nowTop != null && (
                      <div style={{ position: 'absolute', top: nowTop, left: 0, right: 0, height: 0, borderTop: '2px solid oklch(60% 0.2 25)', zIndex: 2 }}>
                        <div style={{ position: 'absolute', left: -3, top: -4, width: 7, height: 7, borderRadius: '50%', background: 'oklch(60% 0.2 25)' }} />
                      </div>
                    )}
                  </div>
                );
              })}
            </div>
          </div>
        </div>
      )}

      {/* Count footer */}
      <div style={{ marginTop: 10, fontSize: 12, color: 'oklch(56% 0.03 265)' }}>
        {visible.length} lesson{visible.length === 1 ? '' : 's'} shown
        {hidden.size > 0 ? ` · ${teachers.filter(t => hidden.has(t.id)).length} teacher${teachers.filter(t => hidden.has(t.id)).length === 1 ? '' : 's'} hidden` : ''}
        {' · '}{teachers.length} teacher{teachers.length === 1 ? '' : 's'} {view === 'week' ? 'this week' : 'today'}
      </div>

      {active && <AdminCalendarPopup booking={active} tz={tz} onClose={() => setActiveId(null)} />}
    </div>
  );
};

// ── Lesson detail popup (read-only) ─────────────────────────────────────────
const AdminCalendarPopup = ({ booking, tz, onClose }) => {
  const s = new Date(booking.scheduledAt);
  const e = new Date(s.getTime() + (booking.durationMinutes || 60) * 60000);
  const zs = acalZoned(s, tz), ze = acalZoned(e, tz);
  const c = acalColor(acalHashHue(booking.instructorId));
  const isPast = e.getTime() < Date.now();
  const badge = (label, bg, fg) => (
    <span style={{ background: bg, color: fg, borderRadius: 6, padding: '2px 9px', fontSize: 11, fontWeight: 700 }}>{label}</span>
  );
  return (
    <div onClick={onClose} style={{ position: 'fixed', inset: 0, background: 'rgba(20,20,35,0.4)', zIndex: 9998, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 18 }}>
      <div onClick={e2 => e2.stopPropagation()} style={{ background: '#fff', borderRadius: 16, padding: 0, width: 'min(420px, 100%)', boxShadow: '0 20px 60px rgba(0,0,0,0.3)', overflow: 'hidden', fontFamily: "'Plus Jakarta Sans', sans-serif" }}>
        <div style={{ height: 6, background: c.border }} />
        <div style={{ padding: '20px 22px' }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 }}>
            <div style={{ minWidth: 0 }}>
              <div style={{ fontSize: 18, fontWeight: 700, color: 'oklch(20% 0.05 265)' }}>{booking.student?.name || 'Unknown student'}</div>
              <div style={{ fontSize: 13, color: 'oklch(50% 0.04 265)', marginTop: 2, display: 'flex', alignItems: 'center', gap: 6 }}>
                <span style={{ width: 9, height: 9, borderRadius: '50%', background: c.dot }} />
                with {booking.instructor?.name || 'Unknown'}
              </div>
            </div>
            <button onClick={onClose} style={{ background: 'oklch(96% 0.01 265)', border: '1px solid oklch(90% 0.01 265)', borderRadius: 8, width: 30, height: 30, cursor: 'pointer', fontSize: 16, color: 'oklch(45% 0.04 265)', flexShrink: 0 }}>×</button>
          </div>

          <div style={{ marginTop: 16, fontSize: 13, color: 'oklch(30% 0.05 265)', lineHeight: 1.7 }}>
            <div><b>{ACAL_DAYLONG[new Date(Date.UTC(zs.y, zs.m - 1, zs.d)).getUTCDay()]}, {ACAL_MONTHS[zs.m - 1]} {zs.d}</b></div>
            <div>{acalFmtClock(zs.hour, zs.minute)} – {acalFmtClock(ze.hour, ze.minute)} · {booking.durationMinutes || 60} min <span style={{ color: 'oklch(60% 0.03 265)' }}>({acalTzAbbr(tz)})</span></div>
          </div>

          <div style={{ marginTop: 14, display: 'flex', flexWrap: 'wrap', gap: 7 }}>
            {badge(booking.status, 'oklch(95% 0.02 265)', 'oklch(35% 0.06 265)')}
            {booking.status !== 'cancelled' && (booking.paid
              ? badge('✓ paid', 'oklch(93% 0.09 150)', 'oklch(30% 0.13 150)')
              : (isPast && badge('unpaid', 'oklch(95% 0.08 25)', 'oklch(40% 0.16 25)')))}
            {booking.isRecurring && badge('↻ recurring', 'oklch(95% 0.05 265)', 'oklch(38% 0.12 265)')}
            {/trial/i.test(booking.message || '') && badge('trial', 'oklch(95% 0.08 80)', 'oklch(38% 0.13 80)')}
            {booking.paymentMethod === 'cash' && badge('cash', 'oklch(95% 0.02 265)', 'oklch(40% 0.05 265)')}
          </div>

          {booking.message && (
            <div style={{ marginTop: 14, background: 'oklch(97% 0.008 265)', borderRadius: 9, padding: '10px 12px', fontSize: 12.5, color: 'oklch(40% 0.04 265)', whiteSpace: 'pre-wrap' }}>
              {booking.message}
            </div>
          )}

          {booking.meetingLink && (
            <a href={booking.meetingLink} target="_blank" rel="noopener noreferrer"
              style={{ display: 'block', marginTop: 16, textAlign: 'center', background: 'oklch(22% 0.06 265)', color: '#fff', borderRadius: 9, padding: '10px 0', fontSize: 13, fontWeight: 700, textDecoration: 'none' }}>
              Open meeting link
            </a>
          )}

          <div style={{ marginTop: 14, fontSize: 11, color: 'oklch(62% 0.03 265)', textAlign: 'center' }}>
            To reschedule, mark paid, or delete, use the Schedule tab.
          </div>
        </div>
      </div>
    </div>
  );
};

Object.assign(window, { AdminCalendar, AdminCalendarPopup });
