// admin-runners.jsx — dedicated runner-management page (admin/runners.html),
// split out of the event-edit form (src/admin-app.jsx EventForm) because
// editing/cancelling individual registrations is a different job than
// editing event settings, and it was getting cramped sharing space with
// GPX/cutoff/quota/QR sections there. This page owns: search/filter, inline
// edit of name/phone/distance, cancel (delete) a registration, mark DNF,
// and CSV export.

const { useState: rS, useEffect: rE } = React;

const R_BRAND = '#2d6a4f', R_MONO = "'JetBrains Mono',ui-monospace,monospace";

// Same checkpoint-sequence helpers as mobile-app.jsx's cpSeqFor/cpLabelFor/
// cpKmFor — small duplication accepted rather than pulling this page into a
// shared module, same tradeoff as ADMIN_EMAILS above.
function cpSeqFor(event) {
  return ['start', ...((event && event.checkpoints) || []).map(c => c.id), 'finish'];
}
function cpLabelFor(event, cpId) {
  if (cpId === 'start') return 'จุดสตาร์ท';
  if (cpId === 'finish') return 'เส้นชัย';
  const cp = event && (event.checkpoints || []).find(c => c.id === cpId);
  return cp ? cp.label : cpId;
}
function cpKmFor(event, cpId, distLabel) {
  if (cpId === 'start') return 0;
  if (cpId === 'finish') return parseFloat(distLabel) || 0;
  const cp = event && (event.checkpoints || []).find(c => c.id === cpId);
  return cp ? (parseFloat(cp.km) || 0) : 0;
}

// Kept in sync with ADMIN_EMAILS in src/admin-app.jsx by hand — small
// duplication accepted here to keep this page a standalone entry point
// instead of depending on admin-app.jsx's internals.
const ADMIN_EMAILS = ['patinya.kaeothip@gmail.com'];

function RunnerManagerGate() {
  const [authState, setAuthState] = rS('checking');
  const [user, setUser] = rS(null);

  rE(() => {
    if (!window.fb) { setAuthState('allowed'); return; }
    return window.fb.onAuthChange(u => {
      if (!u) { setUser(null); setAuthState('signed-out'); return; }
      setUser(u);
      setAuthState(ADMIN_EMAILS.includes(u.email) ? 'allowed' : 'denied');
    });
  }, []);

  async function login() { try { await window.fb.signInWithGoogle(); } catch (_) {} }
  function logout() { window.fb.signOutUser(); }

  const cardStyle = { maxWidth: 380, margin: '80px auto', padding: 28, background: '#fff', borderRadius: 14, textAlign: 'center',
    fontFamily: "'Plus Jakarta Sans','Noto Sans Thai',ui-sans-serif,system-ui,sans-serif" };

  if (authState === 'checking') return <div style={{ padding: 60, textAlign: 'center', fontFamily: R_MONO, color: '#5d6b59' }}>กำลังตรวจสอบสิทธิ์…</div>;
  if (authState === 'signed-out') {
    return (
      <div style={{ ...cardStyle, border: '1px solid #e5e0d3' }}>
        <div style={{ fontSize: 30, marginBottom: 8 }}>👥</div>
        <div style={{ fontSize: 17, fontWeight: 700, marginBottom: 4 }}>จัดการนักวิ่ง</div>
        <div style={{ fontSize: 12.5, color: '#5d6b59', marginBottom: 18 }}>เฉพาะบัญชีที่ได้รับสิทธิ์ RD เท่านั้น</div>
        <button onClick={login} style={{ padding: '11px 18px', background: '#fff', border: '1px solid #e5e0d3', borderRadius: 10, fontSize: 13, fontWeight: 700, cursor: 'pointer', boxShadow: '0 1px 3px rgba(31,42,28,0.08)' }}>G เข้าสู่ระบบด้วย Google</button>
      </div>
    );
  }
  if (authState === 'denied') {
    return (
      <div style={{ ...cardStyle, border: '1px solid #f0c9c4' }}>
        <div style={{ fontSize: 30, marginBottom: 8 }}>⛔</div>
        <div style={{ fontSize: 15, fontWeight: 700, marginBottom: 4, color: '#b91c1c' }}>ไม่มีสิทธิ์เข้าถึง</div>
        <div style={{ fontSize: 12.5, color: '#5d6b59', marginBottom: 18 }}>บัญชี {user && user.email} ไม่ได้อยู่ในรายชื่อ RD ที่ได้รับสิทธิ์</div>
        <button onClick={logout} style={{ padding: '10px 16px', background: 'transparent', border: '1px solid #bdb6a4', borderRadius: 10, fontSize: 12.5, fontWeight: 600, cursor: 'pointer' }}>ออกจากระบบ</button>
      </div>
    );
  }
  return <RunnerManagerApp adminEmail={user && user.email} onLogout={window.fb ? logout : null}/>;
}

