/* global React, Icon, money, dayLabel, timeLabel, PageSkeleton, DemoChip, ConfirmDialog,
   Chips, PersonAvatar, useNetwork, useOpsFeed, useCoverage, OPS_RANGES, BOOKING_STATES,
   BOOKING_STATE_KEYS, holdsSeat, seatsOf, setBookingStatus, deleteBooking,
   useTeeSlots, saveTeeSlot, deleteTeeSlot, useEventsAdmin, saveEvent, deleteEvent, Row, Field, StickySave, useDirty */
// Operations — the day-to-day of the network: who is booked, where the tee
// sheet has holes, and what is on the calendar.
//
// The old modules were one course at a time behind a dropdown. These start
// from the network and let you filter down, because the question an admin
// opens this on is "what needs me today", not "show me Melreese".

const opsName = (p) => {
  if (!p) return 'Unknown';
  const full = [p.first_name, p.last_name].filter(Boolean).join(' ');
  return full || (p.handle ? `@${String(p.handle).replace(/^@/, '')}` : 'Unknown');
};
const opsWhen = (iso) => new Date(iso).toLocaleString('en-US', {
  weekday: 'short', month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit',
});
const opsPct = (v) => (v == null ? '—' : `${Math.round(v * 100)}%`);
const courseLabel = (c) => (c ? (c.short_name || c.name) : 'Unknown course');

// ─── KPI row ──────────────────────────────────────────────────────────
// The same block Money and People use, so the three Operations screens
// read as the same product rather than three different tables.
function OpsKpis({ items }) {
  return (
    <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(168px, 1fr))', gap: 12, marginBottom: 16 }}>
      {items.map(k => (
        <div key={k.label} className="card" style={{ padding: '15px 16px' }}>
          <div className="eyebrow">{k.label}</div>
          <div style={{ fontFamily: 'var(--font-display)', fontSize: 25, color: 'var(--paper)', marginTop: 3, fontVariantNumeric: 'tabular-nums' }}>{k.value}</div>
          <div style={{ fontSize: 11.5, color: 'var(--ink-muted)', marginTop: 2 }}>{k.sub}</div>
        </div>
      ))}
    </div>
  );
}

// ─── Booking status pill ──────────────────────────────────────────────
// Shape before colour, the way match results and payouts already read:
// live states are filled, finished states are outlined, and a released
// seat is dashed — so "this seat is back on sale" is legible at a glance.
function BookingPill({ status }) {
  const meta = BOOKING_STATES[status] || { label: status };
  const s = meta.released
    ? { background: 'transparent', color: 'var(--ink-muted)', border: '1px dashed var(--ink-faint)' }
    : meta.live
      ? { background: 'var(--cream)', color: 'var(--forest)', border: '1px solid var(--cream)' }
      : { background: 'transparent', color: 'var(--ink)', border: '1px solid var(--ink-faint)' };
  return <span className="pill-mono" style={{ ...s, fontSize: 9, padding: '3px 8px' }}>{meta.label || status}</span>;
}