function csvEscape(v) {
  const s = String(v == null ? '' : v);
  return /[",\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
}
function downloadCsv(filename, rows) {
  const csv = rows.map(row => row.map(csvEscape).join(',')).join('\r\n');
  const blob = new Blob(['﻿' + csv], { type: 'text/csv;charset=utf-8;' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url; a.download = filename; a.click();
  URL.revokeObjectURL(url);
}

// Newest-created first — same ordering (and same id-based fallback for
// events older than the `createdAt` field itself — see the comment above
// this function in admin-app.jsx) as Admin's own event list, so both the
// dropdown's order and its default selection below land on a genuinely
// recent event instead of whatever order Firestore/localStorage happened
// to return (which skewed toward an old, already-finished event by
// coincidence).
function sortEventsNewestFirst(list) {
  return list.slice().sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0) || (b.id > a.id ? 1 : b.id < a.id ? -1 : 0));
}
function RunnerManagerApp({ adminEmail, onLogout }) {
  const [events, setEvents] = rS(() => sortEventsNewestFirst(window.eventStore ? window.eventStore.loadEvents() : []));
  rE(() => {
    const refresh = () => setEvents(sortEventsNewestFirst(window.eventStore.loadEvents()));
    window.addEventListener('trt:events-updated', refresh);
    return () => window.removeEventListener('trt:events-updated', refresh);
  }, []);
  const [eventId, setEventId] = rS(() => new URLSearchParams(location.search).get('event') || (events[0] && events[0].id) || null);
  const selectedEvent = events.find(e => e.id === eventId) || null;

  // includeCancelled: true — Admin is the one place that needs the full
  // audit trail (who cancelled, when, self or admin), not just active
  // registrations. The showCancelled toggle below controls what's actually
  // visible in the table; everywhere else in the app only ever sees the
  // filtered "active" view via the default listRunners() call.
  const [runners, setRunners] = rS(() => (window.runnerStore && eventId ? window.runnerStore.listRunners(eventId, { includeCancelled: true }) : []));
  rE(() => {
    const refresh = () => setRunners(window.runnerStore && eventId ? window.runnerStore.listRunners(eventId, { includeCancelled: true }) : []);
    refresh();
    window.addEventListener('trt:runners-updated', refresh);
    return () => window.removeEventListener('trt:runners-updated', refresh);
  }, [eventId]);

  const [q, setQ] = rS('');
  const [distFilter, setDistFilter] = rS('all');
  const [showCancelled, setShowCancelled] = rS(false);
  const [toast, setToast] = rS(null);
  const [expandedId, setExpandedId] = rS(null);
  const [ckExpandedId, setCkExpandedId] = rS(null);
  const [ckAddCp, setCkAddCp] = rS('');
  const [ckAddTime, setCkAddTime] = rS('');
  function flash(msg) { setToast(msg); setTimeout(() => setToast(null), 1600); }

  function editRunner(r, patch) {
    window.runnerStore.updateRunnerProgress(r.id, patch);
  }
  // Manual checkin fix-up — a runner who missed a real scan (forgot to
  // scan start, auto-checkin's GPS geofence never caught them at a
  // checkpoint they physically passed) had no way to recover; auto mode
  // in particular has no fallback QR button at all, so nothing but this
  // could unstick them. progressKm is kept in step with the added/removed
  // checkpoint the same way the runner's own QR-scan flow does, so
  // Track/Route/Ranking read a consistent position afterward.
  function addCheckin(r) {
    if (!ckAddCp) return;
    const t = (ckAddTime || new Date().toTimeString().slice(0, 8)).trim();
    if (!/^\d{1,2}:\d{2}(:\d{2})?$/.test(t)) { flash('รูปแบบเวลาไม่ถูกต้อง (HH:MM หรือ HH:MM:SS)'); return; }
    const normalizedT = t.length === 5 ? `${t}:00` : t;
    const checkins = [...(r.checkins || []).filter(c => c.cp !== ckAddCp), { cp: ckAddCp, t: normalizedT }];
    const km = cpKmFor(selectedEvent, ckAddCp, r.distance);
    editRunner(r, { checkins, progressKm: Math.max(km, r.progressKm || 0) });
    setCkAddCp(''); setCkAddTime('');
    flash(`✓ เพิ่มเช็คอิน ${cpLabelFor(selectedEvent, ckAddCp)} ให้ #${r.bib} แล้ว`);
  }
  function removeCheckin(r, cp) {
    if (!window.confirm(`ลบเช็คอิน "${cpLabelFor(selectedEvent, cp)}" ของ #${r.bib}?`)) return;
    const checkins = (r.checkins || []).filter(c => c.cp !== cp);
    editRunner(r, { checkins });
    flash(`✓ ลบเช็คอิน ${cpLabelFor(selectedEvent, cp)} แล้ว`);
  }
  function cancelRunner(r) {
    if (!window.confirm(`ยกเลิกการลงทะเบียนของ "${r.nickname}" (บิบ #${r.bib})? บิบนี้จะไม่ถูกใช้ซ้ำ · ยังดูประวัติย้อนหลังได้`)) return;
    window.runnerStore.cancelRunner(r.id, 'admin');
    if (selectedEvent) window.eventStore.decrementRegistration(selectedEvent.id, r.distance);
    flash(`✓ ยกเลิก #${r.bib} แล้ว`);
  }
  // Genuine permanent purge — only offered on already-cancelled rows (mis-
  // registrations, test entries), unlike cancelRunner which keeps a record.
  function deleteRunnerForever(r) {
    if (!window.confirm(`ลบข้อมูลของ "${r.nickname}" (บิบ #${r.bib}) ถาวร? กู้คืนไม่ได้อีก`)) return;
    window.runnerStore.deleteRunner(r.id);
    flash(`✓ ลบ #${r.bib} ถาวรแล้ว`);
  }
  // dnfAt/dnfBy give this an audit trail — a runner's own DNF tap already
  // records when it happened (see mobile-app.jsx's dnf confirm handler),
  // but this admin toggle previously flipped the flag with no record of
  // when or by whom, so a wrongly-set DNF was impossible to trace back
  // afterward.
  function toggleDnf(r) {
    const next = !r.dnf;
    editRunner(r, { dnf: next, dnfAt: next ? Date.now() : null, dnfBy: next ? adminEmail : null });
  }
  function exportCsv() {
    const rows = [['bib', 'ชื่อ', 'เบอร์โทร', 'อีเมล', 'เพศ', 'ระยะ', 'เช็คอิน', 'DNF', 'ผู้ติดต่อฉุกเฉิน', 'เบอร์ฉุกเฉิน', 'ผู้ติดต่อฉุกเฉิน 2', 'เบอร์ฉุกเฉิน 2', 'กรุ๊ปเลือด', 'โรคประจำตัว']];
    filtered.forEach(r => rows.push([r.bib, r.nickname, r.phone, r.email || '', r.gender === 'm' ? 'ชาย' : r.gender === 'f' ? 'หญิง' : '', r.distance, (r.checkins || []).length, r.dnf ? 'DNF' : '', r.emgName || '', r.emgPhone || '', r.emgName2 || '', r.emgPhone2 || '', r.bloodType || '', r.medical || '']));
    downloadCsv(`runners-${selectedEvent ? selectedEvent.id : 'export'}.csv`, rows);
  }
  function renumberAll() {
    const activeCount = runners.filter(r => !r.cancelled).length;
    if (!selectedEvent || !window.confirm(`สร้างเลขบิบใหม่ทั้งหมด ${activeCount} คนของงาน "${selectedEvent.name}"? เลขเดิม (รวมที่อาจปริ้นท์/แจกไปแล้ว) จะเปลี่ยนหมด — เรียงลำดับตามวันที่สมัคร (ไม่กระทบคนที่ยกเลิกแล้ว)`)) return;
    window.runnerStore.renumberBibs(selectedEvent);
    flash('✓ สร้างเลขบิบใหม่ทั้งหมดแล้ว');
  }

  const query = q.trim().toLowerCase();
  const activeRunners = runners.filter(r => !r.cancelled);
  const cancelledRunners = runners.filter(r => r.cancelled);
  const filtered = (showCancelled ? runners : activeRunners)
    .filter(r => distFilter === 'all' || r.distance === distFilter)
    .filter(r => !query || r.nickname.toLowerCase().includes(query) || r.bib.includes(query) || (r.phone || '').includes(query))
    .sort((a, b) => a.bib.localeCompare(b.bib, undefined, { numeric: true }));

  const inputStyle = { padding: '6px 8px', background: '#fff', border: '1px solid #e5e0d3', borderRadius: 6, fontSize: 12.5, fontFamily: 'inherit', width: '100%' };

  return (
    <div style={{ maxWidth: 900, margin: '0 auto', padding: '24px 20px 60px', fontFamily: "'Plus Jakarta Sans','Noto Sans Thai',ui-sans-serif,system-ui,sans-serif", color: '#1f2a1c' }}>
      {toast && <div style={{ position: 'fixed', top: 12, left: '50%', transform: 'translateX(-50%)', padding: '10px 16px', background: '#1f4d39', color: '#fff', borderRadius: 6, fontSize: 13, zIndex: 100, boxShadow: '0 6px 20px rgba(0,0,0,0.2)' }}>{toast}</div>}

      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 14, flexWrap: 'wrap', gap: 10 }}>
        <div style={{ fontFamily: R_MONO, fontSize: 11, letterSpacing: '0.1em', textTransform: 'uppercase', fontWeight: 600 }}>👥 จัดการนักวิ่ง</div>
        {onLogout && (
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, fontFamily: R_MONO, fontSize: 10.5, color: '#5d6b59' }}>
            <span>{adminEmail}</span>
            <button onClick={onLogout} style={{ padding: '5px 10px', background: 'transparent', border: '1px solid #d8d2c2', borderRadius: 6, fontFamily: R_MONO, fontSize: 10, fontWeight: 700, color: '#5d6b59', cursor: 'pointer' }}>ออกจากระบบ</button>
          </div>
        )}
      </div>

      <div style={{ marginBottom: 14 }}>
        <select value={eventId || ''} onChange={e => setEventId(e.target.value)} style={{ ...inputStyle, width: 'auto', minWidth: 240, padding: '10px 12px', fontFamily: R_MONO }}>
          {events.map(ev => <option key={ev.id} value={ev.id}>{ev.name} · {ev.date}</option>)}
        </select>
      </div>

      <div style={{ display: 'flex', gap: 8, marginBottom: 14, flexWrap: 'wrap', alignItems: 'center' }}>
        <input value={q} onChange={e => setQ(e.target.value)} placeholder="🔍 ค้นหาชื่อ / บิบ / เบอร์"
          style={{ ...inputStyle, flex: 1, minWidth: 200, padding: '9px 12px' }}/>
        <div style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
          {['all', ...(selectedEvent && selectedEvent.distances || []).map(d => d.label)].map(v => (
            <button key={v} onClick={() => setDistFilter(v)} style={{ padding: '7px 11px', borderRadius: 999, border: `1px solid ${distFilter === v ? R_BRAND : '#e5e0d3'}`,
              background: distFilter === v ? R_BRAND : '#fff', color: distFilter === v ? '#fff' : '#5d6b59', fontFamily: R_MONO, fontSize: 10.5, fontWeight: 700, cursor: 'pointer' }}>
              {v === 'all' ? 'ทั้งหมด' : v}
            </button>
          ))}
        </div>
        <button onClick={() => setShowCancelled(v => !v)} style={{ padding: '7px 11px', borderRadius: 999, border: `1px solid ${showCancelled ? '#5d6b59' : '#e5e0d3'}`,
          background: showCancelled ? '#5d6b59' : '#fff', color: showCancelled ? '#fff' : '#5d6b59', fontFamily: R_MONO, fontSize: 10.5, fontWeight: 700, cursor: 'pointer', whiteSpace: 'nowrap' }}>
          {showCancelled ? '✓ ' : ''}แสดงที่ยกเลิกแล้ว ({cancelledRunners.length})
        </button>
        <button onClick={exportCsv} disabled={!filtered.length} style={{ padding: '8px 12px', background: 'transparent', border: '1px solid #bdb6a4', borderRadius: 8, fontFamily: R_MONO, fontSize: 11, fontWeight: 700, cursor: filtered.length ? 'pointer' : 'not-allowed', opacity: filtered.length ? 1 : 0.5 }}>⬇ Export CSV</button>
        <button onClick={renumberAll} disabled={!runners.length} title="สร้างเลขบิบใหม่ทั้งหมดของงานนี้ตามระบบ 4 หลักปัจจุบัน" style={{ padding: '8px 12px', background: 'transparent', border: '1px solid #b45309', color: '#b45309', borderRadius: 8, fontFamily: R_MONO, fontSize: 11, fontWeight: 700, cursor: runners.length ? 'pointer' : 'not-allowed', opacity: runners.length ? 1 : 0.5 }}>🔄 รีเซ็ตเลขบิบทั้งหมด</button>
      </div>

      <div style={{ fontFamily: R_MONO, fontSize: 11, color: '#5d6b59', marginBottom: 8 }}>
        ลงทะเบียนอยู่ {activeRunners.length} คน{cancelledRunners.length ? ` · ยกเลิกแล้ว ${cancelledRunners.length}` : ''}{filtered.length !== (showCancelled ? runners.length : activeRunners.length) ? ` · ตรงตัวกรอง ${filtered.length}` : ''}
      </div>

      {!selectedEvent && <div style={{ padding: 30, textAlign: 'center', color: '#5d6b59', fontSize: 13 }}>ยังไม่มีงานแข่ง</div>}
      {selectedEvent && runners.length === 0 && <div style={{ padding: 30, textAlign: 'center', color: '#5d6b59', fontSize: 13, background: '#fafaf8', border: '1px solid #ece7da', borderRadius: 10 }}>ยังไม่มีใครลงทะเบียนงานนี้</div>}
      {filtered.length === 0 && runners.length > 0 && <div style={{ padding: 30, textAlign: 'center', color: '#5d6b59', fontSize: 13, background: '#fafaf8', border: '1px solid #ece7da', borderRadius: 10 }}>ไม่พบนักวิ่งที่ค้นหา</div>}

      {filtered.length > 0 && (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
          {filtered.map(r => r.cancelled ? (
            // Cancelled — read-only audit row instead of the full editable
            // grid, since there's nothing left to edit and it should read
            // clearly as "no longer registered" at a glance.
            <div key={r.id} style={{ display: 'grid', gridTemplateColumns: '54px 1.4fr 1fr 1fr auto', gap: 8, alignItems: 'center',
              padding: '8px 10px', background: '#f4f3ef', border: '1px solid #e5e0d3', borderRadius: 10, opacity: 0.75 }}>
              <span style={{ fontFamily: R_MONO, fontSize: 12, fontWeight: 700, textDecoration: 'line-through' }}>#{r.bib}</span>
              <span style={{ fontSize: 12.5 }}>{r.nickname} <span style={{ fontFamily: R_MONO, fontSize: 10.5, color: '#5d6b59' }}>· {r.phone}</span></span>
              <span style={{ fontFamily: R_MONO, fontSize: 11, color: '#5d6b59' }}>{r.distance}</span>
              <span style={{ fontFamily: R_MONO, fontSize: 10, color: '#5d6b59' }}>
                ยกเลิกโดย{r.cancelledBy === 'runner' ? 'นักวิ่งเอง' : 'RD'} · {r.cancelledAt ? new Date(r.cancelledAt).toLocaleString('th-TH', { dateStyle: 'short', timeStyle: 'short' }) : '—'}
              </span>
              <button onClick={() => deleteRunnerForever(r)} style={{ padding: '6px 9px', background: 'transparent', color: '#9b1c10', border: '1px solid #f0c9c4', borderRadius: 8, fontFamily: R_MONO, fontSize: 10, fontWeight: 700, cursor: 'pointer', whiteSpace: 'nowrap' }}>ลบถาวร</button>
            </div>
          ) : (
            <div key={r.id}>
            <div style={{ display: 'grid', gridTemplateColumns: '54px 1.4fr 1fr 90px 64px 80px auto auto auto', gap: 8, alignItems: 'center',
              padding: '8px 10px', background: r.dnf ? '#fef7f7' : '#fafaf8', border: `1px solid ${r.dnf ? '#f0c9c4' : '#ece7da'}`,
              borderRadius: (expandedId === r.id || ckExpandedId === r.id) ? '10px 10px 0 0' : 10 }}>
              <span style={{ fontFamily: R_MONO, fontSize: 12, fontWeight: 700 }}>#{r.bib}</span>
              <input value={r.nickname} onChange={e => editRunner(r, { nickname: e.target.value })} style={inputStyle}/>
              <input value={r.phone} onChange={e => editRunner(r, { phone: e.target.value })} style={{ ...inputStyle, fontFamily: R_MONO }}/>
              <select value={r.distance} onChange={e => editRunner(r, { distance: e.target.value })} style={{ ...inputStyle, fontFamily: R_MONO }}>
                {/* If this runner's stored distance doesn't match any of the
                    event's current distances (e.g. registered before Admin
                    renamed/removed that distance label), a plain <select>
                    would silently fall back to showing the first option as
                    "selected" without actually changing the stored value —
                    hiding the mismatch instead of surfacing it. Add the
                    stale value as its own flagged option so it's visible and
                    RD can deliberately correct it. */}
                {selectedEvent && !(selectedEvent.distances || []).some(d => d.label === r.distance) && (
                  <option value={r.distance}>⚠ {r.distance} (ไม่ตรงกับระยะปัจจุบันของงาน)</option>
                )}
                {(selectedEvent && selectedEvent.distances || []).map(d => <option key={d.id} value={d.label}>{d.label}</option>)}
              </select>
              <select value={r.gender || ''} onChange={e => editRunner(r, { gender: e.target.value })} style={{ ...inputStyle, fontFamily: R_MONO }}>
                <option value="">—</option>
                <option value="m">ชาย</option>
                <option value="f">หญิง</option>
              </select>
              <button onClick={() => { setCkExpandedId(ckExpandedId === r.id ? null : r.id); setCkAddCp(''); setCkAddTime(''); }} title="แก้ไขเช็คอิน" style={{ fontFamily: R_MONO, fontSize: 10.5, color: ckExpandedId === r.id ? '#fff' : '#5d6b59', textAlign: 'center', background: ckExpandedId === r.id ? R_BRAND : 'transparent', border: `1px solid ${ckExpandedId === r.id ? R_BRAND : '#d8d2c2'}`, borderRadius: 8, padding: '6px 4px', cursor: 'pointer' }}>{(r.checkins || []).length} เช็คอิน</button>
              <button onClick={() => setExpandedId(expandedId === r.id ? null : r.id)} title="ข้อมูลฉุกเฉิน / กรุ๊ปเลือด" style={{ padding: '6px 9px', background: expandedId === r.id ? '#dc2626' : 'transparent', color: expandedId === r.id ? '#fff' : '#dc2626', border: '1px solid #dc2626', borderRadius: 8, fontFamily: R_MONO, fontSize: 10, fontWeight: 700, cursor: 'pointer', whiteSpace: 'nowrap' }}>🆘 ฉุกเฉิน</button>
              <button onClick={() => toggleDnf(r)} style={{ padding: '6px 9px', background: r.dnf ? '#b91c1c' : 'transparent', color: r.dnf ? '#fff' : '#b91c1c', border: '1px solid #b91c1c', borderRadius: 8, fontFamily: R_MONO, fontSize: 10, fontWeight: 700, cursor: 'pointer', whiteSpace: 'nowrap' }}>{r.dnf ? 'ยกเลิก DNF' : 'Mark DNF'}</button>
              <button onClick={() => cancelRunner(r)} style={{ padding: '6px 9px', background: 'transparent', color: '#5d6b59', border: '1px solid #d8d2c2', borderRadius: 8, fontFamily: R_MONO, fontSize: 10, fontWeight: 700, cursor: 'pointer' }}>ยกเลิก</button>
            </div>
            {ckExpandedId === r.id && (
              <div style={{ padding: '12px 14px', background: '#f4f7f4', border: '1px solid #d9e5db', borderTop: 'none', borderRadius: expandedId === r.id ? 0 : '0 0 10px 10px', fontSize: 12 }}>
                {(r.checkins || []).length === 0 && <div style={{ color: '#5d6b59', fontFamily: R_MONO, fontSize: 11, marginBottom: 10 }}>ยังไม่มีเช็คอิน</div>}
                {(r.checkins || []).length > 0 && (
                  <div style={{ display: 'flex', flexDirection: 'column', gap: 4, marginBottom: 10 }}>
                    {r.checkins.map(c => (
                      <div key={c.cp} style={{ display: 'flex', alignItems: 'center', gap: 8, fontFamily: R_MONO, fontSize: 11 }}>
                        <span style={{ flex: 1 }}>{cpLabelFor(selectedEvent, c.cp)}</span>
                        <span style={{ color: '#5d6b59' }}>{c.t}</span>
                        <button onClick={() => removeCheckin(r, c.cp)} style={{ padding: '3px 7px', background: 'transparent', color: '#b91c1c', border: '1px solid #f0c9c4', borderRadius: 6, fontFamily: R_MONO, fontSize: 10, fontWeight: 700, cursor: 'pointer' }}>ลบ</button>
                      </div>
                    ))}
                  </div>
                )}
                <div style={{ display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' }}>
                  <select value={ckAddCp} onChange={e => setCkAddCp(e.target.value)} style={{ ...inputStyle, width: 'auto', fontFamily: R_MONO }}>
                    <option value="">— เลือกจุดเช็คอิน —</option>
                    {cpSeqFor(selectedEvent).map(cp => <option key={cp} value={cp}>{cpLabelFor(selectedEvent, cp)}</option>)}
                  </select>
                  <input value={ckAddTime} onChange={e => setCkAddTime(e.target.value)} placeholder={new Date().toTimeString().slice(0, 8)} style={{ ...inputStyle, width: 100, fontFamily: R_MONO }}/>
                  <button onClick={() => addCheckin(r)} disabled={!ckAddCp} style={{ padding: '6px 12px', background: ckAddCp ? R_BRAND : '#e5e0d3', color: '#fff', border: 'none', borderRadius: 8, fontFamily: R_MONO, fontSize: 10.5, fontWeight: 700, cursor: ckAddCp ? 'pointer' : 'not-allowed' }}>+ เพิ่ม/แก้ไข</button>
                  <span style={{ fontFamily: R_MONO, fontSize: 9.5, color: '#5d6b59' }}>เว้นเวลาไว้ = ใช้เวลาปัจจุบัน · เลือกจุดที่เช็คอินไปแล้วเพื่อแก้เวลา</span>
                </div>
              </div>
            )}
            {expandedId === r.id && (
              <div style={{ display: 'flex', flexWrap: 'wrap', gap: 16, padding: '12px 14px', background: '#fef2f2', border: '1px solid #fecaca', borderTop: 'none', borderRadius: '0 0 10px 10px', fontSize: 12 }}>
                {/* Editable — a runner who registered before this info was
                    collected (or who just never filled it in) has nothing
                    here otherwise, and it never backfills on its own since
                    registration doesn't re-run. RD can fill it in by hand
                    instead of being stuck with permanently blank fields. */}
                <div style={{ width: 160 }}>
                  <div style={{ fontFamily: R_MONO, fontSize: 9.5, color: '#9b1c10', textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: 3 }}>ผู้ติดต่อฉุกเฉิน · ชื่อ</div>
                  <input value={r.emgName || ''} onChange={e => editRunner(r, { emgName: e.target.value })} placeholder="ชื่อ" style={inputStyle}/>
                </div>
                <div style={{ width: 140 }}>
                  <div style={{ fontFamily: R_MONO, fontSize: 9.5, color: '#9b1c10', textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: 3 }}>ผู้ติดต่อฉุกเฉิน · เบอร์</div>
                  <input value={r.emgPhone || ''} onChange={e => editRunner(r, { emgPhone: e.target.value })} placeholder="08X-XXX-XXXX" style={{ ...inputStyle, fontFamily: R_MONO }}/>
                  {r.emgPhone && <a href={`tel:${r.emgPhone.replace(/[^\d+]/g, '')}`} style={{ display: 'block', marginTop: 3, color: '#9b1c10', fontFamily: R_MONO, fontSize: 10.5, fontWeight: 700 }}>📞 โทรออก</a>}
                </div>
                <div style={{ width: 160 }}>
                  <div style={{ fontFamily: R_MONO, fontSize: 9.5, color: '#9b1c10', textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: 3 }}>ผู้ติดต่อฉุกเฉิน คนที่ 2 · ชื่อ</div>
                  <input value={r.emgName2 || ''} onChange={e => editRunner(r, { emgName2: e.target.value })} placeholder="ชื่อ (ถ้ามี)" style={inputStyle}/>
                </div>
                <div style={{ width: 140 }}>
                  <div style={{ fontFamily: R_MONO, fontSize: 9.5, color: '#9b1c10', textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: 3 }}>ผู้ติดต่อฉุกเฉิน คนที่ 2 · เบอร์</div>
                  <input value={r.emgPhone2 || ''} onChange={e => editRunner(r, { emgPhone2: e.target.value })} placeholder="08X-XXX-XXXX (ถ้ามี)" style={{ ...inputStyle, fontFamily: R_MONO }}/>
                  {r.emgPhone2 && <a href={`tel:${r.emgPhone2.replace(/[^\d+]/g, '')}`} style={{ display: 'block', marginTop: 3, color: '#9b1c10', fontFamily: R_MONO, fontSize: 10.5, fontWeight: 700 }}>📞 โทรออก</a>}
                </div>
                <div style={{ width: 90 }}>
                  <div style={{ fontFamily: R_MONO, fontSize: 9.5, color: '#9b1c10', textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: 3 }}>กรุ๊ปเลือด</div>
                  <select value={r.bloodType || ''} onChange={e => editRunner(r, { bloodType: e.target.value })} style={{ ...inputStyle, fontFamily: R_MONO }}>
                    <option value="">—</option>
                    {['A+', 'A-', 'B+', 'B-', 'AB+', 'AB-', 'O+', 'O-'].map(bt => <option key={bt} value={bt}>{bt}</option>)}
                  </select>
                </div>
                <div style={{ width: 180 }}>
                  <div style={{ fontFamily: R_MONO, fontSize: 9.5, color: '#9b1c10', textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: 3 }}>โรคประจำตัว</div>
                  <input value={r.medical || ''} onChange={e => editRunner(r, { medical: e.target.value })} placeholder="เช่น หอบหืด, แพ้ยา" style={inputStyle}/>
                </div>
                <div style={{ width: 200 }}>
                  <div style={{ fontFamily: R_MONO, fontSize: 9.5, color: '#9b1c10', textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: 3 }}>อีเมล</div>
                  <input value={r.email || ''} onChange={e => editRunner(r, { email: e.target.value })} placeholder="you@example.com" style={{ ...inputStyle, fontFamily: R_MONO }}/>
                </div>
                <div>
                  <div style={{ fontFamily: R_MONO, fontSize: 9.5, color: '#9b1c10', textTransform: 'uppercase', letterSpacing: '0.06em' }}>อีเมล</div>
                  <div style={{ marginTop: 2 }}>{r.email ? <a href={`mailto:${r.email}`} style={{ color: '#9b1c10', fontFamily: R_MONO }}>{r.email}</a> : 'ไม่ได้ระบุไว้'}</div>
                </div>
              </div>
            )}
            </div>
          ))}
        </div>
      )}

    </div>
  );
}

Object.assign(window, { RunnerManagerGate });