// ─── BookingsFeed ─────────────────────────────────────────────────────
function BookingsFeed({ onOpenCourse }) {
  const [net] = useNetwork();
  const courses = net ? net.courses : null;
  const [range, setRange] = React.useState('today');
  const [feed, reload] = useOpsFeed(courses, range);
  const [courseId, setCourseId] = React.useState('all');
  const [status, setStatus] = React.useState('all');
  const [query, setQuery] = React.useState('');
  const [busy, setBusy] = React.useState('');
  const [err, setErr] = React.useState('');
  const [confirm, setConfirm] = React.useState(null);

  if (!net || !feed) return <PageSkeleton/>;

  const term = query.trim().toLowerCase();
  const shown = feed.rows.filter(r =>
    (courseId === 'all' || r.course.id === courseId)
    && (status === 'all' || r.status === status)
    && (!term || opsName(r.user).toLowerCase().includes(term)
      || String(r.user && r.user.handle || '').toLowerCase().includes(term)
      || courseLabel(r.course).toLowerCase().includes(term)))
    .sort((a, b) => new Date(a.slot.starts_at) - new Date(b.slot.starts_at));

  const statusCounts = { all: feed.rows.length };
  BOOKING_STATE_KEYS.forEach(k => { statusCounts[k] = feed.rows.filter(r => r.status === k).length; });
  const courseCounts = {};
  feed.rows.forEach(r => { courseCounts[r.course.id] = (courseCounts[r.course.id] || 0) + 1; });

  const t = feed.totals;

  async function change(row, next) {
    if (feed.demo) { setErr('Demo Mode is on — these bookings are generated, so nothing is written.'); return; }
    setBusy(row.id); setErr('');
    try { await setBookingStatus(row.id, next); reload(); }
    catch (e) { setErr(e.message); }
    setBusy('');
  }

  async function remove(row) {
    if (feed.demo) { setErr('Demo Mode is on — these bookings are generated, so nothing is written.'); setConfirm(null); return; }
    setBusy(row.id); setErr('');
    try { await deleteBooking(row.id); reload(); }
    catch (e) { setErr(e.message); }
    setBusy(''); setConfirm(null);
  }

  return (
    <div style={{ maxWidth: 1240, margin: '0 auto' }}>
      {feed.demo && <div style={{ marginBottom: 12 }}><DemoChip corner={false}/></div>}

      <OpsKpis items={[
        { label: 'Bookings', value: t.bookings.toLocaleString(), sub: `${t.courses} course${t.courses === 1 ? '' : 's'} with times` },
        { label: 'Seats Filled', value: `${t.filled}/${t.seats}`, sub: t.fill == null ? 'No published seats' : `${opsPct(t.fill)} of the sheet` },
        { label: 'Booking Value', value: money(t.value), sub: 'Held seats only' },
        { label: 'Released', value: t.cancelled.toLocaleString(), sub: 'Cancelled or no-show' },
      ]}/>

      <div style={{ display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap', marginBottom: 12 }}>
        <Chips value={range} onChange={setRange} options={OPS_RANGES.map(r => ({ key: r.key, label: r.label }))}/>
        <div style={{ position: 'relative', flex: '1 1 200px', minWidth: 180 }}>
          <input className="input" value={query} onChange={e => setQuery(e.target.value)}
            placeholder="Search golfer or course" aria-label="Search bookings" style={{ paddingLeft: 32 }}/>
          <Icon name="users" size={14} style={{ position: 'absolute', left: 11, top: '50%', transform: 'translateY(-50%)', opacity: 0.45, pointerEvents: 'none' }}/>
        </div>
      </div>

      <div style={{ display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap', marginBottom: 12 }}>
        <Chips value={status} onChange={setStatus} options={[
          { key: 'all', label: 'Any status', count: statusCounts.all },
          ...BOOKING_STATE_KEYS.filter(k => statusCounts[k]).map(k => ({ key: k, label: BOOKING_STATES[k].label, count: statusCounts[k] })),
        ]}/>
        <Chips value={courseId} onChange={setCourseId} options={[
          { key: 'all', label: 'All courses' },
          ...net.courses.filter(c => courseCounts[c.id]).map(c => ({ key: c.id, label: courseLabel(c), count: courseCounts[c.id] })),
        ]}/>
      </div>

      {err && (
        <div className="card" style={{ padding: '13px 16px', marginBottom: 14, borderLeft: '3px solid var(--loss)' }}>
          <div style={{ fontSize: 13, color: 'var(--ink-soft)', lineHeight: 1.5 }}>{err}</div>
        </div>
      )}

      <div className="card" style={{ padding: 0, overflow: 'hidden' }}>
        <div style={{ overflowX: 'auto' }}>
          <table className="table" style={{ minWidth: 940 }}>
            <thead>
              <tr>
                <th>Golfer</th>
                <th>Course</th>
                <th>Tee time</th>
                <th>Match</th>
                <th style={{ textAlign: 'right' }}>Value</th>
                <th>Status</th>
                <th style={{ textAlign: 'right' }}>&nbsp;</th>
              </tr>
            </thead>
            <tbody>
              {shown.map(r => (
                <tr key={r.id}>
                  <td>
                    <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
                      <PersonAvatar src={r.user && r.user.avatar_url} name={opsName(r.user)} size={28}/>
                      <span style={{ minWidth: 0 }}>
                        <span style={{ display: 'block', fontWeight: 700, color: 'var(--paper)' }}>{opsName(r.user)}</span>
                        <span style={{ display: 'block', fontSize: 11.5, color: 'var(--ink-muted)' }}>
                          {r.match_type === '2v2' && r.partner ? `+ ${opsName(r.partner)}`
                            : r.match_type === '2v2' && r.needs_partner ? 'needs a partner'
                              : (r.user && r.user.handle ? `@${String(r.user.handle).replace(/^@/, '')}` : '—')}
                        </span>
                      </span>
                    </div>
                  </td>
                  <td>
                    <button onClick={() => onOpenCourse && onOpenCourse(r.course)}
                      style={{ background: 'none', border: 'none', padding: 0, font: 'inherit', cursor: 'pointer', color: 'var(--paper)', fontWeight: 700, textAlign: 'left' }}>
                      {courseLabel(r.course)}
                    </button>
                  </td>
                  <td style={{ fontSize: 12.5, color: 'var(--ink-soft)', whiteSpace: 'nowrap' }}>{opsWhen(r.slot.starts_at)}</td>
                  <td style={{ fontFamily: 'var(--font-mono)', fontSize: 11.5, color: 'var(--ink-muted)' }}>{r.match_type || '—'}</td>
                  <td style={{ textAlign: 'right', fontFamily: 'var(--font-mono)', fontVariantNumeric: 'tabular-nums', color: holdsSeat(r) ? 'var(--paper)' : 'var(--ink-faint)' }}>
                    {money(r.value)}
                  </td>
                  <td><BookingPill status={r.status}/></td>
                  <td style={{ textAlign: 'right' }}>
                    <div style={{ display: 'flex', gap: 6, justifyContent: 'flex-end' }}>
                      {/* The pill states where the booking is; this states
                          where you are moving it to. A select showing the
                          current value would have said the same thing twice. */}
                      <select className="select" style={{ width: 112, padding: '4px 8px', fontSize: 11.5 }}
                        value="" disabled={busy === r.id}
                        aria-label={`Change status for ${opsName(r.user)}`}
                        onChange={e => { if (e.target.value) change(r, e.target.value); }}>
                        <option value="">Change…</option>
                        {BOOKING_STATE_KEYS.filter(k => k !== r.status)
                          .map(k => <option key={k} value={k}>{BOOKING_STATES[k].label}</option>)}
                      </select>
                      <button className="btn btn-ghost" style={{ padding: '4px 10px', fontSize: 11.5 }}
                        disabled={busy === r.id} onClick={() => setConfirm(r)}>Delete</button>
                    </div>
                  </td>
                </tr>
              ))}
              {!shown.length && (
                <tr>
                  <td colSpan={7} style={{ padding: 30, textAlign: 'center', color: 'var(--ink-muted)' }}>
                    {feed.rows.length
                      ? 'No bookings match these filters.'
                      : 'Nothing booked in this window. Published tee times show up here the moment somebody takes one.'}
                  </td>
                </tr>
              )}
            </tbody>
          </table>
        </div>
      </div>

      {confirm && (
        <ConfirmDialog
          open
          title="Delete this booking?"
          body={`${opsName(confirm.user)} at ${courseLabel(confirm.course)}, ${opsWhen(confirm.slot.starts_at)}. The seat goes back on sale and the golfer is not told — cancelling instead keeps the record.`}
          confirmLabel="Delete"
          onConfirm={() => remove(confirm)}
          onCancel={() => setConfirm(null)}/>
      )}
    </div>
  );
}

// ─── Coverage cell ────────────────────────────────────────────────────
// Fill drives the ink: an empty day is dashed and hollow, a busy one is
// nearly solid cream. Reading the grid should be a squint, not a scan.
function CoverageCell({ cell, onClick, label }) {
  const fill = cell.seats ? Math.min(1, cell.filled / cell.seats) : 0;
  const empty = !cell.slots;
  return (
    <button onClick={onClick} title={label}
      style={{
        display: 'block', width: '100%', height: 38, cursor: 'pointer', font: 'inherit',
        borderRadius: 7, padding: 0,
        background: empty ? 'transparent' : `color-mix(in srgb, var(--cream) ${Math.round(14 + fill * 76)}%, transparent)`,
        border: empty ? '1px dashed var(--line-strong)' : '1px solid transparent',
        color: fill > 0.45 ? 'var(--forest)' : 'var(--paper)',
        fontFamily: 'var(--font-mono)', fontSize: 11, fontWeight: 700, fontVariantNumeric: 'tabular-nums',
      }}>
      {empty ? '—' : `${cell.filled}/${cell.seats}`}
    </button>
  );
}

// ─── TeeSheet ─────────────────────────────────────────────────────────
function TeeSheet({ onOpenCourse }) {
  const [net] = useNetwork();
  const courses = net ? net.courses : null;
  const [days, setDays] = React.useState(14);
  const [grid, reload] = useCoverage(courses, days);
  const [drill, setDrill] = React.useState(null); // { course, date }

  if (!net || !grid) return <PageSkeleton/>;

  if (drill) {
    return <CourseDaySlots course={drill.course} date={drill.date}
      onBack={() => { setDrill(null); reload(); }} onOpenCourse={onOpenCourse}/>;
  }

  const openTomorrow = grid.dates[1];
  const gapsTomorrow = grid.gaps.filter(g => g.date === openTomorrow);
  const totalSeats = Object.values(grid.cells).reduce((n, row) =>
    n + Object.values(row).reduce((m, c) => m + c.seats, 0), 0);
  const totalFilled = Object.values(grid.cells).reduce((n, row) =>
    n + Object.values(row).reduce((m, c) => m + c.filled, 0), 0);
  const publishing = net.courses.filter(c =>
    Object.values(grid.cells[c.id] || {}).some(cell => cell.slots)).length;

  return (
    <div style={{ maxWidth: 1240, margin: '0 auto' }}>
      {grid.demo && <div style={{ marginBottom: 12 }}><DemoChip corner={false}/></div>}

      <OpsKpis items={[
        { label: 'Courses Publishing', value: `${publishing}/${net.courses.length}`, sub: `In the next ${days} days` },
        { label: 'Seats Published', value: totalSeats.toLocaleString(), sub: `${totalFilled.toLocaleString()} taken` },
        { label: 'Network Fill', value: totalSeats ? opsPct(totalFilled / totalSeats) : '—', sub: 'Held seats over published' },
        { label: 'Gaps Tomorrow', value: String(gapsTomorrow.length), sub: gapsTomorrow.length ? 'Courses with no times' : 'Every course has times' },
      ]}/>

      <div style={{ display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap', marginBottom: 12 }}>
        <Chips value={String(days)} onChange={v => setDays(Number(v))} options={[
          { key: '7', label: '7 days' }, { key: '14', label: '14 days' }, { key: '30', label: '30 days' },
        ]}/>
        <div style={{ flex: 1 }}/>
        <div style={{ fontSize: 11.5, color: 'var(--ink-muted)' }}>Filled seats over published seats · click a day to edit it</div>
      </div>

      {gapsTomorrow.length > 0 && (
        <div className="card" style={{ padding: '13px 16px', marginBottom: 14, borderLeft: '3px solid var(--loss)' }}>
          <div style={{ fontSize: 13, color: 'var(--ink-soft)', lineHeight: 1.5 }}>
            <strong style={{ color: 'var(--paper)' }}>No tee times published for tomorrow</strong> at{' '}
            {gapsTomorrow.map(g => courseLabel(g.course)).join(', ')}. Nothing can be booked there until somebody publishes a sheet.
          </div>
        </div>
      )}

      <div className="card" style={{ padding: 0, overflow: 'hidden' }}>
        <div style={{ overflowX: 'auto' }}>
          <table className="table" style={{ minWidth: 120 + days * 56 }}>
            <thead>
              <tr>
                <th style={{ position: 'sticky', left: 0, background: 'var(--surface)', zIndex: 1, minWidth: 150 }}>Course</th>
                {grid.dates.map((d, i) => (
                  <th key={d} style={{ textAlign: 'center', minWidth: 52, fontSize: 10 }}>
                    {i === 0 ? 'Today' : dayLabel(d)}
                  </th>
                ))}
              </tr>
            </thead>
            <tbody>
              {net.courses.map(c => (
                <tr key={c.id}>
                  <td style={{ position: 'sticky', left: 0, background: 'var(--surface)', zIndex: 1 }}>
                    <button onClick={() => onOpenCourse && onOpenCourse(c)}
                      style={{ background: 'none', border: 'none', padding: 0, font: 'inherit', cursor: 'pointer', color: 'var(--paper)', fontWeight: 700, textAlign: 'left' }}>
                      {courseLabel(c)}
                    </button>
                  </td>
                  {grid.dates.map(d => (
                    <td key={d} style={{ padding: '4px 3px' }}>
                      <CoverageCell cell={grid.cells[c.id][d]} onClick={() => setDrill({ course: c, date: d })}
                        label={`${courseLabel(c)} · ${d} · ${grid.cells[c.id][d].slots} slot${grid.cells[c.id][d].slots === 1 ? '' : 's'}`}/>
                    </td>
                  ))}
                </tr>
              ))}
              {!net.courses.length && (
                <tr><td colSpan={days + 1} style={{ padding: 30, textAlign: 'center', color: 'var(--ink-muted)' }}>
                  No partner courses yet.
                </td></tr>
              )}
            </tbody>
          </table>
        </div>
      </div>
    </div>
  );
}

// ─── One course, one day ──────────────────────────────────────────────
function CourseDaySlots({ course, date, onBack, onOpenCourse }) {
  const [all, reload] = useTeeSlots(course.id);
  const [editing, setEditing] = React.useState(null); // null | 'new' | slot
  const slots = (all || []).filter(s => String(s.starts_at).slice(0, 10) === date
    || new Date(s.starts_at).toDateString() === new Date(`${date}T12:00:00`).toDateString());

  return (
    <div style={{ maxWidth: 820, margin: '0 auto' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap', marginBottom: 18 }}>
        <button className="btn btn-ghost" onClick={onBack} style={{ padding: '6px 12px' }}>← Tee sheet</button>
        <div style={{ flex: 1, minWidth: 160 }}>
          <div style={{ fontFamily: 'var(--font-display)', fontSize: 21, color: 'var(--paper)', lineHeight: 1.1 }}>{courseLabel(course)}</div>
          <div style={{ fontSize: 12.5, color: 'var(--ink-muted)', marginTop: 2, fontFamily: 'var(--font-mono)' }}>{date}</div>
        </div>
        <button className="btn btn-ghost" onClick={() => onOpenCourse && onOpenCourse(course)}>Course details</button>
      </div>

      {editing ? (
        <SlotEditor course={course} date={date} slot={editing === 'new' ? null : editing}
          onClose={() => setEditing(null)}
          onSaved={() => { setEditing(null); reload(); }}/>
      ) : (
        <>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }}>
            <div style={{ fontSize: 13, color: 'var(--ink-muted)' }}>
              {all === null ? 'Loading…' : `${slots.length} tee time${slots.length === 1 ? '' : 's'} on this day`}
            </div>
            <button className="btn btn-forest" onClick={() => setEditing('new')}>+ Add tee time</button>
          </div>

          {all === null ? <PageSkeleton/> : (
            <div className="card" style={{ padding: 0, overflow: 'hidden' }}>
              <table className="table">
                <thead>
                  <tr>
                    <th>Time</th>
                    <th style={{ textAlign: 'right' }}>Seats</th>
                    <th style={{ textAlign: 'right' }}>Price</th>
                    <th>Status</th>
                    <th style={{ textAlign: 'right' }}>&nbsp;</th>
                  </tr>
                </thead>
                <tbody>
                  {slots.map(s => (
                    <tr key={s.id}>
                      <td style={{ fontWeight: 700, color: 'var(--paper)' }}>{timeLabel(s.starts_at)}</td>
                      <td style={{ textAlign: 'right', fontFamily: 'var(--font-mono)' }}>{seatsOf(s)}</td>
                      <td style={{ textAlign: 'right', fontFamily: 'var(--font-mono)', color: 'var(--paper)' }}>{money(s.price)}</td>
                      <td>
                        <span className="pill-mono" style={{
                          fontSize: 9, padding: '3px 8px',
                          ...(s.status === 'open'
                            ? { background: 'var(--cream)', color: 'var(--forest)', border: '1px solid var(--cream)' }
                            : { background: 'transparent', color: 'var(--ink-muted)', border: '1px dashed var(--ink-faint)' }),
                        }}>{s.status || 'open'}</span>
                      </td>
                      <td style={{ textAlign: 'right' }}>
                        <button className="btn btn-ghost" style={{ padding: '4px 10px', fontSize: 11.5 }}
                          onClick={() => setEditing(s)}>Edit</button>
                      </td>
                    </tr>
                  ))}
                  {!slots.length && (
                    <tr><td colSpan={5} style={{ padding: 30, textAlign: 'center', color: 'var(--ink-muted)' }}>
                      Nothing published for this day. Nothing can be booked until there is.
                    </td></tr>
                  )}
                </tbody>
              </table>
            </div>
          )}
        </>
      )}
    </div>
  );
}

// Local datetime helpers for the slot editor. Named for this file so they
// cannot collide with anything else in the shared global scope.
function opsToLocalInput(iso) {
  if (!iso) return '';
  const d = new Date(iso), p = n => String(n).padStart(2, '0');
  return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}T${p(d.getHours())}:${p(d.getMinutes())}`;
}
const opsFromLocalInput = (s) => (s ? new Date(s).toISOString() : null);

function SlotEditor({ course, date, slot, onClose, onSaved }) {
  const isNew = !slot;
  const [form, setForm] = React.useState(() => ({
    id: slot ? slot.id : undefined,
    startsLocal: slot ? opsToLocalInput(slot.starts_at) : `${date}T16:00`,
    capacity: slot ? seatsOf(slot) : 4,
    price: slot ? slot.price : (course.suggested_price || 0),
    status: slot ? (slot.status || 'open') : 'open',
  }));
  const [saving, setSaving] = React.useState(false);
  const [err, setErr] = React.useState('');
  const [confirm, setConfirm] = React.useState(false);
  const set = (k, v) => setForm(f => ({ ...f, [k]: v }));

  async function save() {
    setSaving(true); setErr('');
    try {
      await saveTeeSlot({
        id: form.id, course_id: course.id,
        starts_at: opsFromLocalInput(form.startsLocal),
        capacity: form.capacity, price: form.price,
        type: 'open', title: '', status: form.status,
      });
      onSaved();
    } catch (e) { setErr(e.message || 'Could not save.'); setSaving(false); }
  }
  async function remove() {
    setSaving(true); setErr('');
    try { await deleteTeeSlot(form.id); onSaved(); }
    catch (e) { setErr(e.message || 'Could not delete.'); setSaving(false); setConfirm(false); }
  }

  return (
    <div className="card" style={{ padding: 22 }}>
      <div className="eyebrow" style={{ marginBottom: 14 }}>{isNew ? 'New tee time' : 'Edit tee time'}</div>
      <Row>
        <Field label="Date & time" full>
          <input className="input" type="datetime-local" value={form.startsLocal} onChange={e => set('startsLocal', e.target.value)}/>
        </Field>
      </Row>
      <Row>
        <Field label="Seats"><input className="input" type="number" min="1" max="8" value={form.capacity} onChange={e => set('capacity', e.target.value)}/></Field>
        <Field label="Price ($)"><input className="input" type="number" min="0" value={form.price} onChange={e => set('price', e.target.value)}/></Field>
      </Row>
      <Row>
        <Field label="Status" full>
          <select className="select" value={form.status} onChange={e => set('status', e.target.value)}>
            <option value="open">open</option>
            <option value="closed">closed</option>
          </select>
        </Field>
      </Row>

      {course.suggested_price ? (
        <div className="suggest-pill" style={{ marginBottom: 14 }}>
          Suggested price: {money(course.suggested_price)} — about 40% of this course&rsquo;s normal tee-time rate for the window.
        </div>
      ) : null}

      {err && <div className="form-error" role="alert" style={{ marginBottom: 14 }}>{err}</div>}

      <div style={{ display: 'flex', gap: 10, alignItems: 'center' }}>
        <button className="btn btn-forest" onClick={save} disabled={saving}>{saving ? 'Saving…' : (isNew ? 'Create' : 'Save changes')}</button>
        <button className="btn btn-ghost" onClick={onClose} disabled={saving}>Cancel</button>
        {!isNew && <button className="btn btn-danger" onClick={() => setConfirm(true)} disabled={saving} style={{ marginLeft: 'auto' }}>Delete</button>}
      </div>

      {confirm && (
        <ConfirmDialog open title="Delete this tee time?"
          body="Any booking on it is orphaned rather than refunded. Closing the slot keeps the record and stops new bookings."
          confirmLabel="Delete" onConfirm={remove} onCancel={() => setConfirm(false)}/>
      )}
    </div>
  );
}

// ─── EventsBoard ──────────────────────────────────────────────────────
// Corporate events never appear in public listings, so the board says so
// on the row rather than leaving it to whoever remembers the rule.
const EVENT_TYPES = ['weekly', 'major', 'social', 'member-only', 'corporate'];
const EVENT_STATUSES = ['open', 'live', 'member-only', 'closed', 'cancelled'];

function EventsBoard() {
  const [events, reload] = useEventsAdmin();
  const [editing, setEditing] = React.useState(null); // null | 'new' | row
  const [type, setType] = React.useState('all');
  const [when, setWhen] = React.useState('upcoming');

  if (editing) {
    return <EventForm event={editing === 'new' ? null : editing}
      onClose={() => setEditing(null)} onSaved={() => { setEditing(null); reload(); }}/>;
  }
  if (events === null) return <PageSkeleton/>;

  const now = Date.now();
  const shown = events.filter(e =>
    (type === 'all' || e.type === type)
    && (when === 'all'
      || (when === 'upcoming' ? new Date(e.starts_at).getTime() >= now : new Date(e.starts_at).getTime() < now)))
    .sort((a, b) => (when === 'past' ? new Date(b.starts_at) - new Date(a.starts_at) : new Date(a.starts_at) - new Date(b.starts_at)));

  const upcoming = events.filter(e => new Date(e.starts_at).getTime() >= now);
  const typeCounts = {};
  events.forEach(e => { typeCounts[e.type] = (typeCounts[e.type] || 0) + 1; });
  const field = upcoming.reduce((n, e) => n + (Number(e.field) || 0), 0);
  const anyCounts = upcoming.some(e => e.filled != null);
  const filled = upcoming.reduce((n, e) => n + (Number(e.filled) || 0), 0);

  return (
    <div style={{ maxWidth: 1120, margin: '0 auto' }}>
      <OpsKpis items={[
        { label: 'Upcoming', value: String(upcoming.length), sub: `${events.length} on the books` },
        { label: 'Majors', value: String(upcoming.filter(e => e.is_major || e.type === 'major').length), sub: 'Separate pricing & capacity' },
        { label: 'Registered', value: anyCounts ? `${filled}/${field}` : field.toLocaleString(),
          sub: anyCounts ? (field ? `${Math.round((filled / field) * 100)}% of the field sold` : 'No field set') : 'Seats across upcoming events' },
        { label: 'Private', value: String(upcoming.filter(e => e.type === 'corporate').length), sub: 'Corporate — never listed publicly' },
      ]}/>

      <div style={{ display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap', marginBottom: 12 }}>
        <Chips value={when} onChange={setWhen} options={[
          { key: 'upcoming', label: 'Upcoming', count: upcoming.length },
          { key: 'past', label: 'Past', count: events.length - upcoming.length },
          { key: 'all', label: 'All', count: events.length },
        ]}/>
        <Chips value={type} onChange={setType} options={[
          { key: 'all', label: 'Any type' },
          ...EVENT_TYPES.filter(t => typeCounts[t]).map(t => ({ key: t, label: t, count: typeCounts[t] })),
        ]}/>
        <div style={{ flex: 1 }}/>
        <button className="btn btn-forest" onClick={() => setEditing('new')}>+ Add event</button>
      </div>

      <div className="card" style={{ padding: 0, overflow: 'hidden' }}>
        <div style={{ overflowX: 'auto' }}>
          <table className="table" style={{ minWidth: 820 }}>
            <thead>
              <tr>
                <th>Event</th>
                <th>When</th>
                <th style={{ textAlign: 'right' }}>Filled</th>
                <th style={{ textAlign: 'right' }}>Member / Walk-up</th>
                <th>Status</th>
              </tr>
            </thead>
            <tbody>
              {shown.map(e => (
                <tr key={e.id} onClick={() => setEditing(e)} style={{ cursor: 'pointer' }}>
                  <td>
                    <div style={{ display: 'flex', alignItems: 'center', gap: 7, flexWrap: 'wrap' }}>
                      <span style={{ fontWeight: 700, color: 'var(--paper)' }}>{e.course_short}</span>
                      {(e.is_major || e.type === 'major') && (
                        <span className="pill-mono" style={{ fontSize: 8.5, padding: '2px 6px', background: 'var(--cream)', color: 'var(--forest)', border: '1px solid var(--cream)' }}>Major</span>
                      )}
                      {e.type === 'corporate' && (
                        <span className="pill-mono" style={{ fontSize: 8.5, padding: '2px 6px', background: 'transparent', color: 'var(--ink-muted)', border: '1px dashed var(--ink-faint)' }}>Private</span>
                      )}
                    </div>
                    <div style={{ fontSize: 11.5, color: 'var(--ink-muted)', marginTop: 2 }}>{e.tagline || e.course_name}</div>
                  </td>
                  <td style={{ fontSize: 12.5, color: 'var(--ink-soft)', whiteSpace: 'nowrap' }}>{opsWhen(e.starts_at)}</td>
                  <td style={{ textAlign: 'right', fontFamily: 'var(--font-mono)' }}>
                    {e.filled == null ? e.field : (
                      <span style={{ color: e.filled >= e.field ? 'var(--paper)' : 'inherit' }}>
                        {e.filled}<span style={{ color: 'var(--ink-muted)' }}>/{e.field}</span>
                        {e.filled >= e.field && <span style={{ marginLeft: 6, fontSize: 10 }}>FULL</span>}
                      </span>
                    )}
                  </td>
                  <td style={{ textAlign: 'right', fontFamily: 'var(--font-mono)', color: 'var(--paper)' }}>
                    {money(e.price_member)} / {money(e.price_walkup)}
                  </td>
                  <td>
                    <span className="pill-mono" style={{
                      fontSize: 9, padding: '3px 8px',
                      ...(e.status === 'open' || e.status === 'live'
                        ? { background: 'var(--cream)', color: 'var(--forest)', border: '1px solid var(--cream)' }
                        : { background: 'transparent', color: 'var(--ink-muted)', border: '1px dashed var(--ink-faint)' }),
                    }}>{e.status}</span>
                  </td>
                </tr>
              ))}
              {!shown.length && (
                <tr><td colSpan={5} style={{ padding: 30, textAlign: 'center', color: 'var(--ink-muted)' }}>
                  {events.length ? 'No events match these filters.' : 'No events yet. Add a league night or a major.'}
                </td></tr>
              )}
            </tbody>
          </table>
        </div>
      </div>
    </div>
  );
}

function EventForm({ event, onClose, onSaved }) {
  const isNew = !event;
  const [form, setForm] = React.useState(() => ({
    id: event ? event.id : undefined,
    course_short: event ? event.course_short : '',
    course_name: event ? event.course_name : '',
    startsLocal: event ? opsToLocalInput(event.starts_at) : '',
    field: event ? event.field : 24,
    type: event ? (event.type || 'weekly') : 'weekly',
    tagline: event ? (event.tagline || '') : '',
    description: event ? (event.description || '') : '',
    img_url: event ? (event.img_url || '') : '',
    price_walkup: event ? (event.price_walkup != null ? event.price_walkup : 20) : 20,
    price_member: event ? (event.price_member != null ? event.price_member : 0) : 0,
    status: event ? (event.status || 'open') : 'open',
  }));
  const [saving, setSaving] = React.useState(false);
  const [err, setErr] = React.useState('');
  const [confirm, setConfirm] = React.useState(false);
  const pristine = React.useRef(null);
  if (pristine.current === null) pristine.current = form;
  const dirty = useDirty(form, pristine);
  const set = (k, v) => setForm(f => ({ ...f, [k]: v }));

  // The pricing rule, checked as you type rather than only on save: walk-up
  // must sit above the member price, or the membership stops being worth
  // buying. Same rule the data layer enforces.
  const priceBad = Number(form.price_walkup) <= Number(form.price_member);

  async function save() {
    setSaving(true); setErr('');
    try {
      await saveEvent({
        id: form.id, course_short: form.course_short, course_name: form.course_name,
        starts_at: opsFromLocalInput(form.startsLocal), field: form.field, type: form.type,
        tagline: form.tagline, description: form.description, img_url: form.img_url,
        price_walkup: form.price_walkup, price_member: form.price_member, status: form.status,
      });
      onSaved();
    } catch (e) { setErr(e.message || 'Could not save.'); setSaving(false); }
  }
  async function remove() {
    setSaving(true); setErr('');
    try { await deleteEvent(form.id); onSaved(); }
    catch (e) { setErr(e.message || 'Could not delete.'); setSaving(false); setConfirm(false); }
  }

  return (
    <div style={{ maxWidth: 860, margin: '0 auto' }}>
      <StickySave dirty={isNew ? !!String(form.course_short || '').trim() : dirty}
        saving={saving} error={err} disabled={priceBad}
        title={isNew ? 'New event' : 'Unsaved changes'}
        label={isNew ? 'Create event' : 'Save changes'}
        note={priceBad ? 'walk-up has to price above the member rate' : null}
        onSave={save}
        onDiscard={isNew ? null : () => { setForm(pristine.current); setErr(''); }}/>

      <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 18 }}>
        <button className="btn btn-ghost" onClick={onClose} style={{ padding: '6px 12px' }}>← Events</button>
        <div style={{ fontFamily: 'var(--font-display)', fontSize: 21, color: 'var(--paper)' }}>
          {isNew ? 'New event' : form.course_short || 'Event'}
        </div>
      </div>

      <div className="card" style={{ padding: 22, marginBottom: 14 }}>
        <Row>
          <Field label="Course short name"><input className="input" value={form.course_short} onChange={e => set('course_short', e.target.value)} placeholder="Melreese"/></Field>
          <Field label="Course full name"><input className="input" value={form.course_name} onChange={e => set('course_name', e.target.value)} placeholder="International Links Melreese"/></Field>
        </Row>
        <Row>
          <Field label="Date & time"><input className="input" type="datetime-local" value={form.startsLocal} onChange={e => set('startsLocal', e.target.value)}/></Field>
          <Field label="Field size"><input className="input" type="number" min="1" value={form.field} onChange={e => set('field', e.target.value)}/></Field>
        </Row>
        <Row>
          <Field label="Type">
            <select className="select" value={form.type} onChange={e => set('type', e.target.value)}>
              {EVENT_TYPES.map(t => <option key={t} value={t}>{t}</option>)}
            </select>
          </Field>
          <Field label="Status">
            <select className="select" value={form.status} onChange={e => set('status', e.target.value)}>
              {EVENT_STATUSES.map(t => <option key={t} value={t}>{t}</option>)}
            </select>
          </Field>
        </Row>
        <Row>
          <Field label="Member price ($)"><input className="input" type="number" min="0" value={form.price_member} onChange={e => set('price_member', e.target.value)}/></Field>
          <Field label="Walk-up price ($)"><input className="input" type="number" min="0" value={form.price_walkup} onChange={e => set('price_walkup', e.target.value)}/></Field>
        </Row>

        {priceBad && (
          <div className="form-error" role="alert" style={{ marginBottom: 14 }}>
            Walk-up has to cost more than the member price — otherwise membership buys nothing.
          </div>
        )}
        {form.type === 'corporate' && (
          <div className="suggest-pill" style={{ marginBottom: 14 }}>
            Corporate events never appear in public listings. This one is bookable only by direct link.
          </div>
        )}

        <Row>
          <Field label="Tagline" full><input className="input" value={form.tagline} onChange={e => set('tagline', e.target.value)} placeholder="Weekly Match Night"/></Field>
        </Row>
        <Row>
          <Field label="Description" full><textarea className="input" style={{ minHeight: 64, resize: 'vertical' }} value={form.description} onChange={e => set('description', e.target.value)}/></Field>
        </Row>
        <Row>
          <Field label="Hero image URL" full><input className="input" value={form.img_url} onChange={e => set('img_url', e.target.value)} placeholder="https://…"/></Field>
        </Row>
      </div>

      <div style={{ display: 'flex', gap: 10, alignItems: 'center' }}>
        <button className="btn btn-ghost" onClick={onClose} disabled={saving}>Cancel</button>
        {!isNew && <button className="btn btn-danger" onClick={() => setConfirm(true)} disabled={saving} style={{ marginLeft: 'auto' }}>Delete event</button>}
      </div>

      {confirm && (
        <ConfirmDialog open title={`Delete "${form.course_short}"?`}
          body="Registrations against this event are not refunded or notified — cancelling it instead keeps the record and tells the field."
          confirmLabel="Delete" onConfirm={remove} onCancel={() => setConfirm(false)}/>
      )}
    </div>
  );
}

Object.assign(window, { BookingsFeed, TeeSheet, EventsBoard });
