// other-sections.jsx — Classes, Printing, Invoices, Settings

const { useState: useOS } = React;

// ─────────────────────────────────────────────────────────────
// CLASSES
// ─────────────────────────────────────────────────────────────

// A full 10-week term of lessons for a class. Week dates come from the shared
// classWeeks() calendar; HW/QZ figures are seeded from the class id so they're
// stable. Per-week attendance is computed live from SAMPLE_ATTENDANCE in the tab.
const LESSON_SUBTOPICS = ['Limits & Continuity', 'Derivatives (E)', 'Rates of Change (E)', 'Integration Basics',
  'Applications of Integration', 'Trigonometric Functions', 'Logs & Exponentials', 'Probability',
  'Statistical Analysis', 'Term Review & Exam'];
// The class's Curriculum catalog (by Course×Pace×Stream — matched on course code).
function classCatalog(cls) {
  const courseStr = (cls && cls.course) || 'YR11 ADVN';
  try {
    const cur = (typeof loadCurriculum === 'function') ? loadCurriculum() : null;
    if (cur && cur.curriculum) {
      const courseCode = /EXT2/.test(courseStr) ? 'EXT2' : /EXT1/.test(courseStr) ? 'EXT1' : /YR10/.test(courseStr) ? 'YR10' : /STD|YR09/.test(courseStr) ? 'STND' : 'ADVN';
      const k = Object.keys(cur.curriculum).find(x => x.indexOf(courseCode + '|') === 0);
      if (k) return cur.curriculum[k];
    }
  } catch (e) {}
  return null;
}
// A year's 4 terms with week-counts (from the editable Settings terms; falls back to TERM_SCHEDULE)
// and a running booklet offset, so curriculum lessons flow term → term across the whole year.
function yearTermPlan(year) {
  const all = (typeof loadTerms === 'function') ? loadTerms() : [];
  const d0 = iso => iso ? new Date(iso + 'T00:00:00') : null;
  const plan = []; let offset = 0;
  for (let n = 1; n <= 4; n++) {
    const name = `Term ${n}, ${year}`;
    const t = all.find(x => x.name === name);
    let weeks = 10;
    if (t && t.start && t.end) { const a = d0(t.start), b = d0(t.end); if (b >= a) weeks = Math.ceil((Math.round((b - a) / 86400000) + 1) / 7); }
    else { const s = TERM_SCHEDULE.find(x => x.name === name); if (s) weeks = s.weeks; }
    plan.push({ name, start: (t && t.start) || null, weeks, offset });
    offset += weeks;
  }
  return plan;
}
// Monday-aligned weekly dates for a term (used for non-current terms; the enrolled term keeps classWeeks()).
function termWeekDates(termName, weeks, startISO) {
  const today = new Date(); today.setHours(0, 0, 0, 0);
  const sched = TERM_SCHEDULE.find(x => x.name === termName);
  const start = mondayOf(new Date(((startISO || (sched && sched.start)) || '2026-04-28') + 'T00:00:00'));
  return Array.from({ length: weeks }, (_, i) => {
    const d = new Date(start); d.setDate(d.getDate() + i * 7); const end = new Date(d); end.setDate(end.getDate() + 6);
    return { week: i + 1, date: d, end, label: d.toLocaleDateString('en-AU', { day: 'numeric', month: 'short', year: 'numeric' }),
      past: d <= today, covers: ts => { if (!ts) return false; const x = new Date(ts + 'T00:00:00'); return x >= d && x <= end; } };
  });
}
// Lessons for a class's selected term: catalog booklets sliced by the term's offset, laid onto that
// term's weeks. The enrolled (current) term keeps the live attendance weeks; others are plan-only.
function buildTermLessons(cls, selTermName) {
  const ym = String((cls && cls.term) || 'Term 2, 2026').match(/\d{4}/);
  const classYear = parseInt((ym && ym[0]) || '2026', 10);
  const plan = yearTermPlan(classYear);
  const enrolledTerm = (cls && cls.term) || `Term 2, ${classYear}`;
  const termName = selTermName || enrolledTerm;
  const selTerm = plan.find(p => p.name === termName) || plan.find(p => p.name === enrolledTerm) || plan[1] || plan[0];
  const isCurrentTerm = selTerm.name === enrolledTerm;
  const cat = classCatalog(cls);
  const sMap = cat && typeof seriesMap === 'function' ? seriesMap(cat.booklets) : {};
  const weeks = isCurrentTerm ? classWeeks() : termWeekDates(selTerm.name, selTerm.weeks, selTerm.start);
  const offset = selTerm.offset;
  let seed = (parseInt(String((cls && cls.id) || 'c1').replace(/\D/g, ''), 10) || 1) * 7919 + 11 + offset * 13;
  const rnd = () => { seed = (seed * 48271) % 2147483647; return seed / 2147483647; };
  const lessons = weeks.map((w, i) => {
    const b = cat && cat.booklets[offset + i];
    const s = b ? sMap[b.id] : null;
    return {
      week: w.week, date: w.label, past: w.past,
      topic: b ? b.title : '—',
      code: b ? `${cat.course}-${cat.pace}-${cat.stream}-${offset + i + 1}` : '—',
      subtopic: b ? b.subtitle : (LESSON_SUBTOPICS[i] || '—'),
      series: s ? s.letter : '—',
      count: s ? `${s.pos}/${s.total}` : '—',
      hw: (isCurrentTerm && w.past) ? `${70 + Math.floor(rnd() * 26)}%` : '—',
      qz: (isCurrentTerm && w.past) ? `${68 + Math.floor(rnd() * 29)}%` : '—',
    };
  });
  return { lessons, weeks, isCurrentTerm, plan, termName: selTerm.name };
}

// Stable, human-readable class code (e.g. C-001) derived from the row id — easy
// to quote over the phone and never changes for a given class.
function classCode(c) {
  const num = parseInt(String(c.id).replace(/\D/g, ''), 10) || 0;
  return `C-${String(num).padStart(3, '0')}`;
}

// Every term class caps at 12 students. The fill badge reflects how close a
// class is to that cap: available (green) → almost full (amber) → full (red).
const CLASS_CAP = 12;
function classFill(count, cap = CLASS_CAP) {
  const n = count || 0;
  if (n >= cap)     return { label: 'Full',        color: '#B44040', bg: 'rgba(180,60,60,0.12)',  border: 'rgba(180,60,60,0.30)' };
  if (n >= cap - 2) return { label: 'Almost full', color: '#B8922A', bg: 'rgba(184,146,42,0.14)', border: 'rgba(184,146,42,0.35)' };
  return            { label: 'Available',  color: '#4A9B7E', bg: 'rgba(74,155,126,0.12)', border: 'rgba(74,155,126,0.30)' };
}

// Class start/end time choices — 30-minute increments across teaching hours
// (6:00am–10:00pm), am/pm formatted to match the stored values ("4:30pm").
// Used with a searchable dropdown so typing a number narrows the list fast.
const TIME_OPTIONS = (() => {
  const out = [];
  for (let mins = 6 * 60; mins <= 22 * 60; mins += 30) {
    const h = Math.floor(mins / 60), m = mins % 60;
    const ampm = h < 12 ? 'am' : 'pm';
    const h12 = h % 12 === 0 ? 12 : h % 12;
    out.push(`${h12}:${m === 0 ? '00' : '30'}${ampm}`);
  }
  return out;
})();

// Delivery dot colour for the inline table dropdown.
function deliveryColor(d) { return d === 'online' ? '#4A8BB2' : '#4A9B7E'; }

function ClassesSection({ showData }) {
  const [catTab,     setCatTab]     = useOS('term');     // 'term' | 'holiday'
  const [version,    setVersion]    = useOS(0);          // bump to re-render after catalogue edits
  const [showArch,   setShowArch]   = useOS(false);
  const [selClass,   setSelClass]   = useOS(null);
  const [drawerOpen, setDrawerOpen] = useOS(false);
  const [classTab,   setClassTab]   = useOS('Students');
  const [attWeek,    setAttWeek]    = useOS(null);       // expanded week in the Lessons/attendance tab
  const [lessonTerm, setLessonTerm] = useOS(null);       // selected term in the Lessons tab (null = class's enrolled term)
  const [editor,     setEditor]     = useOS(null);       // { kind, isNew, id, data }
  // Term-class filters (course / day / campus / tutor / delivery) — 'all' = no filter
  const [fCourse,    setFCourse]    = useOS('all');
  const [fDay,       setFDay]       = useOS('all');
  const [fCampus,    setFCampus]    = useOS('all');
  const [fTutor,     setFTutor]     = useOS('all');
  const [fDelivery,  setFDelivery]  = useOS('all');
  const [moveStu,    setMoveStu]    = useOS(null);          // student being moved to another class
  const [selIds,     setSelIds]     = useOS(() => new Set()); // selected class rows (bulk actions)
  const force = () => setVersion(v => v + 1);

  const tutors  = SAMPLE_STAFF.filter(s => /tutor/i.test(s.role));
  const COURSES = ['YR09', 'YR10', 'YR11 STD', 'YR11 ADVN', 'YR11 EXT1', 'YR12 STD', 'YR12 ADVN', 'YR12 EXT1', 'YR12 EXT2'];
  const DAYS    = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];

  // Resizable column widths — drag the dividers in the header row to adjust.
  const CLASS_COLS = [
    { key: 'sel', w: 38 }, { key: 'ID', w: 58 }, { key: 'Course', w: 132 }, { key: 'Day & time', w: 158 }, { key: 'Room', w: 110 },
    { key: 'Campus', w: 132 }, { key: 'Delivery', w: 124 }, { key: 'Tutor', w: 184 }, { key: 'Students', w: 168 },
  ];
  const [colW, setColW] = useOS(() => Object.fromEntries(CLASS_COLS.map(c => [c.key, c.w])));
  const gridTemplate = CLASS_COLS.map(c => `${colW[c.key]}px`).join(' ');
  const startResize = (key, e) => {
    e.preventDefault(); e.stopPropagation();
    const startX = e.clientX, startW = colW[key];
    const onMove = ev => setColW(prev => ({ ...prev, [key]: Math.max(48, startW + (ev.clientX - startX)) }));
    const onUp = () => { document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); document.body.style.cursor = ''; document.body.style.userSelect = ''; };
    document.addEventListener('mousemove', onMove); document.addEventListener('mouseup', onUp);
    document.body.style.cursor = 'col-resize'; document.body.style.userSelect = 'none';
  };

  const setD    = (k, v) => setEditor(e => ({ ...e, error: null, data: { ...e.data, [k]: v } }));
  const nextId  = (arr, p) => `${p}${Math.max(0, ...arr.map(o => parseInt(String(o.id).replace(/\D/g, ''), 10) || 0)) + 1}`;

  // ── Roster mutations (mutate the shared SAMPLE_STUDENTS so the customer profile stays in sync) ──
  const enrolledOf = cid => SAMPLE_STUDENTS.filter(s => s.status === 'enrolled' && s.classId === cid);
  const trialsOf   = cid => SAMPLE_STUDENTS.filter(s => s.status === 'trial'    && s.classId === cid);
  const recount    = (...ids) => ids.filter(Boolean).forEach(id => { const c = SAMPLE_CLASSES.find(x => x.id === id); if (c) c.studentCount = enrolledOf(id).length; });
  const assignStudent = (sid, cid, { enrol = true } = {}) => {
    const s = SAMPLE_STUDENTS.find(x => x.id === sid); if (!s) return;
    const c = SAMPLE_CLASSES.find(x => x.id === cid);
    const prev = s.classId; s.classId = cid;
    if (c) { s.course = c.course; s.campus = c.campus; } // keep the customer record in lockstep with the class
    if (enrol) s.status = 'enrolled';
    recount(prev, cid); force();
  };
  // Edit a class field directly (table cells + class-card header). Course/campus
  // changes flow to that class's enrolled + trial students so records stay in lockstep.
  const updateClass = (c, key, val) => {
    if (!c) return;
    if (val === '' && key !== 'room') return; // required fields can't be blanked (room may be)
    c[key] = val;
    if (key === 'course' || key === 'campus') SAMPLE_STUDENTS.forEach(s => { if (s.classId === c.id && (s.status === 'enrolled' || s.status === 'trial')) s[key] = val; });
    force();
  };
  const setTutor = (cid, tid) => updateClass(SAMPLE_CLASSES.find(x => x.id === cid), 'tutorId', tid);
  // Mark/correct a week's attendance. Clears any prior follow-up so the absence
  // task re-evaluates from the new status (absent ⇒ task appears, present ⇒ gone).
  const setAtt = (cid, wk, sid, status) => {
    SAMPLE_ATTENDANCE[cid] = SAMPLE_ATTENDANCE[cid] || {};
    SAMPLE_ATTENDANCE[cid][wk] = SAMPLE_ATTENDANCE[cid][wk] || {};
    SAMPLE_ATTENDANCE[cid][wk][sid] = status;
    ABSENCE_RESOLVED.delete(`${cid}:${wk}:${sid}`);
    force();
  };

  // ── Row selection + bulk actions (mirrors the Customers table) ──
  const toggleSel    = id => setSelIds(prev => { const n = new Set(prev); n.has(id) ? n.delete(id) : n.add(id); return n; });
  const toggleSelAll = (ids, on) => setSelIds(prev => { const n = new Set(prev); ids.forEach(id => on ? n.add(id) : n.delete(id)); return n; });
  const selectedClasses = () => SAMPLE_CLASSES.filter(c => selIds.has(c.id));
  const bulkArchive = restore => { selectedClasses().forEach(c => { c.archived = !restore; }); setSelIds(new Set()); force(); };
  const exportClassesCSV = () => {
    const head = ['Code', 'Course', 'Day', 'Start', 'End', 'Room', 'Campus', 'Delivery', 'Tutor', 'Enrolled', 'Capacity', 'Term'];
    const esc  = v => `"${String(v == null ? '' : v).replace(/"/g, '""')}"`;
    const lines = [head.join(',')].concat(selectedClasses().map(c => {
      const t = SAMPLE_STAFF.find(s => s.id === c.tutorId);
      return [classCode(c), c.course, c.day, c.startTime, c.endTime, c.room || '', c.campus,
        c.delivery === 'online' ? 'Online' : 'In-person', t ? `${t.firstName} ${t.lastName}` : '',
        c.studentCount, c.capacity || CLASS_CAP, c.term].map(esc).join(',');
    }));
    const blob = new Blob([lines.join('\n')], { type: 'text/csv' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a'); a.href = url; a.download = `classes-${new Date().toISOString().slice(0, 10)}.csv`; a.click();
    URL.revokeObjectURL(url);
  };

  const newTerm     = () => setEditor({ kind: 'term', isNew: true, data: { course: 'YR11 ADVN', day: 'Wednesday', startTime: '4:30pm', endTime: '6:30pm', room: 'Room 1', campus: 'Parramatta', tutorId: (tutors[0] || {}).id, delivery: 'in-person', term: 'Term 2, 2026' } });
  const newHoliday  = () => setEditor({ kind: 'holiday', isNew: true, data: { name: '', shortName: '', period: '', campus: 'Parramatta', delivery: 'in-person', price: '' } });
  const editHoliday = p => setEditor({ kind: 'holiday', isNew: false, id: p.id, data: { name: p.name, shortName: p.shortName || '', period: p.period || '', campus: p.campus, delivery: p.delivery || 'in-person', price: p.price != null ? p.price : '' } });

  const saveEditor = () => {
    const { kind, isNew, id, data } = editor;
    if (kind === 'term') {
      // E11 — block save until all key fields are set; warn on an unusual duration.
      const required = [['course', data.course], ['day', data.day], ['start time', data.startTime], ['end time', data.endTime], ['campus', data.campus], ['tutor', data.tutorId]];
      const missing = required.filter(([, val]) => !val || val === '—').map(([k]) => k);
      if (missing.length) { setEditor(e => ({ ...e, error: `Please fill in: ${missing.join(', ')}.` })); return; }
      const hrs = lessonHours({ startTime: data.startTime, endTime: data.endTime });
      if (hrs <= 0) { setEditor(e => ({ ...e, error: 'End time must be after start time.' })); return; }
      const exp = expectedCourseHours(data.course);
      if (exp != null && Math.abs(hrs - exp) > 0.01 && !window.confirm(`${data.course} classes are usually ${exp}h, but this one is ${hrs}h. Save anyway?`)) return;
      if (isNew) SAMPLE_CLASSES.push({ id: nextId(SAMPLE_CLASSES, 'c'), type: 'term', studentCount: 0, capacity: CLASS_CAP, ...data });
      else { const c = SAMPLE_CLASSES.find(x => x.id === id); if (c) Object.assign(c, data); }
    } else {
      if (!data.name || !data.name.trim()) { setEditor(e => ({ ...e, error: 'Program name is required.' })); return; }
      const payload = { ...data, shortName: data.shortName || data.name, price: parseFloat(data.price) || 0 };
      if (isNew) SAMPLE_HOLIDAY_PROGRAMS.push({ id: nextId(SAMPLE_HOLIDAY_PROGRAMS, 'hp'), type: 'holiday', ...payload });
      else { const p = SAMPLE_HOLIDAY_PROGRAMS.find(x => x.id === id); if (p) Object.assign(p, payload); }
    }
    setEditor(null); force();
  };
  const toggleArchive = (kind, id) => {
    const arr = kind === 'term' ? SAMPLE_CLASSES : SAMPLE_HOLIDAY_PROGRAMS;
    const o = arr.find(x => x.id === id); if (o) o.archived = !o.archived; force();
  };

  const hcell    = { fontSize: '11px', fontWeight: 500, color: 'var(--text-secondary)', letterSpacing: '0.05em', textTransform: 'uppercase' };
  const muted    = { fontSize: '12px', color: 'var(--text-secondary)' };
  const archBadge = { fontSize: '9px', fontWeight: 600, color: 'var(--text-muted)', background: 'var(--bg-surface-muted)', border: '1px solid var(--border-default)', borderRadius: '4px', padding: '1px 6px', textTransform: 'uppercase', letterSpacing: '0.04em', flexShrink: 0 };
  const actBtn   = { background: 'none', border: '1px solid var(--border-default)', borderRadius: '5px', padding: '4px 9px', fontSize: '11px', color: 'var(--text-secondary)', cursor: 'pointer', fontFamily: 'inherit' };
  const inp      = { background: 'var(--bg-surface-muted)', border: '1px solid var(--border-default)', borderRadius: '4px', padding: '8px 10px', fontSize: '13px', color: 'var(--text-primary)', fontFamily: 'inherit', outline: 'none', width: '100%' };
  const labeled  = (label, node) => (<div><label style={{ fontSize: '12px', color: 'var(--text-muted)', display: 'block', marginBottom: '4px' }}>{label}</label>{node}</div>);
  const delivery = d => (
    <span style={{ display: 'inline-flex', alignItems: 'center', gap: '4px', fontSize: '10px', fontWeight: 600, padding: '2px 7px', borderRadius: '4px', whiteSpace: 'nowrap',
      background: d === 'online' ? 'rgba(74,139,178,0.13)' : 'rgba(74,155,126,0.13)', color: d === 'online' ? '#4A8BB2' : '#4A9B7E',
      border: `1px solid ${d === 'online' ? 'rgba(74,139,178,0.3)' : 'rgba(74,155,126,0.3)'}` }}>{d === 'online' ? 'Online' : 'In-person'}</span>
  );

  const filtersActive = fCourse !== 'all' || fDay !== 'all' || fCampus !== 'all' || fTutor !== 'all' || fDelivery !== 'all';
  const clearFilters  = () => { setFCourse('all'); setFDay('all'); setFCampus('all'); setFTutor('all'); setFDelivery('all'); };
  const termList    = SAMPLE_CLASSES.filter(c => (showArch || !c.archived)
    && (fCourse   === 'all' || c.course === fCourse)
    && (fDay      === 'all' || c.day === fDay)
    && (fCampus   === 'all' || c.campus === fCampus)
    && (fTutor    === 'all' || c.tutorId === fTutor)
    && (fDelivery === 'all' || (c.delivery || 'in-person') === fDelivery));
  const holidayList = SAMPLE_HOLIDAY_PROGRAMS.filter(p => showArch || !p.archived);
  // Term-table columns are resizable: gridTemplate / CLASS_COLS are defined above.
  const TCOL_GAP = '16px';
  const HCOLS = '2.2fr 1fr 110px 100px 132px';

  // Tab counts (active = non-archived) + archived counts for the archived toggle.
  const termCount = SAMPLE_CLASSES.filter(c => !c.archived).length;
  const holCount  = SAMPLE_HOLIDAY_PROGRAMS.filter(p => !p.archived).length;
  const archTerm  = SAMPLE_CLASSES.filter(c => c.archived).length;
  const archHol   = SAMPLE_HOLIDAY_PROGRAMS.filter(p => p.archived).length;
  // Subtle, contextual archived toggle — only appears when there's something archived
  // (or while it's on). Replaces the floating "Show archived" checkbox.
  const renderArchToggle = count => ((count > 0 || showArch) ? (
    <button onClick={() => setShowArch(s => !s)} title={showArch ? 'Hide archived' : 'Show archived'}
      style={{ marginLeft: 'auto', display: 'inline-flex', alignItems: 'center', gap: '6px',
        background: showArch ? 'rgba(31,77,61,0.15)' : 'none', border: `1px solid ${showArch ? 'rgba(31,77,61,0.4)' : 'transparent'}`,
        color: showArch ? '#4A9B7E' : 'var(--text-muted)', borderRadius: '6px', padding: '5px 10px', fontSize: '12px', cursor: 'pointer', fontFamily: 'inherit', transition: '120ms ease' }}>
      <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
        <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" /><circle cx="12" cy="12" r="3" />
      </svg>
      {showArch ? 'Hide archived' : `Show archived${count ? ` (${count})` : ''}`}
    </button>
  ) : null);

  return (
    <div>
      <SectionHeader title="Classes & Programs" />

      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '18px', flexWrap: 'wrap', gap: '12px' }}>
        <div style={{ display: 'flex', gap: '6px' }}>
          <Chip label="Term classes"     count={termCount} active={catTab === 'term'}    onClick={() => setCatTab('term')} />
          <Chip label="Holiday programs" count={holCount}  active={catTab === 'holiday'} onClick={() => setCatTab('holiday')} />
        </div>
        <Button variant="primary" onClick={() => (catTab === 'term' ? newTerm() : newHoliday())}>+ New {catTab === 'term' ? 'class' : 'holiday program'}</Button>
      </div>

      {/* Term-class filters */}
      {showData && catTab === 'term' && (
        <div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap', alignItems: 'center', marginBottom: '16px' }}>
          <div style={{ width: '150px' }}>
            <FormSelect value={fCourse} onChange={setFCourse} buttonStyle={{ padding: '6px 10px' }}
              options={[{ value: 'all', label: 'All courses' }, ...COURSES.map(c => ({ value: c, label: c }))]} />
          </div>
          <div style={{ width: '140px' }}>
            <FormSelect value={fDay} onChange={setFDay} buttonStyle={{ padding: '6px 10px' }}
              options={[{ value: 'all', label: 'All days' }, ...DAYS.map(d => ({ value: d, label: d }))]} />
          </div>
          <div style={{ width: '150px' }}>
            <FormSelect value={fCampus} onChange={setFCampus} buttonStyle={{ padding: '6px 10px' }}
              options={[{ value: 'all', label: 'All campuses' }, { value: 'Parramatta', label: 'Parramatta' }, { value: 'Bella Vista', label: 'Bella Vista' }]} />
          </div>
          <div style={{ width: '162px' }}>
            <FormSelect value={fTutor} onChange={setFTutor} buttonStyle={{ padding: '6px 10px' }}
              options={[{ value: 'all', label: 'All tutors' }, ...tutors.map(t => ({ value: t.id, label: `${t.firstName} ${t.lastName}` }))]} />
          </div>
          <div style={{ width: '150px' }}>
            <FormSelect value={fDelivery} onChange={setFDelivery} buttonStyle={{ padding: '6px 10px' }}
              options={[{ value: 'all', label: 'All delivery' }, { value: 'in-person', label: 'In-person' }, { value: 'online', label: 'Online' }]} />
          </div>
          {filtersActive && (
            <button onClick={clearFilters} style={{ background: 'none', border: 'none', color: 'var(--text-muted)', fontSize: '12px',
              cursor: 'pointer', fontFamily: 'inherit', textDecoration: 'underline', textUnderlineOffset: '2px', padding: '6px 4px' }}>Clear filters</button>
          )}
          {renderArchToggle(archTerm)}
        </div>
      )}

      {!showData ? (
        <EmptyState message="Enable sample data to see the catalogue" />
      ) : catTab === 'term' ? (
        <React.Fragment>
        {selIds.size > 0 && (() => {
          const allArch = [...selIds].every(id => { const c = SAMPLE_CLASSES.find(x => x.id === id); return c && c.archived; });
          return (
            <div style={{ display: 'flex', alignItems: 'center', gap: '12px', flexWrap: 'wrap', marginBottom: '12px',
              background: 'rgba(31,77,61,0.12)', border: '1px solid rgba(31,77,61,0.4)', borderRadius: '8px', padding: '8px 14px' }}>
              <span style={{ fontSize: '13px', fontWeight: 600, color: 'var(--text-primary)' }}>{selIds.size} selected</span>
              <Button size="sm" variant="primary" onClick={exportClassesCSV}>Export CSV</Button>
              <Button size="sm" variant="secondary" onClick={() => bulkArchive(allArch)}>{allArch ? 'Restore' : 'Archive'}</Button>
              <button onClick={() => setSelIds(new Set())} style={{ background: 'none', border: 'none', color: 'var(--text-muted)', fontSize: '12px', cursor: 'pointer', fontFamily: 'inherit', marginLeft: 'auto' }}>Clear selection</button>
            </div>
          );
        })()}
        {/* Outer bounding card + inner scroll surface — same chrome as the Customers table */}
        <div style={{ border: '1px solid var(--border-default)', borderRadius: '10px', background: 'var(--bg-surface-muted)', padding: '8px' }}>
          <div style={{ overflow: 'auto', border: '1px solid var(--border-soft)', borderRadius: '8px', background: 'var(--bg-surface)' }}>
            <div style={{ minWidth: 'max-content' }}>

              {/* Header (sticky while the list scrolls) — drag the dividers to resize columns */}
              <div style={{ position: 'sticky', top: 0, zIndex: 10, display: 'grid', gridTemplateColumns: gridTemplate, columnGap: TCOL_GAP,
                background: 'var(--bg-surface-muted)', padding: '9px 16px', borderBottom: '1px solid var(--border-soft)',
                fontSize: '11px', fontWeight: 500, color: 'var(--text-secondary)', letterSpacing: '0.05em', textTransform: 'uppercase' }}>
                {CLASS_COLS.map((col, i) => (
                  <span key={col.key} style={{ position: 'relative', display: 'flex', alignItems: 'center', minWidth: 0 }}>
                    {col.key === 'sel' ? (
                      <Checkbox checked={termList.length > 0 && termList.every(c => selIds.has(c.id))}
                        onChange={on => toggleSelAll(termList.map(c => c.id), on)} title="Select all" />
                    ) : (
                      <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{col.key}</span>
                    )}
                    {col.key !== 'sel' && i < CLASS_COLS.length - 1 && (
                      <span onMouseDown={e => startResize(col.key, e)} title="Drag to resize column"
                        style={{ position: 'absolute', top: '-9px', bottom: '-9px', right: '-12px', width: '9px', cursor: 'col-resize', display: 'flex', justifyContent: 'center', zIndex: 5 }}
                        onMouseEnter={e => { e.currentTarget.firstChild.style.background = 'var(--brand-hover)'; }}
                        onMouseLeave={e => { e.currentTarget.firstChild.style.background = 'var(--border-default)'; }}>
                        <span style={{ width: '2px', height: '100%', background: 'var(--border-default)', borderRadius: '1px', transition: '120ms ease' }} />
                      </span>
                    )}
                  </span>
                ))}
              </div>

              {termList.map((c, i) => {
                const cap   = c.capacity || CLASS_CAP;
                const fill  = classFill(c.studentCount, cap);
                const openDrawer = () => { setSelClass(c); setDrawerOpen(true); setClassTab('Students'); setLessonTerm(null); };
                return (
                  <div key={c.id}
                    style={{ display: 'grid', gridTemplateColumns: gridTemplate, columnGap: TCOL_GAP, padding: '9px 16px', alignItems: 'center', background: 'var(--bg-surface)',
                      borderBottom: i < termList.length - 1 ? '1px solid var(--border-soft)' : 'none', opacity: c.archived ? 0.55 : 1, transition: '80ms ease' }}
                    onMouseEnter={e => e.currentTarget.style.background = 'var(--bg-hover)'} onMouseLeave={e => e.currentTarget.style.background = 'var(--bg-surface)'}>
                    {/* Select */}
                    <span style={{ display: 'flex', alignItems: 'center' }}>
                      <Checkbox checked={selIds.has(c.id)} onChange={() => toggleSel(c.id)} title="Select class" />
                    </span>
                    {/* ID — opens the class card */}
                    <span onClick={openDrawer} title="Open class card"
                      style={{ fontSize: '12px', color: 'var(--text-muted)', fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace', fontVariantNumeric: 'tabular-nums',
                        cursor: 'pointer', textDecoration: 'underline', textDecorationColor: 'rgba(255,255,255,0.18)', textUnderlineOffset: '2px',
                        overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{classCode(c)}</span>
                    {/* Course — inline editable */}
                    <div style={{ display: 'flex', alignItems: 'center', gap: '6px', minWidth: 0 }}>
                      <div style={{ flex: 1, minWidth: 0 }}>
                        <InlineDropdownCell value={c.course} options={COURSES} colorFor={courseColor} onCommit={v => updateClass(c, 'course', v)} />
                      </div>
                      {c.archived && <span style={archBadge}>Archived</span>}
                    </div>
                    {/* Day & time — opens the class card to edit (time pickers live there) */}
                    <span onClick={openDrawer} title="Edit day & time in the class card"
                      style={{ ...muted, cursor: 'pointer', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{c.day} {c.startTime}–{c.endTime}</span>
                    {/* Room — inline editable */}
                    <InlineCell value={c.room} onCommit={v => updateClass(c, 'room', v)} />
                    {/* Campus — inline editable */}
                    <InlineDropdownCell value={c.campus} options={['Parramatta', 'Bella Vista']} onCommit={v => updateClass(c, 'campus', v)} />
                    {/* Delivery — inline editable */}
                    <InlineDropdownCell value={c.delivery || 'in-person'} colorFor={deliveryColor}
                      options={[{ value: 'in-person', label: 'In-person' }, { value: 'online', label: 'Online' }]} onCommit={v => updateClass(c, 'delivery', v)} />
                    {/* Tutor — inline editable */}
                    <InlineDropdownCell value={c.tutorId} onCommit={v => updateClass(c, 'tutorId', v)}
                      options={tutors.map(t => ({ value: t.id, label: `${t.firstName} ${t.lastName}` }))} />
                    {/* Students — count / capacity + colour-coded fill badge (opens card) */}
                    <div onClick={openDrawer} style={{ display: 'flex', alignItems: 'center', gap: '8px', minWidth: 0, cursor: 'pointer' }}>
                      <span style={{ fontSize: '13px', fontWeight: 500, color: 'var(--text-primary)', fontVariantNumeric: 'tabular-nums', whiteSpace: 'nowrap' }}>
                        {c.studentCount}<span style={{ color: 'var(--text-muted)', fontWeight: 400 }}> / {cap}</span>
                      </span>
                      <span style={{ display: 'inline-flex', alignItems: 'center', fontSize: '10px', fontWeight: 600, padding: '2px 7px', borderRadius: '4px',
                        whiteSpace: 'nowrap', background: fill.bg, color: fill.color, border: `1px solid ${fill.border}` }}>{fill.label}</span>
                    </div>
                  </div>
                );
              })}
              {termList.length === 0 && (
                <div style={{ padding: '40px', textAlign: 'center', fontSize: '13px', color: 'var(--text-muted)' }}>
                  {filtersActive ? 'No classes match your filters' : 'No term classes'}
                </div>
              )}
            </div>
          </div>
        </div>
        </React.Fragment>
      ) : (
        <React.Fragment>
        {(archHol > 0 || showArch) && (
          <div style={{ display: 'flex', marginBottom: '12px' }}>{renderArchToggle(archHol)}</div>
        )}
        <div style={{ borderRadius: '8px', border: '1px solid var(--border-soft)', overflow: 'hidden' }}>
          <div style={{ display: 'grid', gridTemplateColumns: HCOLS, background: 'var(--bg-surface-muted)', padding: '9px 16px', borderBottom: '1px solid var(--border-soft)' }}>
            {['Holiday program', 'Period', 'Campus', 'Delivery', ''].map((h, i) => <span key={i} style={hcell}>{h}</span>)}
          </div>
          {holidayList.map((p, i) => (
            <div key={p.id} style={{ display: 'grid', gridTemplateColumns: HCOLS, padding: '11px 16px', alignItems: 'center', background: 'var(--bg-surface)',
              borderBottom: i < holidayList.length - 1 ? '1px solid var(--border-soft)' : 'none', opacity: p.archived ? 0.55 : 1 }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: '8px', minWidth: 0 }}>
                <span style={{ width: 7, height: 7, borderRadius: '50%', background: '#B8922A', flexShrink: 0 }} />
                <span style={{ fontSize: '13px', fontWeight: 500, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{p.name}</span>
                {p.archived && <span style={archBadge}>Archived</span>}
              </div>
              <span style={muted}>{p.period || '—'}</span>
              <span style={muted}>{p.campus}</span>
              <span>{delivery(p.delivery)}</span>
              <div style={{ display: 'flex', gap: '6px' }}>
                <button style={actBtn} onClick={() => editHoliday(p)}>Edit</button>
                <button style={actBtn} onClick={() => toggleArchive('holiday', p.id)}>{p.archived ? 'Restore' : 'Archive'}</button>
              </div>
            </div>
          ))}
          {holidayList.length === 0 && <div style={{ padding: '40px', textAlign: 'center', fontSize: '13px', color: 'var(--text-muted)' }}>No holiday programs</div>}
        </div>
        </React.Fragment>
      )}

      {/* Term class detail drawer */}
      {selClass && (() => {
        const cap      = selClass.capacity || CLASS_CAP;
        const enrolled = enrolledOf(selClass.id);
        const trials   = trialsOf(selClass.id);
        const tutor    = SAMPLE_STAFF.find(s => s.id === selClass.tutorId);
        const fill     = classFill(enrolled.length, cap);
        const full     = enrolled.length >= cap;
        const lessonView = buildTermLessons(selClass, lessonTerm);
        const lessons  = lessonView.lessons;
        const weeks    = lessonView.weeks;
        const metaLabel = { fontSize: '10px', textTransform: 'uppercase', letterSpacing: '0.05em', color: 'var(--text-muted)' };
        const metaVal   = { fontSize: '13px', color: 'var(--text-primary)', fontWeight: 500 };
        const fieldBtn  = { padding: '5px 8px' };
        const fieldInp  = { background: 'var(--bg-surface-muted)', border: '1px solid var(--border-default)', borderRadius: '4px', padding: '5px 8px', fontSize: '13px', color: 'var(--text-primary)', fontFamily: 'inherit', outline: 'none', width: '100%', boxSizing: 'border-box' };
        const secHead   = { fontSize: '11px', fontWeight: 600, letterSpacing: '0.05em', textTransform: 'uppercase', color: 'var(--text-secondary)' };
        const fmtTrial  = d => { if (!d) return ''; try { return new Date(d + 'T00:00:00').toLocaleDateString('en-AU', { day: '2-digit', month: 'short' }); } catch (e) { return d; } };
        const rosterRow = (s, accent) => (
          <div key={s.id} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '10px',
            padding: '8px 10px 8px 12px', borderRadius: '8px', background: 'var(--bg-surface-muted)',
            borderLeft: accent ? `3px solid ${accent}` : '3px solid transparent' }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: '10px', minWidth: 0 }}>
              <Avatar name={`${s.firstName} ${s.lastName}`} size={28} />
              <div style={{ minWidth: 0 }}>
                <div style={{ fontSize: '13px', fontWeight: 500, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{s.firstName} {s.lastName}</div>
                <div style={{ fontSize: '11px', color: 'var(--text-muted)' }}>
                  {s.status === 'trial' ? `Trial${s.trialDate ? ` · ${fmtTrial(s.trialDate)}` : ''}` : s.course}
                </div>
              </div>
            </div>
            <div style={{ display: 'flex', gap: '6px', flexShrink: 0 }} onClick={e => e.stopPropagation()}>
              {s.status === 'trial' && <button style={actBtn} disabled={full} title={full ? 'Class is full' : 'Convert to enrolled'}
                onClick={() => assignStudent(s.id, selClass.id, { enrol: true })}>Enrol</button>}
              <button style={actBtn} onClick={() => setMoveStu(s)}>Move</button>
            </div>
          </div>
        );
        return (
        <Drawer open={drawerOpen} onClose={() => setDrawerOpen(false)} title={classLabel(selClass)}
          subtitle={`${classCode(selClass)} · ${selClass.campus} · ${selClass.delivery === 'online' ? 'Online' : 'In-person'}`} width={880}>

          {/* ── Metadata header ── */}
          <div style={{ background: 'var(--bg-surface-muted)', border: '1px solid var(--border-soft)', borderRadius: '10px', padding: '12px 14px', margin: '14px 0 14px' }}>
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '10px' }}>
              <span style={{ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace', fontSize: '12px', color: 'var(--text-muted)' }}>{classCode(selClass)}</span>
              <span style={{ display: 'inline-flex', alignItems: 'center', fontSize: '11px', fontWeight: 600, padding: '3px 9px', borderRadius: '5px',
                background: fill.bg, color: fill.color, border: `1px solid ${fill.border}`, fontVariantNumeric: 'tabular-nums' }}>
                {enrolled.length} / {cap} · {fill.label}
              </span>
            </div>
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '9px 14px' }}>
              {[
                ['Course',   <FormSelect value={selClass.course} onChange={v => updateClass(selClass, 'course', v)} options={COURSES} buttonStyle={fieldBtn} />],
                ['Day',      <FormSelect value={selClass.day} onChange={v => updateClass(selClass, 'day', v)} options={DAYS} buttonStyle={fieldBtn} />],
                ['Start',    <FormSelect searchable value={selClass.startTime} onChange={v => updateClass(selClass, 'startTime', v)} options={TIME_OPTIONS} buttonStyle={fieldBtn} />],
                ['End',      <FormSelect searchable value={selClass.endTime} onChange={v => updateClass(selClass, 'endTime', v)} options={TIME_OPTIONS} buttonStyle={fieldBtn} />],
                ['Campus',   <FormSelect value={selClass.campus} onChange={v => updateClass(selClass, 'campus', v)} options={['Parramatta', 'Bella Vista']} buttonStyle={fieldBtn} />],
                ['Room',     <input value={selClass.room || ''} onChange={e => updateClass(selClass, 'room', e.target.value)} placeholder="e.g. Room 3" style={fieldInp} />],
                ['Delivery', <FormSelect value={selClass.delivery || 'in-person'} onChange={v => updateClass(selClass, 'delivery', v)} options={[{ value: 'in-person', label: 'In-person' }, { value: 'online', label: 'Online' }]} buttonStyle={fieldBtn} />],
                ['Tutor',    <div style={{ display: 'flex', alignItems: 'center', gap: '6px', minWidth: 0 }}>{tutor && <Avatar name={`${tutor.firstName} ${tutor.lastName}`} size={22} />}<div style={{ flex: 1, minWidth: 0 }}><FormSelect value={selClass.tutorId} onChange={v => setTutor(selClass.id, v)} buttonStyle={fieldBtn} options={tutors.map(t => ({ value: t.id, label: `${t.firstName} ${t.lastName}` }))} /></div></div>],
              ].map(([l, node]) => (
                <div key={l} style={{ display: 'flex', flexDirection: 'column', gap: '3px', minWidth: 0 }}>
                  <span style={metaLabel}>{l}</span>
                  {node}
                </div>
              ))}
            </div>
          </div>

          <Tabs tabs={['Students', 'Lessons']} active={classTab} onChange={setClassTab} />

          {classTab === 'Students' && (
            <div>
              {/* Trial students — flagged on top */}
              {trials.length > 0 && (
                <div style={{ marginBottom: '20px' }}>
                  <div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '8px' }}>
                    <span style={{ ...secHead, color: '#B8922A' }}>⚑ Trial students</span>
                    <span style={{ fontSize: '11px', color: 'var(--text-muted)' }}>{trials.length} this term</span>
                  </div>
                  <div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
                    {trials.map(s => rosterRow(s, '#B8922A'))}
                  </div>
                </div>
              )}

              {/* Enrolled students */}
              <div style={{ marginBottom: '8px' }}>
                <span style={secHead}>Enrolled <span style={{ color: 'var(--text-muted)', fontWeight: 500 }}>{enrolled.length}/{cap}</span></span>
              </div>
              <div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
                {enrolled.map(s => rosterRow(s, null))}
                {enrolled.length === 0 && <EmptyState message="No students enrolled yet" subtle />}
              </div>
              {full && <div style={{ marginTop: '10px', fontSize: '11px', color: '#B44040' }}>Class is full ({cap}/{cap}).</div>}
            </div>
          )}

          {classTab === 'Lessons' && (
            <div style={{ overflowX: 'auto' }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'wrap', marginBottom: '10px' }}>
                <span style={{ fontSize: '10px', textTransform: 'uppercase', letterSpacing: '0.05em', color: 'var(--text-muted)' }}>Term</span>
                {lessonView.plan.map(p => { const on = p.name === lessonView.termName; return (
                  <button key={p.name} onClick={() => setLessonTerm(p.name)} style={{ border: '1px solid var(--border-default)', borderRadius: '20px', padding: '3px 11px', cursor: 'pointer', fontFamily: 'inherit', fontSize: '11.5px',
                    background: on ? '#1F4D3D' : 'var(--bg-surface)', color: on ? '#fff' : 'var(--text-secondary)', fontWeight: on ? 600 : 400, whiteSpace: 'nowrap' }}>{p.name.split(',')[0]} · {p.weeks}w</button>
                ); })}
              </div>
              <div style={{ fontSize: '11px', color: 'var(--text-muted)', marginBottom: '8px' }}>{lessonView.isCurrentTerm ? 'Click a held week to see who attended and mark present / absent.' : 'Full-year lesson plan for this term — attendance is tracked on the current term.'}</div>
              <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '12px' }}>
                <thead><tr style={{ background: 'var(--bg-surface-muted)' }}>
                  {['', 'Wk', 'Date', 'Topic', 'Subtopic', 'Series', 'Count', 'Attendance', 'HW avg', 'QZ avg'].map((h, i) => (
                    <th key={i} style={{ padding: '8px 10px', textAlign: 'left', color: 'var(--text-muted)', fontWeight: 500, fontSize: '11px', letterSpacing: '0.04em', textTransform: 'uppercase', borderBottom: '1px solid var(--border-soft)', whiteSpace: 'nowrap' }}>{h}</th>
                  ))}
                </tr></thead>
                <tbody>
                  {lessons.map(l => {
                    const wk         = weeks[l.week - 1];
                    const showAtt    = lessonView.isCurrentTerm && l.past;
                    const present    = showAtt ? enrolled.filter(s => attendanceOf(selClass.id, l.week, s.id) === 'present').length : 0;
                    const absent     = enrolled.length - present;
                    const attLabel   = showAtt ? `${present}/${enrolled.length}` : '—';
                    const weekTrials = showAtt ? trials.filter(s => wk && wk.covers && wk.covers(s.trialDate)) : [];
                    const open       = showAtt && attWeek === l.week;
                    return (
                      <React.Fragment key={l.week}>
                        <tr onClick={() => showAtt && setAttWeek(open ? null : l.week)}
                          style={{ borderBottom: open ? 'none' : '1px solid var(--border-soft)', opacity: l.past ? 1 : 0.5,
                            cursor: showAtt ? 'pointer' : 'default', background: open ? 'var(--bg-surface-muted)' : 'transparent' }}>
                          <td style={{ padding: '10px 8px', width: 22, color: 'var(--text-muted)' }}>
                            {showAtt && (
                              <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"
                                style={{ transform: open ? 'rotate(90deg)' : 'none', transition: '150ms ease' }}><path d="M9 18l6-6-6-6" /></svg>
                            )}
                          </td>
                          <td style={{ padding: '10px', color: 'var(--text-muted)', fontWeight: 500 }}>{l.week}</td>
                          <td style={{ padding: '10px', color: 'var(--text-secondary)', whiteSpace: 'nowrap' }}>{l.date}</td>
                          <td style={{ padding: '10px' }}>
                            <div style={{ color: 'var(--text-primary)', fontWeight: 500 }}>{l.topic}</div>
                            <div style={{ fontFamily: 'monospace', fontSize: '9px', color: 'var(--text-muted)', marginTop: '1px', letterSpacing: '0.02em' }}>{l.code}</div>
                          </td>
                          <td style={{ padding: '10px', color: 'var(--text-secondary)' }}>{l.subtopic}</td>
                          <td style={{ padding: '10px', textAlign: 'center', fontWeight: 700, color: l.series === '—' ? 'var(--text-muted)' : '#4A9B7E' }}>{l.series}</td>
                          <td style={{ padding: '10px', textAlign: 'center', color: 'var(--text-secondary)', fontVariantNumeric: 'tabular-nums' }}>{l.count}</td>
                          <td style={{ padding: '10px', whiteSpace: 'nowrap', fontVariantNumeric: 'tabular-nums',
                            color: !showAtt ? 'var(--text-muted)' : absent > 0 ? '#B8922A' : 'var(--text-secondary)', fontWeight: showAtt && absent > 0 ? 600 : 400 }}>{attLabel}</td>
                          <td style={{ padding: '10px', color: 'var(--text-secondary)' }}>{l.hw}</td>
                          <td style={{ padding: '10px', color: 'var(--text-secondary)' }}>{l.qz}</td>
                        </tr>
                        {open && (
                          <tr style={{ borderBottom: '1px solid var(--border-soft)' }}>
                            <td colSpan={10} style={{ padding: '0 12px 12px', background: 'var(--bg-surface-muted)' }}>
                              <div style={{ display: 'flex', flexDirection: 'column', gap: '5px', paddingTop: '4px' }}>
                                {weekTrials.map(s => (
                                  <div key={s.id} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '10px', padding: '6px 10px', borderRadius: '6px', background: 'var(--bg-surface)', borderLeft: '3px solid #B8922A' }}>
                                    <span style={{ display: 'flex', alignItems: 'center', gap: '9px', minWidth: 0 }}>
                                      <Avatar name={`${s.firstName} ${s.lastName}`} size={24} />
                                      <span style={{ fontSize: '12px', color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{s.firstName} {s.lastName}</span>
                                    </span>
                                    <span style={{ fontSize: '10px', fontWeight: 600, color: '#B8922A', background: 'rgba(184,146,42,0.14)', border: '1px solid rgba(184,146,42,0.35)', borderRadius: '4px', padding: '2px 7px', whiteSpace: 'nowrap', flexShrink: 0 }}>Trial visitor</span>
                                  </div>
                                ))}
                                {enrolled.map(s => {
                                  const st = attendanceOf(selClass.id, l.week, s.id);
                                  return (
                                    <div key={s.id} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '10px', padding: '5px 10px', borderRadius: '6px', background: 'var(--bg-surface)' }}>
                                      <span style={{ display: 'flex', alignItems: 'center', gap: '9px', minWidth: 0 }}>
                                        <Avatar name={`${s.firstName} ${s.lastName}`} size={24} />
                                        <span style={{ fontSize: '12px', color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{s.firstName} {s.lastName}</span>
                                      </span>
                                      <span style={{ display: 'inline-flex', gap: '4px', flexShrink: 0 }}>
                                        {[['present', 'Present', '#4A9B7E'], ['absent', 'Absent', '#B44040']].map(([val, lbl, col]) => {
                                          const on = st === val;
                                          return (
                                            <button key={val} onClick={() => setAtt(selClass.id, l.week, s.id, val)}
                                              style={{ fontSize: '11px', fontWeight: 600, padding: '3px 9px', borderRadius: '5px', cursor: 'pointer', fontFamily: 'inherit',
                                                background: on ? `${col}22` : 'transparent', color: on ? col : 'var(--text-muted)', border: `1px solid ${on ? `${col}66` : 'var(--border-default)'}` }}>{lbl}</button>
                                          );
                                        })}
                                      </span>
                                    </div>
                                  );
                                })}
                                {enrolled.length === 0 && <div style={{ fontSize: '12px', color: 'var(--text-muted)', padding: '8px 2px' }}>No enrolled students.</div>}
                              </div>
                            </td>
                          </tr>
                        )}
                      </React.Fragment>
                    );
                  })}
                </tbody>
              </table>
            </div>
          )}
        </Drawer>
        );
      })()}

      {/* Move-student picker */}
      {moveStu && (() => {
        const targets = SAMPLE_CLASSES.filter(c => !c.archived && c.id !== moveStu.classId);
        return (
          <Modal open={true} onClose={() => setMoveStu(null)} title={`Move ${moveStu.firstName} ${moveStu.lastName}`}>
            <div style={{ display: 'flex', flexDirection: 'column', gap: '4px', maxHeight: '380px', overflowY: 'auto' }}>
              {targets.map(c => {
                const cnt = enrolledOf(c.id).length, cCap = c.capacity || CLASS_CAP, cFull = cnt >= cCap;
                return (
                  <button key={c.id} disabled={cFull} onClick={() => { assignStudent(moveStu.id, c.id, { enrol: true }); setMoveStu(null); }}
                    style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '10px', padding: '9px 12px', borderRadius: '6px',
                      background: 'var(--bg-surface-muted)', border: '1px solid var(--border-soft)', cursor: cFull ? 'not-allowed' : 'pointer',
                      opacity: cFull ? 0.5 : 1, fontFamily: 'inherit', textAlign: 'left', width: '100%' }}>
                    <span style={{ display: 'flex', alignItems: 'center', gap: '8px', minWidth: 0 }}>
                      <span style={{ width: 7, height: 7, borderRadius: '50%', background: courseColor(c.course) || 'var(--text-muted)', flexShrink: 0 }} />
                      <span style={{ fontSize: '13px', color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{classCode(c)} · {classLabel(c)}</span>
                    </span>
                    <span style={{ fontSize: '12px', color: 'var(--text-muted)', flexShrink: 0, fontVariantNumeric: 'tabular-nums' }}>{cnt}/{cCap}</span>
                  </button>
                );
              })}
              {targets.length === 0 && <div style={{ padding: '20px', textAlign: 'center', fontSize: '13px', color: 'var(--text-muted)' }}>No other classes</div>}
            </div>
          </Modal>
        );
      })()}

      {/* Create / edit modal */}
      {editor && (
        <Modal open={true} onClose={() => setEditor(null)} title={`${editor.isNew ? 'New' : 'Edit'} ${editor.kind === 'term' ? 'class' : 'holiday program'}`}>
          <div style={{ display: 'flex', flexDirection: 'column', gap: '14px' }}>
            {editor.kind === 'term' ? (
              <React.Fragment>
                {labeled('Course', <FormSelect value={editor.data.course} onChange={v => setD('course', v)} options={COURSES} />)}
                <div style={{ display: 'grid', gridTemplateColumns: '1.2fr 1fr 1fr', gap: '12px' }}>
                  {labeled('Day', <FormSelect value={editor.data.day} onChange={v => setD('day', v)} options={DAYS} />)}
                  {labeled('Start', <FormSelect searchable value={editor.data.startTime} onChange={v => setD('startTime', v)} options={TIME_OPTIONS} />)}
                  {labeled('End', <FormSelect searchable value={editor.data.endTime} onChange={v => setD('endTime', v)} options={TIME_OPTIONS} />)}
                </div>
                <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '12px' }}>
                  {labeled('Campus', <FormSelect value={editor.data.campus} onChange={v => setD('campus', v)} options={['Parramatta', 'Bella Vista']} />)}
                  {labeled('Room', <input value={editor.data.room || ''} onChange={e => setD('room', e.target.value)} placeholder="e.g. Room 3" style={inp} />)}
                  {labeled('Delivery', <FormSelect value={editor.data.delivery} onChange={v => setD('delivery', v)} options={[{ value: 'in-person', label: 'In-person' }, { value: 'online', label: 'Online' }]} />)}
                </div>
                {labeled('Tutor', <FormSelect value={editor.data.tutorId} onChange={v => setD('tutorId', v)} options={tutors.map(t => ({ value: t.id, label: `${t.firstName} ${t.lastName}` }))} />)}
              </React.Fragment>
            ) : (
              <React.Fragment>
                {labeled('Program name', <input value={editor.data.name} onChange={e => setD('name', e.target.value)} placeholder="e.g. Term 3 Mock Exam Program" style={inp} />)}
                {labeled('Short name (used on chips & links)', <input value={editor.data.shortName} onChange={e => setD('shortName', e.target.value)} placeholder="e.g. Mock Exam T3" style={inp} />)}
                <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px' }}>
                  {labeled('Period', <input value={editor.data.period} onChange={e => setD('period', e.target.value)} placeholder="e.g. Jul 2026" style={inp} />)}
                  {labeled('Campus', <FormSelect value={editor.data.campus} onChange={v => setD('campus', v)} options={['Parramatta', 'Bella Vista', 'Both']} />)}
                </div>
                <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px' }}>
                  {labeled('Delivery', <FormSelect value={editor.data.delivery} onChange={v => setD('delivery', v)} options={[{ value: 'in-person', label: 'In-person' }, { value: 'online', label: 'Online' }]} />)}
                  {labeled('Program price ($, incl. GST)', <input type="number" min="0" value={editor.data.price} onChange={e => setD('price', e.target.value)} placeholder="e.g. 480" style={inp} />)}
                </div>
              </React.Fragment>
            )}
            {editor.error && <div style={{ background: 'rgba(192,86,86,0.12)', border: '1px solid rgba(192,86,86,0.4)', borderRadius: '6px', padding: '8px 12px', fontSize: '12px', color: '#C05656' }}>⛔ {editor.error}</div>}
            <div style={{ display: 'flex', gap: '8px', justifyContent: 'flex-end', paddingTop: '4px' }}>
              <Button variant="secondary" onClick={() => setEditor(null)}>Cancel</Button>
              <Button variant="primary" onClick={saveEditor}>{editor.isNew ? 'Create' : 'Save changes'}</Button>
            </div>
          </div>
        </Modal>
      )}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// PRINTING
// ─────────────────────────────────────────────────────────────

function PrintingSection({ showData }) {
  const [tab,         setTab]       = useOS('Class material');
  const [printOpen,   setPrintOpen] = useOS(false);
  const [selLesson,   setSelLesson] = useOS(null);
  const [ticked,      setTicked]    = useOS({});

  const getT = id => ticked[id] || { th: false, qz: false, hw: false };
  const setT = (id, k, v) => setTicked(prev => ({ ...prev, [id]: { ...getT(id), [k]: v } }));

  return (
    <div>
      <SectionHeader title="Printing" subtitle="Class material queue and once-off print jobs" />
      <Tabs tabs={['Class material', 'Once-off']} active={tab} onChange={setTab} />

      {tab === 'Class material' && (
        <div style={{ borderRadius: '8px', border: '1px solid var(--border-soft)', overflow: 'hidden' }}>
          <div style={{ display: 'grid', gridTemplateColumns: '2fr 130px 100px 70px 110px 120px',
            background: 'var(--bg-surface-muted)', padding: '9px 16px', borderBottom: '1px solid var(--border-soft)',
            fontSize: '11px', fontWeight: 500, color: 'var(--text-secondary)', letterSpacing: '0.05em', textTransform: 'uppercase' }}>
            {['Class', 'Lesson code', 'Date', 'Stud.', 'Printed', ''].map(h => <span key={h}>{h}</span>)}
          </div>

          {showData ? SAMPLE_LESSONS_PRINT.map((l, i) => {
            const t = getT(l.id);
            const d = new Date(l.date).toLocaleDateString('en-AU', { weekday: 'short', day: '2-digit', month: 'short' });
            return (
              <div key={l.id} style={{ display: 'grid', gridTemplateColumns: '2fr 130px 100px 70px 110px 120px',
                padding: '12px 16px', alignItems: 'center', background: 'var(--bg-surface)',
                borderBottom: i < SAMPLE_LESSONS_PRINT.length - 1 ? '1px solid var(--border-soft)' : 'none' }}>
                <div>
                  <div style={{ fontSize: '13px', fontWeight: 500, color: 'var(--text-primary)' }}>{l.className}</div>
                  <div style={{ fontSize: '11px', color: 'var(--text-muted)' }}>{l.campus} · {l.tutor}</div>
                </div>
                <span style={{ fontSize: '11px', color: 'var(--text-muted)', fontFamily: 'monospace' }}>{l.lessonCode}</span>
                <span style={{ fontSize: '12px', color: 'var(--text-secondary)' }}>{d}</span>
                <span style={{ fontSize: '12px', color: 'var(--text-secondary)' }}>{l.studentCount}</span>
                <div style={{ display: 'flex', gap: '8px', fontSize: '12px', fontWeight: 500 }}>
                  {[['th','TH',[l.printedTH,t.th]],['qz','QZ',[l.printedQZ,t.qz]],['hw','HW',[l.printedHW,t.hw]]].map(([k,label,[base,over]]) => {
                    const done = base || over;
                    return <span key={k} style={{ color: done ? '#4A9B7E' : 'var(--text-muted)' }}>{done ? '✓' : '○'} {label}</span>;
                  })}
                </div>
                <Button size="sm" variant="secondary" onClick={() => { setSelLesson(l); setPrintOpen(true); }}>Print now</Button>
              </div>
            );
          }) : (
            <div style={{ padding: '48px', textAlign: 'center', fontSize: '13px', color: 'var(--text-muted)' }}>Enable sample data to see print queue</div>
          )}
        </div>
      )}

      {tab === 'Once-off' && (
        <div>
          <div style={{ border: '2px dashed var(--border-default)', borderRadius: '8px', padding: '52px', textAlign: 'center',
            color: 'var(--text-muted)', cursor: 'pointer', transition: '120ms ease' }}
            onMouseEnter={e => { e.currentTarget.style.borderColor = '#1F4D3D'; e.currentTarget.style.color = '#4A9B7E'; }}
            onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--border-default)'; e.currentTarget.style.color = 'var(--text-muted)'; }}>
            <Icon name="printing" size={28} color="currentColor" style={{ margin: '0 auto 12px', display: 'block' }} />
            <div style={{ fontSize: '14px', fontWeight: 500, marginBottom: '4px' }}>Drop a PDF here, or browse Drive</div>
            <div style={{ fontSize: '12px' }}>Supports PDF up to 50 MB</div>
          </div>
        </div>
      )}

      {selLesson && (
        <Modal open={printOpen} onClose={() => setPrintOpen(false)} title="Print lesson material">
          <div style={{ display: 'flex', flexDirection: 'column', gap: '14px' }}>
            <div style={{ background: 'var(--bg-surface-muted)', borderRadius: '8px', padding: '12px' }}>
              <div style={{ fontSize: '13px', fontWeight: 600, color: 'var(--text-primary)' }}>{selLesson.className}</div>
              <div style={{ fontSize: '12px', color: 'var(--text-muted)', marginTop: '3px' }}>{selLesson.lessonCode} · {selLesson.subtopic}</div>
            </div>
            {[['TH — Theory','A4'],['QZ — Quiz','A4'],['HW — Homework','A4']].map(([f]) => (
              <div key={f} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between',
                padding: '10px 12px', background: 'var(--bg-surface-muted)', borderRadius: '6px' }}>
                <span style={{ fontSize: '13px', fontWeight: 500, color: 'var(--text-primary)' }}>{f}</span>
                <div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
                  <TMSelect value="A4" onChange={() => {}} options={['A4','A3']} style={{ padding: '4px 8px', fontSize: '12px', width: 'auto' }} />
                  <input type="number" defaultValue={selLesson.studentCount}
                    style={{ width: '56px', textAlign: 'center', background: 'var(--bg-elevated)',
                      border: '1px solid var(--border-default)', borderRadius: '4px', padding: '4px 8px',
                      fontSize: '13px', color: 'var(--text-primary)', fontFamily: 'inherit', outline: 'none' }} />
                </div>
              </div>
            ))}
            <TMSelect label="Printer" value="Parramatta printer" onChange={() => {}} options={['Parramatta printer','Bella Vista printer']} />
            <div style={{ display: 'flex', gap: '8px', justifyContent: 'flex-end' }}>
              <Button variant="secondary" onClick={() => setPrintOpen(false)}>Cancel</Button>
              <Button variant="primary"   onClick={() => setPrintOpen(false)}>Send to printer</Button>
            </div>
          </div>
        </Modal>
      )}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// INVOICES
// ─────────────────────────────────────────────────────────────

// ── Activity / audit log (shared with Settings → Audit log) ───
const SAMPLE_AUDIT = [
  { user: 'Maya R.',   action: 'Updated student status', record: 'James Chen',              detail: 'trial → post-trial',        time: '20/05/2026 09:41' },
  { user: 'System',    action: 'Auto status transition', record: 'Tom Bradley',             detail: 'trial → post-trial',        time: '20/05/2026 00:01' },
  { user: 'Jamie L.',  action: 'Created class',          record: 'YR11 ADVN — Wed 4:30pm',  detail: 'new record',                time: '19/05/2026 14:22' },
  { user: 'Taylor K.', action: 'Printed material',       record: 'ADVN-REG-B1-17',          detail: 'TH + QZ + HW × 12 copies',  time: '19/05/2026 11:05' },
  { user: 'Maya R.',   action: 'Logged call action',     record: 'James Chen',              detail: 'Called — no answer',        time: '19/05/2026 10:15' },
];
function logActivity(e) {
  const now = new Date().toLocaleString('en-AU', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' });
  SAMPLE_AUDIT.unshift({ user: e.user || 'Maya R.', action: e.action, record: e.record, detail: e.detail || '', time: now });
}

// ── Invoice spec ⇄ draft ⇄ commit (shared by single + batch flows) ──
const INV_PAY_METHODS = [
  { value: '',             label: '— Not set' },
  { value: 'stripe',       label: 'Card (Stripe)' },
  { value: 'direct-debit', label: 'Direct debit' },
  { value: 'cash',         label: 'Cash (5% off)' },
];
function invPayLabel(m) { return ({ cash: 'Cash', stripe: 'Stripe', 'direct-debit': 'Direct debit', bank: 'Bank transfer' })[m] || '—'; }

function invDefaultSpec(student, term, over = {}) {
  const cls = SAMPLE_CLASSES.find(c => c.id === student.classId);
  const t = term || (cls && cls.term) || defaultBillingTerm();
  const lines = (student.classId && cls) ? [classLine(student, cls, t, over)] : [];
  return {
    billToParentId: student.parentId, term: t, lines,
    discountsByStudent: { [student.id]: effectiveDiscounts(student).map(d => ({ ...d })) },
    applyCredit: true, sendTo: 'parent', paymentMethod: '', notes: '', issueDate: toISODate(INVOICE_TODAY),
  };
}
function invFamilySpec(parent, term) {
  const kids = SAMPLE_STUDENTS.filter(s => s.parentId === parent.id && s.status === 'enrolled' && s.classId);
  const t = term || defaultBillingTerm();
  const lines = [], disc = {};
  kids.forEach(s => { lines.push(classLine(s, SAMPLE_CLASSES.find(c => c.id === s.classId), t)); disc[s.id] = effectiveDiscounts(s).map(d => ({ ...d })); });
  return { billToParentId: parent.id, term: t, lines, discountsByStudent: disc, applyCredit: true, sendTo: 'parent', paymentMethod: '', notes: '', issueDate: toISODate(INVOICE_TODAY) };
}
function invSpecToDraft(spec, number) { return buildInvoiceRecord(spec, number || null); }
function invCommitSpec(spec) {
  const number = nextInvoiceNumber(new Date(spec.issueDate + 'T00:00:00'));
  const rec = buildInvoiceRecord(spec, number); rec.id = number.toLowerCase();
  SAMPLE_INVOICES.unshift(rec);
  Object.keys(spec.discountsByStudent || {}).forEach(sid => {
    const s = SAMPLE_STUDENTS.find(x => x.id === sid);
    if (s && s.discounts) (spec.discountsByStudent[sid] || []).forEach(d => { if (d.scope === 'once') { const m = s.discounts.find(x => x.id === d.id); if (m) m.used = true; } });
  });
  (rec.creditLines || []).forEach(c => { const s = SAMPLE_STUDENTS.find(x => x.id === c.studentId); if (s) s.creditBalance = Math.max(0, Math.round(((s.creditBalance || 0) - c.amount) * 100) / 100); });
  logActivity({ action: 'Created invoice', record: number, detail: `${invoiceCoursesLabel(rec)} · ${rec.term} · ${fmtAUD(rec.totalInc)}` });
  return rec;
}
function invSpecFromInvoice(inv) {
  const disc = {};
  (inv.studentIds || []).forEach(id => { const s = SAMPLE_STUDENTS.find(x => x.id === id); disc[id] = (s ? effectiveDiscounts(s) : []).map(d => ({ ...d })); });
  return { billToParentId: inv.billToParentId, term: inv.term, lines: (inv.lines || []).map(l => ({ ...l })), discountsByStudent: disc, applyCredit: (inv.creditApplied || 0) > 0, sendTo: inv.sendTo, paymentMethod: inv.paymentMethod || '', notes: inv.notes || '', issueDate: inv.issueDate };
}
function invApplyEdit(inv, spec) {
  const d = buildInvoiceRecord({ ...spec, issueDate: inv.issueDate }, inv.number);
  Object.assign(inv, d, { id: inv.id, number: inv.number, issueDate: inv.issueDate, dueDate: inv.dueDate, payments: inv.payments, sent: inv.sent, sentAt: inv.sentAt, sendHistory: inv.sendHistory, void: inv.void, receiptSentAt: inv.receiptSentAt, createdAt: inv.createdAt });
  logActivity({ action: 'Edited invoice', record: inv.number, detail: fmtAUD(inv.totalInc) });
}
// E9 — block a 2nd live invoice for the same student + term + course.
function invDuplicateConflict(spec, excludeId) {
  for (const l of spec.lines.filter(x => x.kind === 'class')) {
    const hit = SAMPLE_INVOICES.find(inv => !inv.void && inv.id !== excludeId && (inv.lines || []).some(x => x.kind === 'class' && x.studentId === l.studentId && x.term === l.term && x.course === l.course));
    if (hit) return { invoice: hit, student: SAMPLE_STUDENTS.find(x => x.id === l.studentId), course: l.course, term: l.term };
  }
  return null;
}
function invRecipients(inv) { return invoiceRecipients(inv); }

// ── Status pill for the list ──────────────────────────────────
function InvoiceStatusPill({ inv }) {
  const st = paymentStatus(inv);
  const pill = (col, txt) => (
    <span style={{ display: 'inline-flex', alignItems: 'center', gap: '4px', fontSize: '11px', fontWeight: 600, padding: '2px 8px',
      borderRadius: '4px', whiteSpace: 'nowrap', background: `color-mix(in srgb, ${col} 15%, transparent)`, color: col, border: `1px solid color-mix(in srgb, ${col} 32%, transparent)` }}>{txt}</span>
  );
  if (st === 'paid') return <Badge status="paid" label="Paid" />;
  if (st === 'void') return <span style={{ fontSize: '11px', color: 'var(--text-muted)' }}>Void</span>;
  if (st === 'written-off') return pill('#7A7367', 'Written off');
  if (st === 'credited') return pill('#5B8FCF', 'Credited');
  if (st === 'refunded') return pill('#9B6AC0', 'Refunded');
  const ov = invoiceOverdueInfo(inv);
  const part = st === 'partial';
  if (ov.overdue) return pill(ov.flag ? '#C05656' : '#D4A03A', `${ov.flag ? '⚑ ' : ''}${part ? 'Part · ' : ''}${ov.daysOverdue}d overdue`);
  if (part) return pill('#D4A03A', `Part-paid · ${fmtAUD(invoiceBalance(inv))} due`);
  const dueIn = ov.dueInDays;
  return pill('#7A7367', dueIn <= 0 ? 'Due today' : `Unpaid · due ${dueIn}d`);
}

// ── Searchable customer picker (enrolled students with a class) ──
function InvoiceCustomerPicker({ onPick }) {
  const [q, setQ] = useOS('');
  const inp = { width: '100%', boxSizing: 'border-box', background: 'var(--bg-surface-muted)', border: '1px solid var(--border-default)', borderRadius: '6px', padding: '9px 12px 9px 32px', fontSize: '13px', color: 'var(--text-primary)', fontFamily: 'inherit', outline: 'none' };
  const pool = SAMPLE_STUDENTS.filter(s => ['enrolled', 'trial', 'post-trial'].indexOf(s.status) !== -1);
  const query = q.trim().toLowerCase();
  const matches = (query ? pool.filter(s => `${s.firstName} ${s.lastName} ${s.course} ${customerNo(s)}`.toLowerCase().includes(query)) : pool).slice(0, 60);
  return (
    <div>
      <div style={{ position: 'relative', marginBottom: '10px' }}>
        <Icon name="search" size={14} color="var(--text-muted)" style={{ position: 'absolute', left: 10, top: '50%', transform: 'translateY(-50%)' }} />
        <input autoFocus value={q} onChange={e => setQ(e.target.value)} placeholder="Search a student by name, course or customer ID…" style={inp} />
      </div>
      <div style={{ border: '1px solid var(--border-soft)', borderRadius: '8px', maxHeight: '420px', overflowY: 'auto' }}>
        {matches.map((s, i) => {
          const p = SAMPLE_PARENTS.find(x => x.id === s.parentId);
          const cls = SAMPLE_CLASSES.find(c => c.id === s.classId);
          return (
            <div key={s.id} onClick={() => onPick(s)} style={{ display: 'flex', alignItems: 'center', gap: '10px', padding: '9px 12px',
              borderTop: i ? '1px solid var(--border-soft)' : 'none', cursor: 'pointer' }}
              onMouseEnter={e => e.currentTarget.style.background = 'var(--bg-hover)'} onMouseLeave={e => e.currentTarget.style.background = 'transparent'}>
              <Avatar name={`${s.firstName} ${s.lastName}`} size={30} />
              <div style={{ minWidth: 0, flex: 1 }}>
                <div style={{ fontSize: '13px', fontWeight: 500, color: 'var(--text-primary)' }}>{s.firstName} {s.lastName} <span style={{ fontSize: '11px', color: 'var(--text-muted)', fontFamily: 'ui-monospace, monospace' }}>· {customerNo(s)}</span></div>
                <div style={{ fontSize: '11px', color: 'var(--text-muted)' }}>{s.course} · {cls ? classLabel(cls) : '—'}{p ? ` · ${p.firstName} ${p.lastName}` : ''}</div>
              </div>
              <div style={{ display: 'flex', alignItems: 'center', gap: '8px', flexShrink: 0 }}>
                {(s.creditBalance > 0) && <span style={{ fontSize: '10px', fontWeight: 600, color: '#4A9B7E' }}>+{fmtAUD(s.creditBalance)} credit</span>}
                {s.status !== 'enrolled' && <Badge status={s.status} label={s.status} />}
              </div>
            </div>
          );
        })}
        {matches.length === 0 && <div style={{ padding: '24px', textAlign: 'center', fontSize: '13px', color: 'var(--text-muted)' }}>No matching students</div>}
      </div>
    </div>
  );
}

// ── The editable invoice form: lines (class/private/holiday/custom) + family ──
function InvoiceForm({ spec, setSpec }) {
  const studentIds = [...new Set([...(spec.lines || []).map(l => l.studentId), ...Object.keys(spec.discountsByStudent || {})])];
  const students = studentIds.map(id => SAMPLE_STUDENTS.find(s => s.id === id)).filter(Boolean);
  const family = students.length > 1;
  const parent = SAMPLE_PARENTS.find(p => p.id === spec.billToParentId);
  const [target, setTarget] = useOS(students[0] ? students[0].id : null);
  const tgt = studentIds.indexOf(target) !== -1 ? target : (students[0] && students[0].id);
  const set = patch => setSpec({ ...spec, ...patch });

  const lbl = { fontSize: '12px', color: 'var(--text-muted)', display: 'block', marginBottom: '5px' };
  const inp = { width: '100%', boxSizing: 'border-box', background: 'var(--bg-surface-muted)', border: '1px solid var(--border-default)', borderRadius: '4px', padding: '7px 10px', fontSize: '13px', color: 'var(--text-primary)', fontFamily: 'inherit', outline: 'none' };
  const numI = { ...inp, padding: '6px 8px' };
  const seg = on => ({ flex: 1, padding: '6px 10px', fontSize: '12px', fontWeight: on ? 600 : 500, fontFamily: 'inherit', cursor: 'pointer', background: on ? 'var(--bg-surface)' : 'transparent', color: on ? 'var(--text-primary)' : 'var(--text-secondary)', border: '1px solid', borderColor: on ? 'var(--border-default)' : 'transparent', borderRadius: '6px' });
  const addBtn = { background: 'var(--bg-surface-muted)', border: '1px dashed var(--border-default)', borderRadius: '6px', padding: '6px 10px', fontSize: '12px', color: 'var(--text-secondary)', cursor: 'pointer', fontFamily: 'inherit' };
  const fname = id => { const s = SAMPLE_STUDENTS.find(x => x.id === id); return s ? s.firstName + ' ' + s.lastName : ''; };

  const recalcClassLine = (l, patch) => {
    const m = { ...l, ...patch }; const cls = SAMPLE_CLASSES.find(c => c.id === m.classId); const weeks = termWeeks(m.term);
    const fw = Math.min(Math.max(1, m.firstWeek), weeks.length), lw = Math.min(Math.max(fw, m.lastWeek), weeks.length); const day = cls ? cls.day : 'Monday';
    return { ...m, firstWeek: fw, lastWeek: lw, qty: lw - fw + 1, startDate: toISODate(lessonDateInWeek(weeks[fw - 1].start, day)), endDate: toISODate(lessonDateInWeek(weeks[lw - 1].start, day)) };
  };
  const recalcPrivateTermLine = (l, patch) => {
    const m = { ...l, ...patch }; const weeks = termWeeks(m.term);
    const fw = Math.min(Math.max(1, m.firstWeek), weeks.length), lw = Math.min(Math.max(fw, m.lastWeek), weeks.length);
    return { ...m, firstWeek: fw, lastWeek: lw, qty: lw - fw + 1, hours: lessonHours({ startTime: m.startTime, endTime: m.endTime }),
      startDate: toISODate(lessonDateInWeek(weeks[fw - 1].start, m.day)), endDate: toISODate(lessonDateInWeek(weeks[lw - 1].start, m.day)) };
  };
  const updateLine = (id, patch) => set({ lines: spec.lines.map(l => {
    if (l.id !== id) return l;
    if (l.kind === 'class') return recalcClassLine(l, patch);
    if (l.kind === 'private-term') return recalcPrivateTermLine(l, patch);
    return { ...l, ...patch };
  }) });
  const removeLine = id => set({ lines: spec.lines.filter(l => l.id !== id) });
  const ensureDisc = (sp, sid) => { if (!sp.discountsByStudent[sid]) { const s = SAMPLE_STUDENTS.find(x => x.id === sid); sp.discountsByStudent = { ...sp.discountsByStudent, [sid]: (s ? effectiveDiscounts(s) : []).map(d => ({ ...d })) }; } };
  const addLine = ln => { const sp = { ...spec, lines: [...spec.lines, ln] }; ensureDisc(sp, ln.studentId); setSpec(sp); };
  const setTerm = v => set({ term: v, lines: spec.lines.map(l => l.kind === 'class' ? recalcClassLine(l, { term: v }) : l.kind === 'private-term' ? recalcPrivateTermLine(l, { term: v }) : l) });
  const setDisc = (sid, arr) => set({ discountsByStudent: { ...spec.discountsByStudent, [sid]: arr } });

  const siblings = SAMPLE_STUDENTS.filter(s => s.parentId === spec.billToParentId && s.status === 'enrolled' && s.classId && studentIds.indexOf(s.id) === -1);
  const tgtObj = SAMPLE_STUDENTS.find(s => s.id === tgt);

  const lineCard = l => {
    const cls = l.classId && SAMPLE_CLASSES.find(c => c.id === l.classId);
    const weeks = termWeeks(l.term || spec.term);
    return (
      <div key={l.id} style={{ background: 'var(--bg-surface-muted)', border: '1px solid var(--border-soft)', borderRadius: '8px', padding: '10px 12px' }}>
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '8px', marginBottom: '8px' }}>
          <span style={{ fontSize: '12px', fontWeight: 600, color: 'var(--text-primary)' }}>
            {family && <span style={{ color: '#4A9B7E' }}>{fname(l.studentId).split(' ')[0]} · </span>}
            {l.kind === 'class' ? 'Class' : l.kind === 'private-term' ? 'Weekly private' : l.kind === 'private' ? 'Private (ad-hoc)' : l.kind === 'holiday' ? 'Holiday program' : 'Custom item'}
          </span>
          <div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
            <span style={{ fontSize: '13px', fontWeight: 700, color: 'var(--text-primary)', fontVariantNumeric: 'tabular-nums' }}>{fmtAUD(lineAmount(l))}</span>
            <button onClick={() => removeLine(l.id)} title="Remove line" style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-muted)', fontSize: '16px', lineHeight: 1, fontFamily: 'inherit' }}>×</button>
          </div>
        </div>
        {l.kind === 'class' && (
          <div>
            <div style={{ fontSize: '12px', color: 'var(--text-secondary)', marginBottom: '8px' }}>{l.course}{cls ? ` · ${cls.day} ${cls.startTime}–${cls.endTime}` : ''} · {l.hours}h/lesson</div>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '8px' }}>
              <div><label style={lbl}>From week</label><FormSelect value={String(l.firstWeek)} onChange={v => updateLine(l.id, { firstWeek: parseInt(v, 10) })} options={weeks.map(w => ({ value: String(w.week), label: `Wk ${w.week}` }))} buttonStyle={{ padding: '6px 8px' }} /></div>
              <div><label style={lbl}>To week</label><FormSelect value={String(l.lastWeek)} onChange={v => updateLine(l.id, { lastWeek: parseInt(v, 10) })} options={weeks.filter(w => w.week >= l.firstWeek).map(w => ({ value: String(w.week), label: `Wk ${w.week}` }))} buttonStyle={{ padding: '6px 8px' }} /></div>
              <div><label style={lbl}>Rate $/h</label><input type="number" min="0" step="5" value={l.rate} onChange={e => updateLine(l.id, { rate: parseFloat(e.target.value) || 0 })} style={numI} /></div>
            </div>
          </div>
        )}
        {l.kind === 'private-term' && (
          <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
            <div><label style={lbl}>Description</label><input value={l.description} onChange={e => updateLine(l.id, { description: e.target.value })} style={numI} /></div>
            <div style={{ display: 'grid', gridTemplateColumns: '1.1fr 1fr 1fr 72px', gap: '8px' }}>
              <div><label style={lbl}>Day</label><FormSelect value={l.day} onChange={v => updateLine(l.id, { day: v })} options={['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']} buttonStyle={{ padding: '6px 8px' }} /></div>
              <div><label style={lbl}>Start</label><FormSelect searchable value={l.startTime} onChange={v => updateLine(l.id, { startTime: v })} options={TIME_OPTIONS} buttonStyle={{ padding: '6px 8px' }} /></div>
              <div><label style={lbl}>End</label><FormSelect searchable value={l.endTime} onChange={v => updateLine(l.id, { endTime: v })} options={TIME_OPTIONS} buttonStyle={{ padding: '6px 8px' }} /></div>
              <div><label style={lbl}>Rate $/h</label><input type="number" min="0" step="5" value={l.rate} onChange={e => updateLine(l.id, { rate: parseFloat(e.target.value) || 0 })} style={numI} /></div>
            </div>
            <div style={{ display: 'grid', gridTemplateColumns: '1.4fr 1fr 1fr', gap: '8px' }}>
              <div><label style={lbl}>Term</label><FormSelect value={l.term} onChange={v => updateLine(l.id, { term: v })} options={TERM_SCHEDULE.map(t => ({ value: t.name, label: t.name }))} buttonStyle={{ padding: '6px 8px' }} /></div>
              <div><label style={lbl}>From week</label><FormSelect value={String(l.firstWeek)} onChange={v => updateLine(l.id, { firstWeek: parseInt(v, 10) })} options={weeks.map(w => ({ value: String(w.week), label: `Wk ${w.week}` }))} buttonStyle={{ padding: '6px 8px' }} /></div>
              <div><label style={lbl}>To week</label><FormSelect value={String(l.lastWeek)} onChange={v => updateLine(l.id, { lastWeek: parseInt(v, 10) })} options={weeks.filter(w => w.week >= l.firstWeek).map(w => ({ value: String(w.week), label: `Wk ${w.week}` }))} buttonStyle={{ padding: '6px 8px' }} /></div>
            </div>
            <div style={{ fontSize: '11px', color: 'var(--text-muted)' }}>{lessonHours({ startTime: l.startTime, endTime: l.endTime })}h × {l.qty} week{l.qty === 1 ? '' : 's'} = {fmtAUD(lineAmount(l))}</div>
          </div>
        )}
        {l.kind === 'private' && (
          <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 90px', gap: '8px' }}>
              <div><label style={lbl}>Description</label><input value={l.description} onChange={e => updateLine(l.id, { description: e.target.value })} style={numI} /></div>
              <div><label style={lbl}>Rate $/h</label><input type="number" min="0" step="5" value={l.rate} onChange={e => updateLine(l.id, { rate: parseFloat(e.target.value) || 0 })} style={numI} /></div>
            </div>
            <label style={lbl}>Sessions ({(l.sessions || []).length}) — each can be a different day &amp; time</label>
            <div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
              {(l.sessions || []).map((s, si) => (
                <div key={si} style={{ display: 'grid', gridTemplateColumns: '1.1fr 1fr 1fr auto', gap: '6px', alignItems: 'center' }}>
                  <input type="date" value={s.date} onChange={e => updateLine(l.id, { sessions: l.sessions.map((x, j) => j === si ? { ...x, date: e.target.value } : x) })} style={{ ...numI, colorScheme: 'dark' }} />
                  <FormSelect searchable value={s.startTime} onChange={v => updateLine(l.id, { sessions: l.sessions.map((x, j) => j === si ? { ...x, startTime: v } : x) })} options={TIME_OPTIONS} buttonStyle={{ padding: '6px 8px' }} />
                  <FormSelect searchable value={s.endTime} onChange={v => updateLine(l.id, { sessions: l.sessions.map((x, j) => j === si ? { ...x, endTime: v } : x) })} options={TIME_OPTIONS} buttonStyle={{ padding: '6px 8px' }} />
                  <button onClick={() => updateLine(l.id, { sessions: l.sessions.filter((_, j) => j !== si) })} title="Remove session" style={{ background: 'none', border: '1px solid var(--border-default)', borderRadius: '5px', padding: '4px 9px', fontSize: '13px', color: 'var(--text-muted)', cursor: 'pointer', fontFamily: 'inherit', flexShrink: 0, lineHeight: 1 }}>×</button>
                </div>
              ))}
            </div>
            <button onClick={() => updateLine(l.id, { sessions: [...(l.sessions || []), { date: toISODate(INVOICE_TODAY), startTime: '4:00pm', endTime: '5:00pm' }] })} style={{ ...addBtn, alignSelf: 'flex-start' }}>+ Add session</button>
            <div style={{ fontSize: '11px', color: 'var(--text-muted)' }}>{(l.sessions || []).length} session{(l.sessions || []).length === 1 ? '' : 's'} = {fmtAUD(lineAmount(l))}</div>
          </div>
        )}
        {l.kind === 'holiday' && (
          <div style={{ display: 'grid', gridTemplateColumns: '2fr 1fr', gap: '8px', alignItems: 'end' }}>
            <div><label style={lbl}>Program</label><input value={l.description} onChange={e => updateLine(l.id, { description: e.target.value })} style={numI} /></div>
            <div><label style={lbl}>Price (incl. GST)</label><input type="number" min="0" value={l.rate} onChange={e => updateLine(l.id, { rate: parseFloat(e.target.value) || 0 })} style={numI} /></div>
          </div>
        )}
        {l.kind === 'custom' && (
          <div style={{ display: 'grid', gridTemplateColumns: '2fr 1fr 1fr', gap: '8px', alignItems: 'end' }}>
            <div><label style={lbl}>Description</label><input value={l.description} onChange={e => updateLine(l.id, { description: e.target.value })} placeholder="e.g. Refundable deposit" style={numI} /></div>
            <div><label style={lbl}>Unit price</label><input type="number" min="0" value={l.rate} onChange={e => updateLine(l.id, { rate: parseFloat(e.target.value) || 0 })} style={numI} /></div>
            <div><label style={lbl}>Qty</label><input type="number" min="1" value={l.qty} onChange={e => updateLine(l.id, { qty: parseInt(e.target.value, 10) || 1 })} style={numI} /></div>
          </div>
        )}
      </div>
    );
  };

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: '14px' }}>
      {/* Bill-to + students */}
      <div style={{ background: 'var(--bg-surface-muted)', border: '1px solid var(--border-soft)', borderRadius: '8px', padding: '12px' }}>
        <div style={{ fontSize: '11px', color: 'var(--text-muted)' }}>Bill to</div>
        <div style={{ fontSize: '13px', fontWeight: 600, color: 'var(--text-primary)' }}>{parent ? `${parent.firstName} ${parent.lastName}` : '—'}</div>
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: '6px', marginTop: '8px' }}>
          {students.map(s => <span key={s.id} style={{ fontSize: '11px', padding: '2px 8px', borderRadius: '4px', background: 'var(--bg-surface)', border: '1px solid var(--border-default)', color: 'var(--text-secondary)' }}>{s.firstName} {s.lastName} · {customerNo(s)}</span>)}
        </div>
      </div>

      {/* Term (shared) */}
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '10px', alignItems: 'end' }}>
        <div><label style={lbl}>Term</label><FormSelect value={spec.term} onChange={setTerm} options={TERM_SCHEDULE.map(t => ({ value: t.name, label: t.name }))} /></div>
        {(students.length > 1 || siblings.length > 0) && <div><label style={lbl}>Add lines for</label><FormSelect value={tgt} onChange={setTarget} options={students.map(s => ({ value: s.id, label: s.firstName }))} /></div>}
      </div>

      {/* Lines */}
      <div>
        <label style={lbl}>Invoice lines</label>
        <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
          {spec.lines.length === 0 && <div style={{ fontSize: '12px', color: '#B8922A' }}>No lines yet — add a class, private lesson, holiday program or custom item below.</div>}
          {spec.lines.map(lineCard)}
        </div>
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px', alignItems: 'center', marginTop: '10px' }}>
          <div style={{ minWidth: 150 }}>
            <TMSelect value="" placeholder="+ Add class" searchable onChange={cid => { const cls = SAMPLE_CLASSES.find(c => c.id === cid); if (cls && tgtObj) addLine(classLine(tgtObj, cls, spec.term)); }}
              options={SAMPLE_CLASSES.filter(c => !c.archived).map(c => ({ value: c.id, label: `${classLabel(c)} · ${c.campus}` }))} buttonStyle={{ padding: '6px 9px' }} />
          </div>
          <button style={addBtn} onClick={() => tgtObj && addLine(privateTermLine(tgtObj, { term: spec.term }))}>+ Weekly private</button>
          <button style={addBtn} onClick={() => tgtObj && addLine({ id: newLineId(), studentId: tgtObj.id, kind: 'private', course: null, rate: INVOICE_SETTINGS.privateRate, description: 'PRIVATE 1-on-1', sessions: [{ date: toISODate(INVOICE_TODAY), startTime: '4:00pm', endTime: '5:00pm' }] })}>+ Ad-hoc private</button>
          <div style={{ minWidth: 140 }}>
            <TMSelect value="" placeholder="+ Holiday program" onChange={pid => { const p = SAMPLE_HOLIDAY_PROGRAMS.find(x => x.id === pid); if (p && tgtObj) addLine(holidayLine(tgtObj, p)); }}
              options={SAMPLE_HOLIDAY_PROGRAMS.filter(p => !p.archived).map(p => ({ value: p.id, label: `${p.shortName || p.name} · ${fmtAUD(p.price)}` }))} buttonStyle={{ padding: '6px 9px' }} />
          </div>
          <button style={addBtn} onClick={() => tgtObj && addLine(enrolmentFeeLine(tgtObj))}>+ {INVOICE_SETTINGS.enrolmentFeeLabel} ({fmtAUD(INVOICE_SETTINGS.enrolmentFee)})</button>
          <button style={addBtn} onClick={() => tgtObj && addLine({ id: newLineId(), studentId: tgtObj.id, kind: 'custom', course: null, hours: null, rate: 0, qty: 1, description: 'Custom item' })}>+ Custom line</button>
        </div>
        {siblings.length > 0 && (
          <div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px', alignItems: 'center', marginTop: '8px' }}>
            <span style={{ fontSize: '11px', color: 'var(--text-muted)' }}>Combine sibling:</span>
            {siblings.map(s => { const cls = SAMPLE_CLASSES.find(c => c.id === s.classId); return <button key={s.id} style={addBtn} onClick={() => addLine(classLine(s, cls, spec.term))}>+ {s.firstName} ({s.course})</button>; })}
          </div>
        )}
      </div>

      {/* Discounts (per student) */}
      <div>
        <label style={lbl}>Discounts (auto-applied)</label>
        {students.map(s => {
          const ds = spec.discountsByStudent[s.id] || [];
          const addable = SAMPLE_DISCOUNT_TYPES.filter(t => !ds.some(d => d.typeId === t.id));
          return (
            <div key={s.id} style={{ marginBottom: '8px' }}>
              {family && <div style={{ fontSize: '11px', fontWeight: 600, color: 'var(--text-secondary)', marginBottom: '4px' }}>{s.firstName}</div>}
              <div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
                {ds.length === 0 && <div style={{ fontSize: '12px', color: 'var(--text-muted)' }}>None.</div>}
                {ds.map(d => (
                  <div key={d.id} style={{ display: 'flex', alignItems: 'center', gap: '8px', background: 'var(--bg-surface-muted)', border: '1px solid var(--border-soft)', borderRadius: '6px', padding: '6px 8px 6px 10px' }}>
                    <span style={{ fontSize: '12px', color: 'var(--text-primary)', flex: 1 }}>{d.name} <span style={{ color: 'var(--text-muted)' }}>· {d.kind === 'percent' ? `${d.value}%` : fmtAUD(d.value)}</span>{d.scope === 'once' && <span style={{ marginLeft: 6, fontSize: '10px', color: '#B8922A' }}>once-off</span>}</span>
                    <button onClick={() => setDisc(s.id, ds.filter(x => x.id !== d.id))} title="Remove" style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-muted)', fontSize: '15px', lineHeight: 1, fontFamily: 'inherit' }}>×</button>
                  </div>
                ))}
              </div>
              {addable.length > 0 && (
                <div style={{ marginTop: '6px' }}>
                  <TMSelect value="" placeholder="+ Add a discount…" onChange={id => { const t = SAMPLE_DISCOUNT_TYPES.find(x => x.id === id); if (t) setDisc(s.id, [...ds, { id: `adhoc-${t.id}-${Date.now()}`, typeId: t.id, name: t.name, kind: t.kind, value: t.value, scope: 'once', used: false }]); }}
                    options={addable.map(t => ({ value: t.id, label: `${t.name} (${t.kind === 'percent' ? t.value + '%' : '$' + t.value})` }))} />
                </div>
              )}
            </div>
          );
        })}
      </div>

      {/* Credit */}
      {students.some(s => (s.creditBalance || 0) > 0) && (
        <label style={{ display: 'flex', alignItems: 'center', gap: '10px', background: 'rgba(74,155,126,0.08)', border: '1px solid rgba(74,155,126,0.25)', borderRadius: '8px', padding: '10px 12px', cursor: 'pointer' }}>
          <Checkbox checked={spec.applyCredit} onChange={v => set({ applyCredit: v })} color="#4A9B7E" />
          <span style={{ fontSize: '12px', color: 'var(--text-primary)' }}>Apply available credit <strong style={{ color: '#4A9B7E' }}>{fmtAUD(students.reduce((a, s) => a + (s.creditBalance || 0), 0))}</strong> to this invoice</span>
        </label>
      )}

      {/* Send to + method */}
      <div style={{ display: 'grid', gridTemplateColumns: '1.2fr 1fr', gap: '10px', alignItems: 'end' }}>
        <div>
          <label style={lbl}>Email to (default parent)</label>
          <div style={{ display: 'flex', gap: '4px', background: 'var(--bg-surface-muted)', border: '1px solid var(--border-soft)', borderRadius: '7px', padding: '3px' }}>
            {[['parent', 'Parent'], ['student', 'Student'], ['both', 'Both']].map(([v, l]) => (<button key={v} onClick={() => set({ sendTo: v })} style={seg(spec.sendTo === v)}>{l}</button>))}
          </div>
        </div>
        <div><label style={lbl}>Payment method</label><FormSelect value={spec.paymentMethod} onChange={v => set({ paymentMethod: v })} options={INV_PAY_METHODS} /></div>
      </div>

      <div><label style={lbl}>Notes (optional)</label>
        <textarea value={spec.notes} onChange={e => set({ notes: e.target.value })} rows={2} placeholder="Anything to show on the invoice…" style={{ ...inp, resize: 'vertical', lineHeight: 1.5 }} /></div>
    </div>
  );
}

// Simulated email send. E1: no contact on file → don't send, create a reception task.
function emailInvoice(inv) {
  const r = invoiceRecipients(inv);
  if (r.length === 0) {
    inv.sendError = 'No contact on file';
    if (!SAMPLE_SELF_TASKS.some(t => t.id === `inv-noemail-${inv.id}`)) SAMPLE_SELF_TASKS.push({ id: `inv-noemail-${inv.id}`, type: 'Generic task', title: `Email invoice ${inv.number} — no contact on file`, assignee: 'Maya R.', dueDate: toISODate(INVOICE_TODAY), status: 'open' });
    logActivity({ action: 'Invoice email failed', record: inv.number, detail: 'no contact on file → task created' });
    return { ok: false, recipients: [] };
  }
  inv.sendError = null;
  inv.sent = true; inv.sentAt = toISODate(INVOICE_TODAY); inv.sendHistory = inv.sendHistory || []; inv.sendHistory.push({ at: inv.sentAt, to: inv.sendTo });
  logActivity({ action: 'Emailed invoice', record: inv.number, detail: r.join(', ') });
  return { ok: true, recipients: r };
}

// ── New / Edit invoice modal (form + live preview) ────────────
function NewInvoiceModal({ open, onClose, editingInv, onSaved, flash, initialStudentId }) {
  const [spec, setSpec] = useOS(null);
  const [ack, setAck] = useOS(false);
  React.useEffect(() => {
    if (!open) { setSpec(null); setAck(false); return; }
    if (editingInv) setSpec(invSpecFromInvoice(editingInv));
    else if (initialStudentId) { const s = SAMPLE_STUDENTS.find(x => x.id === initialStudentId); setSpec(s ? invDefaultSpec(s) : null); }
    else setSpec(null);
    setAck(false);
  }, [open, editingInv, initialStudentId]);
  if (!open) return null;

  const studentIds = spec ? [...new Set([...(spec.lines || []).map(l => l.studentId), ...Object.keys(spec.discountsByStudent || {})])] : [];
  const nonEnrolled = spec ? studentIds.map(id => SAMPLE_STUDENTS.find(s => s.id === id)).filter(s => s && s.status !== 'enrolled') : [];
  const conflict = spec && !editingInv ? invDuplicateConflict(spec, editingInv && editingInv.id) : null;
  const hasLines = spec && spec.lines.length > 0;
  const needAck = !editingInv && nonEnrolled.length > 0;
  const draft = spec ? invSpecToDraft(spec, editingInv ? editingInv.number : null) : null;
  const zeroLines = draft ? draft.lines.filter(l => (l.amount != null ? l.amount : lineAmount(l)) <= 0).length : 0;

  const pick = s => { setSpec(invDefaultSpec(s)); setAck(false); };
  const save = () => {
    if (conflict || !hasLines || (needAck && !ack) || zeroLines > 0) return;
    if (editingInv) { invApplyEdit(editingInv, spec); flash(`Invoice ${editingInv.number} updated`); }
    else { const rec = invCommitSpec(spec); flash(`Invoice ${rec.number} created — ${invoiceCoursesLabel(rec)} · ${fmtAUD(rec.totalInc)}`); }
    onSaved(); onClose();
  };

  return (
    <Modal open={open} onClose={onClose} title={editingInv ? `Edit ${editingInv.number}` : 'New invoice'} width={spec ? 1320 : 560}>
      {!spec ? (
        <InvoiceCustomerPicker onPick={pick} />
      ) : (
        <div style={{ display: 'grid', gridTemplateColumns: 'minmax(340px, 1fr) 800px', gap: '20px', alignItems: 'start' }}>
          <div>
            <InvoiceForm spec={spec} setSpec={setSpec} />
            {conflict && (
              <div style={{ marginTop: '12px', background: 'rgba(192,86,86,0.12)', border: '1px solid rgba(192,86,86,0.4)', borderRadius: '6px', padding: '10px 12px', fontSize: '12px', color: '#C05656' }}>
                ⛔ <strong>Blocked.</strong> {conflict.student ? `${conflict.student.firstName} ${conflict.student.lastName}` : 'This student'} already has invoice <strong>{conflict.invoice.number}</strong> for <strong>{conflict.course}</strong> in {conflict.term}. Duplicate invoices for the same student, term and course aren’t allowed — edit or void the existing one instead.
              </div>
            )}
            {needAck && !conflict && (
              <label style={{ display: 'flex', gap: '10px', alignItems: 'flex-start', marginTop: '12px', background: 'rgba(212,160,58,0.12)', border: '1px solid rgba(212,160,58,0.35)', borderRadius: '6px', padding: '10px 12px', fontSize: '12px', color: '#B8922A', cursor: 'pointer' }}>
                <Checkbox checked={ack} onChange={setAck} color="#B8922A" />
                <span>⚠ {nonEnrolled.map(s => `${s.firstName} ${s.lastName}`).join(', ')} {nonEnrolled.length > 1 ? 'are' : 'is'} not enrolled yet ({nonEnrolled[0].status}). Confirm you want to invoice anyway (e.g. a refundable trial deposit).</span>
              </label>
            )}
            {zeroLines > 0 && !conflict && (
              <div style={{ marginTop: '12px', background: 'rgba(212,160,58,0.12)', border: '1px solid rgba(212,160,58,0.35)', borderRadius: '6px', padding: '10px 12px', fontSize: '12px', color: '#B8922A' }}>
                ⚠ {zeroLines} line{zeroLines === 1 ? '' : 's'} total $0 — check the start/end times and rate before creating.
              </div>
            )}
            <div style={{ display: 'flex', gap: '8px', justifyContent: 'flex-end', marginTop: '16px' }}>
              {!editingInv && <Button variant="ghost" onClick={() => setSpec(null)}>← Change customer</Button>}
              <Button variant="secondary" onClick={onClose}>Cancel</Button>
              <Button variant="primary" disabled={!!conflict || !hasLines || (needAck && !ack) || zeroLines > 0} onClick={save}>{editingInv ? 'Save changes' : 'Create invoice'}</Button>
            </div>
          </div>
          <div>
            <div style={{ fontSize: '11px', fontWeight: 600, letterSpacing: '0.05em', textTransform: 'uppercase', color: 'var(--text-muted)', marginBottom: '10px' }}>Live preview · full size</div>
            <div style={{ width: 780, maxWidth: '100%', margin: '0 auto', boxShadow: '0 8px 28px rgba(0,0,0,0.35)', borderRadius: '10px' }}>
              <InvoiceDocument inv={draft} />
            </div>
          </div>
        </div>
      )}
    </Modal>
  );
}

// ── Batch create / term roll-over ─────────────────────────────
function BatchInvoiceModal({ open, onClose, onSaved, flash }) {
  const [scope, setScope] = useOS('class');     // 'class' | 'course' | 'term'
  const [classId, setClassId] = useOS('');
  const [course, setCourse] = useOS('YR11 ADVN');
  const [term, setTerm] = useOS(defaultBillingTerm());
  const [specs, setSpecs] = useOS([]);           // [{ spec, include, conflict }]
  const [editIdx, setEditIdx] = useOS(-1);
  const [built, setBuilt] = useOS(false);
  const [excluded, setExcluded] = useOS([]);     // non-running week numbers (e.g. public holidays)
  const [exclNote, setExclNote] = useOS('Public holiday');

  React.useEffect(() => { if (!open) { setSpecs([]); setBuilt(false); setEditIdx(-1); setClassId(''); setScope('class'); setExcluded([]); setExclNote('Public holiday'); } }, [open]);
  if (!open) return null;

  const COURSES = ['YR09', 'YR10', 'YR11 STD', 'YR11 ADVN', 'YR11 EXT1', 'YR12 STD', 'YR12 ADVN', 'YR12 EXT1', 'YR12 EXT2'];
  const seg = on => ({ flex: 1, padding: '7px 12px', fontSize: '13px', fontWeight: on ? 600 : 500, fontFamily: 'inherit', cursor: 'pointer', background: on ? 'var(--bg-surface)' : 'transparent', color: on ? 'var(--text-primary)' : 'var(--text-secondary)', border: '1px solid', borderColor: on ? 'var(--border-default)' : 'transparent', borderRadius: '6px' });

  const targetStudents = () => {
    if (scope === 'class') return classId ? SAMPLE_STUDENTS.filter(s => s.status === 'enrolled' && s.classId === classId) : [];
    if (scope === 'term') return SAMPLE_STUDENTS.filter(s => s.status === 'enrolled' && s.classId);
    const classIds = SAMPLE_CLASSES.filter(c => c.course === course && !c.archived).map(c => c.id);
    return SAMPLE_STUDENTS.filter(s => s.status === 'enrolled' && classIds.indexOf(s.classId) !== -1);
  };
  const billTerm = scope === 'class' ? ((SAMPLE_CLASSES.find(c => c.id === classId) || {}).term || term) : term;
  const weeksForTerm = termWeeks(billTerm);
  const toggleWeek = w => setExcluded(excluded.indexOf(w) !== -1 ? excluded.filter(x => x !== w) : [...excluded, w].sort((a, b) => a - b));
  const build = () => {
    const over = { excludedWeeks: excluded, excludedNote: exclNote };
    setSpecs(targetStudents().map(s => { const spec = invDefaultSpec(s, billTerm, over); const conflict = invDuplicateConflict(spec); return { spec, include: !conflict, conflict }; }));
    setBuilt(true);
  };
  const lbl = { fontSize: '12px', color: 'var(--text-muted)', display: 'block', marginBottom: '5px' };
  const included = specs.filter(x => x.include);
  const conflicts = specs.filter(x => x.conflict).length;
  const primaryOf = sp => SAMPLE_STUDENTS.find(s => s.id === (sp.lines[0] || {}).studentId);

  const createAll = (emailToo) => {
    let n = 0, blocked = 0;
    included.forEach(x => { const rec = invCommitSpec(x.spec); n++; if (emailToo) { const r = emailInvoice(rec); if (!r.ok) blocked++; } });
    flash(`${n} invoice${n === 1 ? '' : 's'} created${emailToo ? ` & emailed${blocked ? ` (${blocked} need a contact — see Tasks)` : ''}` : ' as drafts'}`);
    onSaved(); onClose();
  };

  return (
    <Modal open={open} onClose={onClose} title="Batch create / term roll-over" width={780}>
      {!built ? (
        <div style={{ display: 'flex', flexDirection: 'column', gap: '14px' }}>
          <div>
            <label style={lbl}>Create for</label>
            <div style={{ display: 'flex', gap: '4px', background: 'var(--bg-surface-muted)', border: '1px solid var(--border-soft)', borderRadius: '7px', padding: '3px' }}>
              <button style={seg(scope === 'class')} onClick={() => setScope('class')}>A class</button>
              <button style={seg(scope === 'course')} onClick={() => setScope('course')}>A course</button>
              <button style={seg(scope === 'term')} onClick={() => setScope('term')}>Whole term (roll-over)</button>
            </div>
          </div>
          {scope === 'class' && (
            <div><label style={lbl}>Class</label>
              <FormSelect value={classId} onChange={setClassId} placeholder="Select a class…"
                options={SAMPLE_CLASSES.filter(c => !c.archived).map(c => ({ value: c.id, label: `${classLabel(c)} · ${c.campus}` }))} /></div>
          )}
          {scope === 'course' && (
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '10px' }}>
              <div><label style={lbl}>Course</label><FormSelect value={course} onChange={setCourse} options={COURSES} /></div>
              <div><label style={lbl}>Term</label><FormSelect value={term} onChange={setTerm} options={TERM_SCHEDULE.map(t => ({ value: t.name, label: t.name }))} /></div>
            </div>
          )}
          {scope === 'term' && (
            <div><label style={lbl}>Term to bill</label><FormSelect value={term} onChange={setTerm} options={TERM_SCHEDULE.map(t => ({ value: t.name, label: t.name }))} /></div>
          )}
          <div>
            <label style={lbl}>Non-running weeks <span style={{ color: 'var(--text-muted)' }}>— excluded from billing for the whole batch (e.g. public holidays)</span></label>
            <div style={{ display: 'flex', flexWrap: 'wrap', gap: '6px' }}>
              {weeksForTerm.map(w => { const on = excluded.indexOf(w.week) !== -1; return (
                <button key={w.week} onClick={() => toggleWeek(w.week)} title={`${fmtAUDate(w.start)} – ${fmtAUDate(w.end)}`} style={{ fontSize: '12px', fontWeight: on ? 600 : 500, fontFamily: 'inherit', cursor: 'pointer', padding: '5px 10px', borderRadius: '6px', background: on ? 'rgba(192,86,86,0.12)' : 'var(--bg-surface-muted)', border: `1px solid ${on ? 'rgba(192,86,86,0.45)' : 'var(--border-default)'}`, color: on ? '#C05656' : 'var(--text-secondary)' }}>{on ? '✕ ' : ''}Wk {w.week}</button>
              ); })}
            </div>
            {excluded.length > 0 && <div style={{ marginTop: '8px', maxWidth: 300 }}><input value={exclNote} onChange={e => setExclNote(e.target.value)} placeholder="Reason (shown on invoice)" style={{ width: '100%', boxSizing: 'border-box', background: 'var(--bg-surface-muted)', border: '1px solid var(--border-default)', borderRadius: '4px', padding: '7px 10px', fontSize: '13px', color: 'var(--text-primary)', fontFamily: 'inherit', outline: 'none' }} /></div>}
          </div>
          <div style={{ fontSize: '12px', color: 'var(--text-muted)' }}>{targetStudents().length} enrolled student{targetStudents().length === 1 ? '' : 's'} → one draft invoice each.{excluded.length ? ` ${excluded.length} week${excluded.length === 1 ? '' : 's'} excluded.` : ''} You can edit or exclude any before creating; students who already have an invoice for that term + course are skipped automatically. Each class’s own day sets that student’s dates.</div>
          <div style={{ display: 'flex', gap: '8px', justifyContent: 'flex-end' }}>
            <Button variant="secondary" onClick={onClose}>Cancel</Button>
            <Button variant="primary" disabled={targetStudents().length === 0} onClick={build}>Build {targetStudents().length} drafts</Button>
          </div>
        </div>
      ) : (
        <div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
          <div style={{ fontSize: '12px', color: 'var(--text-secondary)' }}><strong style={{ color: 'var(--text-primary)' }}>{included.length}</strong> of {specs.length} selected · total {fmtAUD(included.reduce((a, x) => a + invSpecToDraft(x.spec).totalInc, 0))}{conflicts ? ` · ${conflicts} skipped (already invoiced)` : ''}</div>
          <div style={{ border: '1px solid var(--border-soft)', borderRadius: '8px', maxHeight: '380px', overflowY: 'auto' }}>
            {specs.map((x, i) => {
              const d = invSpecToDraft(x.spec);
              const s = primaryOf(x.spec);
              const cl = (d.lines || []).find(l => l.kind === 'class');
              return (
                <div key={s.id} style={{ display: 'flex', alignItems: 'center', gap: '10px', padding: '9px 12px', borderTop: i ? '1px solid var(--border-soft)' : 'none', opacity: x.include ? 1 : 0.5 }}>
                  <Checkbox checked={x.include} onChange={() => { if (x.conflict) return; setSpecs(specs.map((y, j) => j === i ? { ...y, include: !y.include } : y)); }} />
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: '13px', color: 'var(--text-primary)', fontWeight: 500 }}>{s.firstName} {s.lastName} <span style={{ fontSize: '11px', color: 'var(--text-muted)' }}>· {customerNo(s)}</span></div>
                    <div style={{ fontSize: '11px', color: x.conflict ? '#C05656' : 'var(--text-muted)' }}>
                      {x.conflict ? `Already invoiced (${x.conflict.invoice.number}) — skipped` : `${invoiceWeeksLabel(d)}${cl ? ` · ${fmtAUDate(cl.startDate)} – ${fmtAUDate(cl.endDate)}` : ''}${d.discountsApplied.length ? ` · ${d.discountsApplied.length} discount${d.discountsApplied.length === 1 ? '' : 's'}` : ''}${d.creditApplied ? ` · credit ${fmtAUD(d.creditApplied)}` : ''}`}
                    </div>
                  </div>
                  <span style={{ fontSize: '13px', fontWeight: 600, color: 'var(--text-primary)', fontVariantNumeric: 'tabular-nums' }}>{fmtAUD(d.totalInc)}</span>
                  {!x.conflict && <button onClick={() => setEditIdx(i)} style={{ background: 'none', border: '1px solid var(--border-default)', borderRadius: '5px', padding: '4px 10px', fontSize: '11px', color: 'var(--text-secondary)', cursor: 'pointer', fontFamily: 'inherit' }}>Edit</button>}
                </div>
              );
            })}
          </div>
          <div style={{ display: 'flex', gap: '8px', justifyContent: 'flex-end', flexWrap: 'wrap' }}>
            <Button variant="ghost" onClick={() => setBuilt(false)}>← Back</Button>
            <Button variant="secondary" onClick={() => createAll(false)} disabled={included.length === 0}>Create {included.length} drafts</Button>
            <Button variant="primary" onClick={() => createAll(true)} disabled={included.length === 0}>Create &amp; email {included.length}</Button>
          </div>

          {editIdx >= 0 && (
            <Modal open={true} onClose={() => setEditIdx(-1)} title={`Edit draft — ${(primaryOf(specs[editIdx].spec) || {}).firstName || ''}`} width={1320}>
              <div style={{ display: 'grid', gridTemplateColumns: 'minmax(340px, 1fr) 800px', gap: '20px', alignItems: 'start' }}>
                <div>
                  <InvoiceForm spec={specs[editIdx].spec} setSpec={sp => setSpecs(specs.map((y, j) => j === editIdx ? { ...y, spec: sp } : y))} />
                  <div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: '14px' }}>
                    <Button variant="primary" onClick={() => setEditIdx(-1)}>Done</Button>
                  </div>
                </div>
                <div>
                  <div style={{ fontSize: '11px', fontWeight: 600, letterSpacing: '0.05em', textTransform: 'uppercase', color: 'var(--text-muted)', marginBottom: '10px' }}>Live preview · full size</div>
                  <div style={{ width: 780, maxWidth: '100%', margin: '0 auto', boxShadow: '0 8px 28px rgba(0,0,0,0.35)', borderRadius: '10px' }}>
                    <InvoiceDocument inv={invSpecToDraft(specs[editIdx].spec)} />
                  </div>
                </div>
              </div>
            </Modal>
          )}
        </div>
      )}
    </Modal>
  );
}

// ── Invoice view drawer (rendered document + actions) ─────────
function InvoiceViewDrawer({ inv, open, onClose, onChange, onEdit, flash }) {
  const [payOpen, setPayOpen] = useOS(false);
  const [pay, setPay] = useOS({ amount: '', method: 'bank' });
  const [cnOpen, setCnOpen] = useOS(false);
  const [cn, setCn] = useOS({ amount: '', reason: '' });
  const [woOpen, setWoOpen] = useOS(false);
  const [woReason, setWoReason] = useOS('Bad debt — uncollectable');
  const [rfOpen, setRfOpen] = useOS(false);
  const [rf, setRf] = useOS({ amount: '', method: 'stripe', reason: '' });
  const [planOpen, setPlanOpen] = useOS(false);
  const [planCadence, setPlanCadence] = useOS('fortnightly');
  const [planFirst, setPlanFirst] = useOS('');
  if (!inv) return null;
  const st = paymentStatus(inv), bal = invoiceBalance(inv), rs = reminderState(inv), paidAmt = amountPaid(inv), rsched = reminderSchedule(inv);
  const owing = st === 'unpaid' || st === 'partial';
  const editable = !inv.sent && paidAmt === 0 && !inv.void && !inv.writeOff && (inv.creditNotes || []).length === 0;
  const planFirstDefault = toISODate(new Date((inv.issueDate || toISODate(INVOICE_TODAY)) + 'T00:00:00') > INVOICE_TODAY ? new Date(inv.issueDate + 'T00:00:00') : INVOICE_TODAY);
  const planFirstVal = planFirst || planFirstDefault;
  const cadenceOpts = planCadenceOptions(inv, new Date(planFirstVal + 'T00:00:00'));

  const afterPay = () => {
    if (paymentStatus(inv) === 'paid' && !inv.receiptSentAt) {
      inv.receiptSentAt = toISODate(INVOICE_TODAY);
      const r = invoiceRecipients(inv);
      logActivity({ action: 'Receipt sent', record: inv.number, detail: r.join(', ') });
      flash(`Payment received — receipt sent to ${r.join(', ') || 'customer'}`);
    } else { flash(`Payment of ${fmtAUD(pay.amount || bal)} recorded for ${inv.number}`); }
    onChange();
  };
  const doRecord = () => {
    const amt = parseFloat(pay.amount) || bal;
    const res = recordPayment(inv, { amount: amt, method: pay.method });
    logActivity({ action: 'Recorded payment', record: inv.number, detail: `${fmtAUD(amt)} · ${invPayLabel(pay.method)}${res.overpaidToCredit ? ` · ${fmtAUD(res.overpaidToCredit)} → credit` : ''}` });
    setPayOpen(false); afterPay();
  };
  const markFull = () => { recordPayment(inv, { amount: invoiceBalance(inv), method: inv.paymentMethod || 'stripe' }); logActivity({ action: 'Marked invoice paid', record: inv.number }); afterPay(); };
  const clearPay = () => { inv.payments = []; inv.receiptSentAt = null; logActivity({ action: 'Cleared payments', record: inv.number }); flash(`${inv.number} reset to unpaid`); onChange(); };
  const email = () => { const r = emailInvoice(inv); flash(r.ok ? `Invoice ${inv.number} emailed to ${r.recipients.join(', ')}` : `Couldn’t email — no contact on file. A task was created in Tasks.`); onChange(); };
  const voidIt = () => { if (window.confirm(`Void ${inv.number}? The number stays reserved and the invoice is excluded from totals.`)) { inv.void = true; logActivity({ action: 'Voided invoice', record: inv.number }); flash(`${inv.number} voided`); onChange(); } };
  const doCreditNote = () => { const amt = parseFloat(cn.amount) || bal; const note = issueCreditNote(inv, { amount: amt, reason: cn.reason }); logActivity({ action: 'Issued credit note', record: note.number, detail: `${fmtAUD(note.amount)} vs ${inv.number}${cn.reason ? ' · ' + cn.reason : ''}` }); setCnOpen(false); flash(`Credit note ${note.number} issued (${fmtAUD(note.amount)})`); onChange(); };
  const doWriteOff = () => { writeOffInvoice(inv, { reason: woReason }); logActivity({ action: 'Wrote off invoice (bad debt)', record: inv.number, detail: `${fmtAUD(bal)}${woReason ? ' · ' + woReason : ''}` }); setWoOpen(false); flash(`${inv.number} written off as bad debt`); onChange(); };
  const undoWriteOff = () => { reverseWriteOff(inv); logActivity({ action: 'Reversed write-off', record: inv.number }); flash(`${inv.number} write-off reversed`); onChange(); };
  const doRefund = () => { const amt = parseFloat(rf.amount) || paidAmt; refundInvoice(inv, { amount: amt, method: rf.method, reason: rf.reason }); logActivity({ action: 'Refund issued', record: inv.number, detail: `${fmtAUD(amt)} · ${invPayLabel(rf.method)}${rf.reason ? ' · ' + rf.reason : ''}` }); setRfOpen(false); flash(`Refund of ${fmtAUD(amt)} recorded for ${inv.number}`); onChange(); };
  const confirmPlan = () => { const opt = cadenceOpts.find(o => o.cadence === planCadence); if (!opt || !opt.fits) return; setPaymentPlan(inv, planCadence, new Date(planFirstVal + 'T00:00:00')); logActivity({ action: 'Set up payment plan', record: inv.number, detail: `${planCadence} · ${opt.count} × direct debit` }); setPlanOpen(false); flash(`Payment plan set — ${opt.count} ${planCadence} debits`); onChange(); };
  const payIns = ins => { recordPayment(inv, { amount: ins.amount, method: 'direct-debit', date: toISODate(INVOICE_TODAY) }); ins.status = 'paid'; logActivity({ action: 'Direct-debit instalment cleared', record: inv.number, detail: fmtAUD(ins.amount) }); afterPay(); };
  const dishonourIns = ins => { dishonourInstalment(inv, ins.id); logActivity({ action: 'Instalment dishonoured', record: inv.number, detail: `${fmtAUD(ins.amount)} · ${fmtAUD(INVOICE_SETTINGS.dishonourFee)} fee added` }); flash(`Dishonoured — ${fmtAUD(INVOICE_SETTINGS.dishonourFee)} fee added`); onChange(); };
  const cancelPlanA = () => { cancelPlan(inv); logActivity({ action: 'Cancelled payment plan', record: inv.number }); flash('Payment plan cancelled'); onChange(); };

  const abtn = { background: 'var(--bg-surface-muted)', border: '1px solid var(--border-default)', borderRadius: '6px', padding: '6px 11px', fontSize: '12px', color: 'var(--text-secondary)', cursor: 'pointer', fontFamily: 'inherit', whiteSpace: 'nowrap' };
  const lbl = { fontSize: '12px', color: 'var(--text-muted)', display: 'block', marginBottom: '5px' };
  const inp = { width: '100%', boxSizing: 'border-box', background: 'var(--bg-surface-muted)', border: '1px solid var(--border-default)', borderRadius: '4px', padding: '8px 10px', fontSize: '13px', color: 'var(--text-primary)', fontFamily: 'inherit', outline: 'none' };

  return (
    <Drawer open={open} onClose={onClose} title={inv.number} subtitle={`${inv.term} · ${fmtAUD(inv.totalInc)}${owing && bal > 0 ? ` · ${fmtAUD(bal)} due` : ''}`} width={820}>
      {/* Action bar — two rows: share (top) · manage (bottom) */}
      <div style={{ display: 'flex', flexDirection: 'column', gap: '10px', padding: '16px 0 14px', borderBottom: '1px solid var(--border-soft)', marginBottom: '12px' }}>
        {/* Row 1 — send & share */}
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px', alignItems: 'center' }}>
          <div style={{ width: 144 }}>
            <FormSelect value={inv.sendTo} onChange={v => { inv.sendTo = v; onChange(); }} buttonStyle={{ padding: '5px 9px' }}
              options={[{ value: 'parent', label: 'To: Parent' }, { value: 'student', label: 'To: Student' }, { value: 'both', label: 'To: Both' }]} />
          </div>
          <button style={abtn} onClick={email}>✉ {inv.sent ? 'Resend' : 'Email'}</button>
          <button style={abtn} onClick={() => printInvoiceDocument(inv)}>⤓ Download / Print</button>
        </div>
        {/* Row 2 — payments, adjustments & lifecycle */}
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px', alignItems: 'center' }}>
          {owing && <Button size="sm" variant="primary" onClick={() => { setPay({ amount: String(bal), method: inv.paymentMethod || 'stripe' }); setPayOpen(true); }}>Record payment</Button>}
          {owing && <button style={abtn} onClick={markFull}>Mark paid in full</button>}
          {st === 'paid' && <button style={abtn} onClick={clearPay}>Reset to unpaid</button>}
          {owing && !inv.plan && <button style={abtn} onClick={() => { const f = planCadenceOptions(inv, new Date(planFirstDefault + 'T00:00:00')).find(o => o.fits); setPlanCadence(f ? f.cadence : 'fortnightly'); setPlanFirst(planFirstDefault); setPlanOpen(true); }}>Set up plan</button>}
          {inv.plan && <button style={abtn} onClick={cancelPlanA}>Cancel plan</button>}
          {owing && <button style={abtn} onClick={() => { setCn({ amount: String(bal), reason: '' }); setCnOpen(true); }}>Credit note</button>}
          {owing && <button style={abtn} onClick={() => { setWoReason('Bad debt — uncollectable'); setWoOpen(true); }}>Write off</button>}
          {paidAmt > 0 && <button style={abtn} onClick={() => { setRf({ amount: String(paidAmt), method: inv.paymentMethod || 'stripe', reason: '' }); setRfOpen(true); }}>Refund</button>}
          {inv.writeOff && <button style={abtn} onClick={undoWriteOff}>Reverse write-off</button>}
          <button style={{ ...abtn, opacity: editable ? 1 : 0.45, cursor: editable ? 'pointer' : 'not-allowed' }} title={editable ? '' : 'Issued invoices are locked — issue a credit note or void instead'} onClick={() => editable && onEdit(inv)}>Edit</button>
          {!inv.void && <button style={{ ...abtn, marginLeft: 'auto', color: '#C05656', borderColor: 'rgba(192,86,86,0.4)' }} onClick={voidIt}>Void</button>}
        </div>
      </div>

      {/* Status / sent / reminder strip */}
      <div style={{ display: 'flex', flexWrap: 'wrap', gap: '16px', marginBottom: '16px', fontSize: '12px' }}>
        <span style={{ color: 'var(--text-muted)' }}>Sent: <strong style={{ color: inv.sent ? '#4A9B7E' : '#B8922A' }}>{inv.sent ? `Yes · ${fmtAUDate(inv.sentAt)}` : 'Not yet (draft)'}</strong></span>
        <span style={{ color: 'var(--text-muted)' }}>Next reminder: <strong style={{ color: rsched.next ? 'var(--text-primary)' : 'var(--text-muted)' }}>{rsched.next ? `${rsched.next.label} · ${fmtAUDate(rsched.next.date)}` : (rsched.enabled ? '—' : 'reminders off')}</strong></span>
        {inv.receiptSentAt && <span style={{ color: '#4A9B7E' }}>✓ Receipt sent {fmtAUDate(inv.receiptSentAt)}</span>}
        {inv.sendError && <span style={{ color: '#C05656', fontWeight: 600 }}>⚠ Email failed: {inv.sendError} — use ✉ to retry</span>}
        {inv.writeOff && <span style={{ color: '#7A7367', fontWeight: 600 }}>Written off {fmtAUDate(inv.writeOff.date)} · {inv.writeOff.reason}</span>}
      </div>

      {/* Reminder sequence */}
      {rsched.enabled && inv.sent && ['paid', 'void', 'written-off', 'credited', 'refunded'].indexOf(st) === -1 && (
        <div style={{ border: '1px solid var(--border-soft)', borderRadius: '10px', padding: '12px 14px', marginBottom: '16px' }}>
          <div style={{ fontSize: '12px', fontWeight: 600, color: 'var(--text-primary)', marginBottom: '8px' }}>Reminder sequence <span style={{ fontWeight: 400, color: 'var(--text-muted)' }}>· auto-cancels when paid</span></div>
          <div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
            {rsched.steps.map(step => {
              const sent = step.status === 'sent', next = step.status === 'next';
              const col = sent ? '#4A9B7E' : next ? '#D4A03A' : 'var(--text-muted)';
              return (
                <span key={step.id} title={`${step.tone} · ${step.channels.map(c => c === 'sms' ? 'SMS' : 'Email').join(' + ')}`} style={{ display: 'inline-flex', alignItems: 'center', gap: '6px', fontSize: '11px', fontWeight: next ? 600 : 500, color: col, background: next ? 'rgba(212,160,58,0.12)' : 'var(--bg-surface-muted)', border: `1px solid ${next ? 'rgba(212,160,58,0.4)' : 'var(--border-default)'}`, borderRadius: '5px', padding: '3px 9px' }}>
                  <span>{sent ? '✓' : next ? '➤' : '○'}</span>{step.label}<span style={{ color: 'var(--text-muted)', fontWeight: 400 }}>· {fmtAUDate(step.date)}</span>
                </span>
              );
            })}
          </div>
        </div>
      )}

      {/* Payment-plan management */}
      {inv.plan && (
        <div style={{ border: '1px solid var(--border-soft)', borderRadius: '10px', padding: '12px 14px', marginBottom: '16px' }}>
          <div style={{ fontSize: '12px', fontWeight: 600, color: 'var(--text-primary)', marginBottom: '8px' }}>Payment plan · <span style={{ textTransform: 'capitalize' }}>{inv.plan.cadence}</span> · direct debit</div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
            {inv.plan.instalments.map((ins, i) => (
              <div key={ins.id} style={{ display: 'flex', alignItems: 'center', gap: '10px', fontSize: '12px' }}>
                <span style={{ color: 'var(--text-muted)', width: 16 }}>{i + 1}</span>
                <span style={{ color: 'var(--text-secondary)', width: 104 }}>{fmtAUDate(ins.date)}</span>
                <span style={{ fontWeight: 600, color: 'var(--text-primary)', width: 76, fontVariantNumeric: 'tabular-nums' }}>{fmtAUD(ins.amount)}</span>
                {ins.status === 'scheduled'
                  ? <span style={{ display: 'flex', gap: '6px' }}><button style={{ ...abtn, padding: '3px 8px' }} onClick={() => payIns(ins)}>Record debit</button><button style={{ ...abtn, padding: '3px 8px' }} onClick={() => dishonourIns(ins)}>Dishonour</button></span>
                  : <span style={{ fontSize: '11px', fontWeight: 600, color: ins.status === 'paid' ? '#4A9B7E' : '#C05656' }}>{ins.status === 'paid' ? '✓ Cleared' : '⚠ Failed — fee added'}</span>}
              </div>
            ))}
          </div>
        </div>
      )}

      <InvoiceDocument inv={inv} />

      {payOpen && (
        <Modal open={true} onClose={() => setPayOpen(false)} title={`Record payment — ${inv.number}`}>
          <div style={{ display: 'flex', flexDirection: 'column', gap: '14px' }}>
            <div style={{ fontSize: '12px', color: 'var(--text-secondary)' }}>Balance due <strong style={{ color: 'var(--text-primary)' }}>{fmtAUD(bal)}</strong>. A part-payment leaves the rest outstanding; overpayment becomes account credit.</div>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px' }}>
              <div><label style={lbl}>Amount</label><input type="number" min="0" step="0.01" value={pay.amount} onChange={e => setPay({ ...pay, amount: e.target.value })} style={inp} /></div>
              <div><label style={lbl}>Method</label><FormSelect value={pay.method} onChange={v => setPay({ ...pay, method: v })} options={[{ value: 'stripe', label: 'Card (Stripe)' }, { value: 'direct-debit', label: 'Direct debit' }, { value: 'cash', label: 'Cash' }]} /></div>
            </div>
            <div style={{ display: 'flex', gap: '8px', justifyContent: 'flex-end' }}>
              <Button variant="secondary" onClick={() => setPayOpen(false)}>Cancel</Button>
              <Button variant="primary" onClick={doRecord}>Record {fmtAUD(parseFloat(pay.amount) || bal)}</Button>
            </div>
          </div>
        </Modal>
      )}

      {cnOpen && (
        <Modal open={true} onClose={() => setCnOpen(false)} title={`Credit note — ${inv.number}`}>
          <div style={{ display: 'flex', flexDirection: 'column', gap: '14px' }}>
            <div style={{ fontSize: '12px', color: 'var(--text-secondary)' }}>A credit note reduces or cancels this issued invoice (e.g. overcharge or partial withdrawal) and gets its own number. It doesn’t delete the invoice. Outstanding balance: <strong style={{ color: 'var(--text-primary)' }}>{fmtAUD(bal)}</strong>.</div>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1.4fr', gap: '12px' }}>
              <div><label style={lbl}>Amount</label><input type="number" min="0" step="0.01" value={cn.amount} onChange={e => setCn({ ...cn, amount: e.target.value })} style={inp} /></div>
              <div><label style={lbl}>Reason</label><input value={cn.reason} onChange={e => setCn({ ...cn, reason: e.target.value })} placeholder="e.g. Overcharged 1 lesson" style={inp} /></div>
            </div>
            <div style={{ display: 'flex', gap: '8px', justifyContent: 'flex-end' }}>
              <Button variant="secondary" onClick={() => setCnOpen(false)}>Cancel</Button>
              <Button variant="primary" onClick={doCreditNote}>Issue credit note</Button>
            </div>
          </div>
        </Modal>
      )}

      {woOpen && (
        <Modal open={true} onClose={() => setWoOpen(false)} title={`Write off — ${inv.number}`}>
          <div style={{ display: 'flex', flexDirection: 'column', gap: '14px' }}>
            <div style={{ fontSize: '12px', color: 'var(--text-secondary)' }}>Write off the outstanding <strong style={{ color: 'var(--text-primary)' }}>{fmtAUD(bal)}</strong> as a bad debt (genuinely uncollectable). It’s removed from outstanding totals and tracked separately. You can reverse it later.</div>
            <div><label style={lbl}>Reason</label><input value={woReason} onChange={e => setWoReason(e.target.value)} style={inp} /></div>
            <div style={{ display: 'flex', gap: '8px', justifyContent: 'flex-end' }}>
              <Button variant="secondary" onClick={() => setWoOpen(false)}>Cancel</Button>
              <Button variant="primary" onClick={doWriteOff}>Write off as bad debt</Button>
            </div>
          </div>
        </Modal>
      )}

      {rfOpen && (
        <Modal open={true} onClose={() => setRfOpen(false)} title={`Refund — ${inv.number}`}>
          <div style={{ display: 'flex', flexDirection: 'column', gap: '14px' }}>
            <div style={{ fontSize: '12px', color: 'var(--text-secondary)' }}>Record money returned to the customer. Paid to date: <strong style={{ color: 'var(--text-primary)' }}>{fmtAUD(paidAmt)}</strong>. (Usually pair with a credit note if you’re cancelling the charge.)</div>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px' }}>
              <div><label style={lbl}>Amount</label><input type="number" min="0" step="0.01" value={rf.amount} onChange={e => setRf({ ...rf, amount: e.target.value })} style={inp} /></div>
              <div><label style={lbl}>Method</label><FormSelect value={rf.method} onChange={v => setRf({ ...rf, method: v })} options={[{ value: 'stripe', label: 'Card (Stripe)' }, { value: 'direct-debit', label: 'Direct debit' }, { value: 'cash', label: 'Cash' }]} /></div>
            </div>
            <div><label style={lbl}>Reason</label><input value={rf.reason} onChange={e => setRf({ ...rf, reason: e.target.value })} placeholder="e.g. Withdrew after week 2" style={inp} /></div>
            <div style={{ display: 'flex', gap: '8px', justifyContent: 'flex-end' }}>
              <Button variant="secondary" onClick={() => setRfOpen(false)}>Cancel</Button>
              <Button variant="primary" onClick={doRefund}>Record refund</Button>
            </div>
          </div>
        </Modal>
      )}

      {planOpen && (
        <Modal open={true} onClose={() => setPlanOpen(false)} title={`Set up payment plan — ${inv.number}`}>
          <div style={{ display: 'flex', flexDirection: 'column', gap: '14px' }}>
            <div style={{ fontSize: '12px', color: 'var(--text-secondary)' }}>Direct-debit instalments for the outstanding <strong style={{ color: 'var(--text-primary)' }}>{fmtAUD(bal)}</strong>. The plan must finish on/before the term ends (<strong style={{ color: 'var(--text-primary)' }}>{fmtAUDate(termEndDate(inv.term))}</strong>).</div>
            <div><label style={lbl}>First debit date</label><input type="date" value={planFirstVal} onChange={e => setPlanFirst(e.target.value)} style={{ ...inp, colorScheme: 'dark' }} /></div>
            <div>
              <label style={lbl}>Cadence</label>
              <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
                {cadenceOpts.map(o => (
                  <button key={o.cadence} disabled={!o.fits} onClick={() => setPlanCadence(o.cadence)} style={{ textAlign: 'left', display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '10px 12px', borderRadius: '8px', cursor: o.fits ? 'pointer' : 'not-allowed', fontFamily: 'inherit', background: planCadence === o.cadence && o.fits ? 'rgba(31,77,61,0.15)' : 'var(--bg-surface-muted)', border: `1px solid ${planCadence === o.cadence && o.fits ? 'rgba(31,77,61,0.5)' : 'var(--border-default)'}`, opacity: o.fits ? 1 : 0.5 }}>
                    <span style={{ fontSize: '13px', fontWeight: 600, color: 'var(--text-primary)', textTransform: 'capitalize' }}>{o.cadence}</span>
                    <span style={{ fontSize: '12px', color: 'var(--text-secondary)' }}>{o.fits ? `${o.count} × ${fmtAUD(o.per)} · last ${fmtAUDate(o.last)}` : 'Not enough time before term ends'}</span>
                  </button>
                ))}
              </div>
            </div>
            <div style={{ fontSize: '11px', color: 'var(--text-muted)' }}>Direct debit only. A failed payment incurs a {fmtAUD(INVOICE_SETTINGS.dishonourFee)} dishonour fee, passed on to the parent.</div>
            <div style={{ display: 'flex', gap: '8px', justifyContent: 'flex-end' }}>
              <Button variant="secondary" onClick={() => setPlanOpen(false)}>Cancel</Button>
              <Button variant="primary" disabled={!(cadenceOpts.find(o => o.cadence === planCadence) || {}).fits} onClick={confirmPlan}>Set up plan</Button>
            </div>
          </div>
        </Modal>
      )}
    </Drawer>
  );
}

// ── Customer adjustment notes (credit notes) — ReckonOne's term ──
// Single source of truth for adjustment notes; a lean register + a pick-an-invoice
// issue flow. Notes are permanent records (no delete) and reduce the invoice balance.
function AdjustmentNotesView({ flash, notify, newOpen, onCloseNew }) {
  const [q, setQ] = useOS('');
  const [selKey, setSelKey] = useOS(null);
  const [drawerOpen, setDrawerOpen] = useOS(false);
  const [pickId, setPickId] = useOS(null);
  const [pickQ, setPickQ] = useOS('');
  const [amount, setAmount] = useOS('');
  const [reason, setReason] = useOS('');

  // Every adjustment note across all invoices, newest number first.
  const all = [];
  SAMPLE_INVOICES.forEach(inv => (inv.creditNotes || []).forEach(note => all.push({ note, inv })));
  all.sort((a, b) => b.note.number.localeCompare(a.note.number));
  const total = all.reduce((a, x) => a + x.note.amount, 0);

  const query = q.trim().toLowerCase();
  const rows = all.filter(({ note, inv }) => {
    if (!query) return true;
    const s = invoicePrimaryStudent(inv) || {}, p = invoiceBillTo(inv) || {};
    return `${note.number} ${inv.number} ${inv.customerNo} ${s.firstName || ''} ${s.lastName || ''} ${p.firstName || ''} ${p.lastName || ''} ${note.reason || ''}`.toLowerCase().includes(query);
  });
  const sel = selKey ? all.find(x => `${x.inv.id}::${x.note.id}` === selKey) : null;

  // New-note flow — only invoices with credit-able room left can be adjusted.
  const creditable = SAMPLE_INVOICES.filter(i => !i.void && Math.round((i.totalInc - creditNotesTotal(i)) * 100) / 100 > 0.005);
  const pickQuery = pickQ.trim().toLowerCase();
  const pickList = creditable.filter(inv => {
    if (!pickQuery) return true;
    const s = invoicePrimaryStudent(inv) || {}, p = invoiceBillTo(inv) || {};
    return `${inv.number} ${inv.customerNo} ${s.firstName || ''} ${s.lastName || ''} ${p.firstName || ''} ${p.lastName || ''}`.toLowerCase().includes(pickQuery);
  });
  const pickInv = SAMPLE_INVOICES.find(i => i.id === pickId);
  const maxCredit = pickInv ? Math.round((pickInv.totalInc - creditNotesTotal(pickInv)) * 100) / 100 : 0;

  const closeNew = () => { setPickId(null); setPickQ(''); setAmount(''); setReason(''); onCloseNew(); };
  const commit = () => {
    if (!pickInv) return;
    const amt = Math.min(parseFloat(amount) || maxCredit, maxCredit);
    const note = issueCreditNote(pickInv, { amount: amt, reason });
    logActivity({ action: 'Issued adjustment note', record: note.number, detail: `${fmtAUD(note.amount)} vs ${pickInv.number}${reason ? ' · ' + reason : ''}` });
    flash(`Adjustment note ${note.number} issued (${fmtAUD(note.amount)})`);
    closeNew(); notify();
  };

  const COLS = '128px 104px minmax(150px,1.4fr) 116px minmax(160px,1.6fr) 104px 96px';
  const hcell = { fontSize: '11px', fontWeight: 500, color: 'var(--text-secondary)', letterSpacing: '0.05em', textTransform: 'uppercase' };
  const lbl = { fontSize: '12px', color: 'var(--text-muted)', display: 'block', marginBottom: '5px' };
  const inp = { width: '100%', boxSizing: 'border-box', background: 'var(--bg-surface-muted)', border: '1px solid var(--border-default)', borderRadius: '4px', padding: '8px 10px', fontSize: '13px', color: 'var(--text-primary)', fontFamily: 'inherit', outline: 'none' };

  return (
    <div>
      {/* Summary strip */}
      <div style={{ display: 'flex', gap: '20px', flexWrap: 'wrap', alignItems: 'center', justifyContent: 'space-between', marginBottom: '16px', background: 'var(--bg-surface)', border: '1px solid var(--border-soft)', borderRadius: '10px', padding: '12px 16px' }}>
        <div style={{ display: 'flex', gap: '28px', flexWrap: 'wrap' }}>
          <div style={{ display: 'flex', flexDirection: 'column' }}>
            <span style={{ fontSize: '11px', color: 'var(--text-muted)', letterSpacing: '0.04em', textTransform: 'uppercase' }}>Adjustment notes</span>
            <span style={{ fontSize: '18px', fontWeight: 700, color: 'var(--text-primary)', fontVariantNumeric: 'tabular-nums' }}>{all.length}</span>
          </div>
          <div style={{ display: 'flex', flexDirection: 'column' }}>
            <span style={{ fontSize: '11px', color: 'var(--text-muted)', letterSpacing: '0.04em', textTransform: 'uppercase' }}>Total credited</span>
            <span style={{ fontSize: '18px', fontWeight: 700, color: '#4A9B7E', fontVariantNumeric: 'tabular-nums' }}>{fmtAUD(total)}</span>
          </div>
        </div>
        <div style={{ fontSize: '11px', color: 'var(--text-muted)', maxWidth: 300, textAlign: 'right' }}>Synced to ReckonOne as customer adjustment notes.</div>
      </div>

      {/* Search */}
      <div style={{ display: 'flex', marginBottom: '16px' }}>
        <div style={{ position: 'relative', marginLeft: 'auto', minWidth: 260 }}>
          <Icon name="search" size={14} color="var(--text-muted)" style={{ position: 'absolute', left: 10, top: '50%', transform: 'translateY(-50%)' }} />
          <input value={q} onChange={e => setQ(e.target.value)} placeholder="Search #, invoice, student, reason…" style={{ width: '100%', boxSizing: 'border-box', background: 'var(--bg-surface-muted)', border: '1px solid var(--border-default)', borderRadius: '6px', padding: '7px 12px 7px 32px', fontSize: '13px', color: 'var(--text-primary)', fontFamily: 'inherit', outline: 'none' }} />
        </div>
      </div>

      {/* Register */}
      {all.length === 0 ? (
        <div style={{ border: '1px solid var(--border-default)', borderRadius: '10px', background: 'var(--bg-surface-muted)', padding: '8px' }}>
          <div style={{ border: '1px solid var(--border-soft)', borderRadius: '8px', background: 'var(--bg-surface)', padding: '48px 16px', textAlign: 'center' }}>
            <div style={{ fontSize: '13px', color: 'var(--text-secondary)', fontWeight: 500 }}>No adjustment notes yet</div>
            <div style={{ fontSize: '12px', color: 'var(--text-muted)', marginTop: '6px' }}>Issue one against an invoice to correct an overcharge or partial withdrawal.</div>
          </div>
        </div>
      ) : (
        <div style={{ border: '1px solid var(--border-default)', borderRadius: '10px', background: 'var(--bg-surface-muted)', padding: '8px' }}>
          <div style={{ overflow: 'auto', border: '1px solid var(--border-soft)', borderRadius: '8px', background: 'var(--bg-surface)' }}>
            <div style={{ minWidth: 'max-content' }}>
              <div style={{ position: 'sticky', top: 0, zIndex: 5, display: 'grid', gridTemplateColumns: COLS, columnGap: '14px', background: 'var(--bg-surface-muted)', padding: '9px 16px', borderBottom: '1px solid var(--border-soft)' }}>
                {['#', 'Date', 'Customer', 'Against', 'Reason', 'Amount', 'Status'].map(h => <span key={h} style={hcell}>{h}</span>)}
              </div>
              {rows.map(({ note, inv }, i) => {
                const s = invoicePrimaryStudent(inv) || {};
                return (
                  <div key={note.id} onClick={() => { setSelKey(`${inv.id}::${note.id}`); setDrawerOpen(true); }}
                    style={{ display: 'grid', gridTemplateColumns: COLS, columnGap: '14px', padding: '10px 16px', alignItems: 'center', cursor: 'pointer', background: 'var(--bg-surface)', borderBottom: i < rows.length - 1 ? '1px solid var(--border-soft)' : 'none' }}
                    onMouseEnter={e => e.currentTarget.style.background = 'var(--bg-hover)'} onMouseLeave={e => e.currentTarget.style.background = 'var(--bg-surface)'}>
                    <span style={{ fontSize: '12px', fontFamily: 'ui-monospace, monospace', color: 'var(--text-secondary)', whiteSpace: 'nowrap' }}>{note.number}</span>
                    <span style={{ fontSize: '12px', color: 'var(--text-muted)', whiteSpace: 'nowrap' }}>{fmtAUDate(note.date)}</span>
                    <div style={{ minWidth: 0 }}>
                      <div style={{ fontSize: '13px', fontWeight: 500, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{s.firstName} {s.lastName}</div>
                      <div style={{ fontSize: '11px', color: 'var(--text-muted)', fontFamily: 'ui-monospace, monospace' }}>{inv.customerNo}</div>
                    </div>
                    <span style={{ fontSize: '12px', fontFamily: 'ui-monospace, monospace', color: 'var(--text-secondary)', whiteSpace: 'nowrap' }}>{inv.number}</span>
                    <span style={{ fontSize: '12px', color: 'var(--text-secondary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{note.reason || '—'}</span>
                    <span style={{ fontSize: '13px', fontWeight: 600, color: '#4A9B7E', fontVariantNumeric: 'tabular-nums' }}>−{fmtAUD(note.amount)}</span>
                    <span><span style={{ display: 'inline-flex', alignItems: 'center', fontSize: '11px', fontWeight: 600, color: '#4A9B7E', background: 'rgba(74,155,126,0.12)', border: '1px solid rgba(74,155,126,0.3)', borderRadius: '4px', padding: '2px 8px' }}>Applied</span></span>
                  </div>
                );
              })}
              {rows.length === 0 && <div style={{ padding: '40px', textAlign: 'center', fontSize: '13px', color: 'var(--text-muted)' }}>No adjustment notes match your search</div>}
            </div>
          </div>
        </div>
      )}

      {/* Detail drawer */}
      <Drawer open={drawerOpen && !!sel} onClose={() => setDrawerOpen(false)} title={sel ? sel.note.number : ''} subtitle={sel ? `Adjustment vs ${sel.inv.number} · ${fmtAUD(sel.note.amount)}` : ''} width={760}>
        {sel && (
          <>
            <div style={{ display: 'flex', gap: '8px', alignItems: 'center', padding: '16px 0 14px', borderBottom: '1px solid var(--border-soft)', marginBottom: '16px' }}>
              <button style={{ background: 'var(--bg-surface-muted)', border: '1px solid var(--border-default)', borderRadius: '6px', padding: '6px 11px', fontSize: '12px', color: 'var(--text-secondary)', cursor: 'pointer', fontFamily: 'inherit' }} onClick={() => printCreditNote(sel.note, sel.inv)}>⤓ Download / Print</button>
            </div>
            <CreditNoteDocument note={sel.note} inv={sel.inv} />
          </>
        )}
      </Drawer>

      {/* New adjustment note */}
      {newOpen && (
        <Modal open={true} onClose={closeNew} title="New adjustment note" width={580}>
          {!pickInv ? (
            <div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
              <div style={{ fontSize: '12px', color: 'var(--text-secondary)' }}>Pick the invoice to adjust. The note reduces that invoice's balance and is recorded against it.</div>
              <div style={{ position: 'relative' }}>
                <Icon name="search" size={14} color="var(--text-muted)" style={{ position: 'absolute', left: 10, top: '50%', transform: 'translateY(-50%)' }} />
                <input value={pickQ} onChange={e => setPickQ(e.target.value)} placeholder="Search invoice #, student, parent…" autoFocus style={{ ...inp, padding: '8px 10px 8px 32px' }} />
              </div>
              <div style={{ maxHeight: 320, overflowY: 'auto', border: '1px solid var(--border-soft)', borderRadius: '8px' }}>
                {pickList.length === 0 && <div style={{ padding: '28px', textAlign: 'center', fontSize: '13px', color: 'var(--text-muted)' }}>No invoices available to adjust</div>}
                {pickList.map((inv, i) => {
                  const s = invoicePrimaryStudent(inv) || {}, p = invoiceBillTo(inv);
                  return (
                    <div key={inv.id} onClick={() => { setPickId(inv.id); setAmount(String(Math.round((inv.totalInc - creditNotesTotal(inv)) * 100) / 100)); }}
                      style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '12px', padding: '10px 12px', cursor: 'pointer', borderBottom: i < pickList.length - 1 ? '1px solid var(--border-soft)' : 'none' }}
                      onMouseEnter={e => e.currentTarget.style.background = 'var(--bg-hover)'} onMouseLeave={e => e.currentTarget.style.background = 'transparent'}>
                      <div style={{ minWidth: 0 }}>
                        <div style={{ fontSize: '13px', fontWeight: 500, color: 'var(--text-primary)' }}>{s.firstName} {s.lastName} <span style={{ fontFamily: 'ui-monospace, monospace', fontSize: '11px', color: 'var(--text-muted)' }}>{inv.number}</span></div>
                        <div style={{ fontSize: '11px', color: 'var(--text-muted)' }}>{p ? `${p.firstName} ${p.lastName}` : '—'} · {inv.term}</div>
                      </div>
                      <span style={{ fontSize: '13px', fontWeight: 600, color: 'var(--text-primary)', fontVariantNumeric: 'tabular-nums' }}>{fmtAUD(inv.totalInc)}</span>
                    </div>
                  );
                })}
              </div>
            </div>
          ) : (
            <div style={{ display: 'flex', flexDirection: 'column', gap: '14px' }}>
              <div style={{ background: 'var(--bg-surface-muted)', border: '1px solid var(--border-soft)', borderRadius: '8px', padding: '12px 14px', display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: '12px' }}>
                <div style={{ minWidth: 0 }}>
                  <div style={{ fontSize: '13px', fontWeight: 600, color: 'var(--text-primary)' }}>{(invoicePrimaryStudent(pickInv) || {}).firstName} {(invoicePrimaryStudent(pickInv) || {}).lastName} · {pickInv.number}</div>
                  <div style={{ fontSize: '11px', color: 'var(--text-muted)' }}>Invoice total {fmtAUD(pickInv.totalInc)} · max credit {fmtAUD(maxCredit)}</div>
                </div>
                <button style={{ background: 'none', border: 'none', color: 'var(--text-muted)', fontSize: '12px', cursor: 'pointer', fontFamily: 'inherit', textDecoration: 'underline', whiteSpace: 'nowrap' }} onClick={() => setPickId(null)}>Change</button>
              </div>
              <div style={{ display: 'grid', gridTemplateColumns: '1fr 1.5fr', gap: '12px' }}>
                <div><label style={lbl}>Amount</label><input type="number" min="0" max={maxCredit} step="0.01" value={amount} onChange={e => setAmount(e.target.value)} style={inp} /></div>
                <div><label style={lbl}>Reason</label><input value={reason} onChange={e => setReason(e.target.value)} placeholder="e.g. Overcharged 1 lesson" style={inp} /></div>
              </div>
              {parseFloat(amount) > maxCredit && <div style={{ fontSize: '11px', color: '#C05656' }}>Amount can't exceed the max credit ({fmtAUD(maxCredit)}) — it will be capped.</div>}
              <div style={{ display: 'flex', gap: '8px', justifyContent: 'flex-end' }}>
                <Button variant="secondary" onClick={closeNew}>Cancel</Button>
                <Button variant="primary" onClick={commit}>Issue adjustment note</Button>
              </div>
            </div>
          )}
        </Modal>
      )}
    </div>
  );
}

// Credit claims — central review list for absence-credit requests. One credit per term is the
// rule, so over-limit claims are flagged; so are claims where attendance shows the student present.
// Approve → credit + approval email; Reject → reason (default or custom) emailed to the parent.
function CreditClaimsView({ flash, notify }) {
  const [rejecting, setRejecting] = useOS(null);   // claim id being rejected
  const [rejReason, setRejReason] = useOS(CLAIM_REJECT_REASONS[0]);
  const [rejCustom, setRejCustom] = useOS('');

  const pending = SAMPLE_CREDIT_CLAIMS.filter(c => c.status === 'pending');
  const decided = SAMPLE_CREDIT_CLAIMS.filter(c => c.status !== 'pending');
  const flagged = pending.filter(c => creditClaimAlreadyThisTerm(c) || studentAttendedWeek(c.studentId, c.term, c.week) === true).length;
  const parentEmailOf = c => { const s = SAMPLE_STUDENTS.find(x => x.id === c.studentId); const p = s && SAMPLE_PARENTS.find(pp => pp.id === s.parentId); return p ? p.email : ''; };

  const approve = c => {
    c.status = 'approved'; c.decidedAt = toISODate(INVOICE_TODAY); c.decisionReason = '';
    const s = SAMPLE_STUDENTS.find(x => x.id === c.studentId);
    if (s) { s.creditBalance = Math.round(((s.creditBalance || 0) + c.amount) * 100) / 100; s.creditClaimedTerm = c.term; }
    logActivity({ action: 'Approved credit claim', record: s ? `${s.firstName} ${s.lastName}` : '', detail: `${fmtAUD(c.amount)} · approval email sent` });
    flash(`Approved — ${fmtAUD(c.amount)} credited; approval email sent to ${parentEmailOf(c) || 'customer'}`);
    notify();
  };
  const confirmReject = c => {
    const reason = rejCustom.trim() || rejReason;
    c.status = 'rejected'; c.decidedAt = toISODate(INVOICE_TODAY); c.decisionReason = reason;
    const s = SAMPLE_STUDENTS.find(x => x.id === c.studentId);
    logActivity({ action: 'Declined credit claim', record: s ? `${s.firstName} ${s.lastName}` : '', detail: `${reason} · email sent` });
    flash(`Declined — email sent to ${parentEmailOf(c) || 'customer'}: “${reason}”`);
    setRejecting(null); setRejCustom(''); notify();
  };

  const metric = (label, value, color) => (
    <div style={{ display: 'flex', flexDirection: 'column' }}>
      <span style={{ fontSize: '11px', color: 'var(--text-muted)', letterSpacing: '0.04em', textTransform: 'uppercase' }}>{label}</span>
      <span style={{ fontSize: '18px', fontWeight: 700, color, fontVariantNumeric: 'tabular-nums' }}>{value}</span>
    </div>
  );
  const aBtn = { background: 'var(--bg-surface-muted)', border: '1px solid var(--border-default)', borderRadius: '6px', padding: '6px 12px', fontSize: '12px', color: 'var(--text-secondary)', cursor: 'pointer', fontFamily: 'inherit' };
  const inp = { width: '100%', boxSizing: 'border-box', background: 'var(--bg-surface-muted)', border: '1px solid var(--border-default)', borderRadius: '4px', padding: '7px 10px', fontSize: '13px', color: 'var(--text-primary)', fontFamily: 'inherit', outline: 'none' };
  const flag = (txt, col) => <span style={{ display: 'inline-flex', alignItems: 'center', gap: '5px', fontSize: '11px', fontWeight: 600, color: col, background: `color-mix(in srgb, ${col} 12%, transparent)`, border: `1px solid color-mix(in srgb, ${col} 34%, transparent)`, borderRadius: '5px', padding: '3px 9px' }}>⚠ {txt}</span>;

  const claimCard = c => {
    const s = SAMPLE_STUDENTS.find(x => x.id === c.studentId);
    const attended = studentAttendedWeek(c.studentId, c.term, c.week) === true;
    const overLimit = creditClaimAlreadyThisTerm(c);
    const danger = attended || overLimit;
    const isRej = rejecting === c.id;
    return (
      <div key={c.id} style={{ background: 'var(--bg-surface)', border: `1px solid ${danger ? 'rgba(192,86,86,0.4)' : 'var(--border-soft)'}`, borderRadius: '10px', padding: '14px 16px', marginBottom: '12px' }}>
        <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: '12px', flexWrap: 'wrap' }}>
          <div style={{ display: 'flex', gap: '10px', alignItems: 'center', minWidth: 0 }}>
            {s && <Avatar name={`${s.firstName} ${s.lastName}`} size={34} />}
            <div style={{ minWidth: 0 }}>
              <div style={{ fontSize: '14px', fontWeight: 600, color: 'var(--text-primary)' }}>{s ? `${s.firstName} ${s.lastName}` : 'Unknown'} <span style={{ fontSize: '11px', color: 'var(--text-muted)', fontFamily: 'ui-monospace, monospace' }}>{s ? customerNo(s) : ''}</span></div>
              <div style={{ fontSize: '12px', color: 'var(--text-muted)' }}>{c.term} · Week {c.week} · {c.reason}</div>
            </div>
          </div>
          <span style={{ fontSize: '15px', fontWeight: 700, color: '#4A9B7E', fontVariantNumeric: 'tabular-nums' }}>{fmtAUD(c.amount)}</span>
        </div>
        {danger && (
          <div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap', marginTop: '10px' }}>
            {overLimit && flag('Already has a credit this term (limit is 1)', '#C05656')}
            {attended && flag(`${s ? s.firstName : 'Student'} marked present that week`, '#C05656')}
          </div>
        )}
        {!isRej ? (
          <div style={{ display: 'flex', gap: '8px', marginTop: '12px' }}>
            <Button size="sm" variant="primary" onClick={() => approve(c)}>Approve &amp; email</Button>
            <button style={aBtn} onClick={() => { setRejecting(c.id); setRejReason(overLimit ? CLAIM_REJECT_REASONS[0] : (attended ? CLAIM_REJECT_REASONS[1] : CLAIM_REJECT_REASONS[0])); setRejCustom(''); }}>Decline…</button>
          </div>
        ) : (
          <div style={{ marginTop: '12px', borderTop: '1px solid var(--border-soft)', paddingTop: '12px', display: 'flex', flexDirection: 'column', gap: '10px' }}>
            <div style={{ fontSize: '12px', color: 'var(--text-secondary)' }}>Pick a reason (or write a custom note) — it's emailed to {parentEmailOf(c) || 'the parent'}.</div>
            <FormSelect value={rejReason} onChange={setRejReason} options={CLAIM_REJECT_REASONS.map(r => ({ value: r, label: r }))} />
            <input value={rejCustom} onChange={e => setRejCustom(e.target.value)} placeholder="Custom note (optional — overrides the dropdown)" style={inp} />
            <div style={{ display: 'flex', gap: '8px', justifyContent: 'flex-end' }}>
              <button style={aBtn} onClick={() => { setRejecting(null); setRejCustom(''); }}>Cancel</button>
              <Button size="sm" variant="primary" onClick={() => confirmReject(c)}>Decline &amp; email</Button>
            </div>
          </div>
        )}
      </div>
    );
  };

  return (
    <div>
      <div style={{ display: 'flex', gap: '20px', flexWrap: 'wrap', alignItems: 'center', justifyContent: 'space-between', marginBottom: '16px', background: 'var(--bg-surface)', border: '1px solid var(--border-soft)', borderRadius: '10px', padding: '12px 16px' }}>
        <div style={{ display: 'flex', gap: '28px', flexWrap: 'wrap' }}>
          {metric('Awaiting review', pending.length, pending.length ? '#D4A03A' : '#4A9B7E')}
          {metric('Need a closer look', flagged, flagged ? '#C05656' : 'var(--text-primary)')}
          {metric('Decided', decided.length, 'var(--text-primary)')}
        </div>
        <div style={{ fontSize: '11px', color: 'var(--text-muted)', maxWidth: 320, textAlign: 'right' }}>One credit per student per term. Over-limit or present-that-week claims are flagged. The parent is emailed on approve or decline.</div>
      </div>

      {pending.length === 0
        ? <div style={{ border: '1px solid var(--border-default)', borderRadius: '10px', background: 'var(--bg-surface-muted)', padding: '8px' }}><div style={{ border: '1px solid var(--border-soft)', borderRadius: '8px', background: 'var(--bg-surface)', padding: '48px 16px', textAlign: 'center' }}><div style={{ fontSize: '13px', color: '#4A9B7E', fontWeight: 600 }}>✓ No claims awaiting review</div><div style={{ fontSize: '12px', color: 'var(--text-muted)', marginTop: '6px' }}>New requests from the “Request a Lesson Credit” form land here.</div></div></div>
        : <div>{pending.map(claimCard)}</div>}

      {decided.length > 0 && (
        <div style={{ marginTop: '8px' }}>
          <div style={{ fontSize: '11px', fontWeight: 600, letterSpacing: '0.05em', textTransform: 'uppercase', color: 'var(--text-muted)', margin: '8px 0 10px' }}>Recently decided</div>
          {decided.map(c => { const s = SAMPLE_STUDENTS.find(x => x.id === c.studentId); const ok = c.status === 'approved'; return (
            <div key={c.id} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '12px', padding: '10px 14px', border: '1px solid var(--border-soft)', borderRadius: '8px', background: 'var(--bg-surface)', marginBottom: '8px' }}>
              <div style={{ minWidth: 0 }}>
                <div style={{ fontSize: '13px', color: 'var(--text-primary)' }}>{s ? `${s.firstName} ${s.lastName}` : '—'} <span style={{ color: 'var(--text-muted)' }}>· {c.term} · Wk {c.week}</span></div>
                {!ok && c.decisionReason && <div style={{ fontSize: '11px', color: 'var(--text-muted)' }}>Reason emailed: {c.decisionReason}</div>}
              </div>
              <span style={{ fontSize: '11px', fontWeight: 600, color: ok ? '#4A9B7E' : '#C05656', background: ok ? 'rgba(74,155,126,0.12)' : 'rgba(192,86,86,0.12)', border: `1px solid ${ok ? 'rgba(74,155,126,0.3)' : 'rgba(192,86,86,0.3)'}`, borderRadius: '4px', padding: '2px 8px' }}>{ok ? `Approved · ${fmtAUD(c.amount)}` : 'Declined'}</span>
            </div>
          ); })}
        </div>
      )}
    </div>
  );
}

// Billing gaps (#1) — enrolled students this term with no invoice, or an invoice created but
// never emailed. Each row can be cleared (dismissed) if the gap is intentional.
function BillingGapsView({ flash, notify, onNewFor }) {
  const term = defaultBillingTerm(INVOICE_TODAY);
  const uninv = uninvoicedStudents().list;
  const unsent = unsentInvoices();
  const total = uninv.length + unsent.length;

  const clearStudent = s => { UNINVOICED_DISMISSED.add(s.id); flash(`${s.firstName} ${s.lastName} cleared from billing gaps`); notify(); };
  const clearInvoice = inv => { inv.gapDismissed = true; flash(`${inv.number} cleared from billing gaps`); notify(); };
  const emailNow = inv => { const r = emailInvoice(inv); flash(r.ok ? `${inv.number} emailed to ${r.recipients.join(', ')}` : `Couldn’t email ${inv.number} — no contact on file (task created)`); notify(); };

  const metric = (label, value, color) => (
    <div style={{ display: 'flex', flexDirection: 'column' }}>
      <span style={{ fontSize: '11px', color: 'var(--text-muted)', letterSpacing: '0.04em', textTransform: 'uppercase' }}>{label}</span>
      <span style={{ fontSize: '18px', fontWeight: 700, color, fontVariantNumeric: 'tabular-nums' }}>{value}</span>
    </div>
  );
  const card = { border: '1px solid var(--border-default)', borderRadius: '10px', background: 'var(--bg-surface-muted)', padding: '8px', marginBottom: '16px' };
  const inner = { border: '1px solid var(--border-soft)', borderRadius: '8px', background: 'var(--bg-surface)', maxHeight: 360, overflow: 'auto' };
  const secHead = { fontSize: '12px', fontWeight: 600, color: 'var(--text-primary)', padding: '10px 14px', borderBottom: '1px solid var(--border-soft)', background: 'var(--bg-surface-muted)', position: 'sticky', top: 0, zIndex: 2 };
  const pill = (txt, col) => <span style={{ fontSize: '11px', fontWeight: 600, color: col, background: `color-mix(in srgb, ${col} 14%, transparent)`, border: `1px solid color-mix(in srgb, ${col} 32%, transparent)`, borderRadius: '4px', padding: '2px 8px', whiteSpace: 'nowrap' }}>{txt}</span>;
  const aBtn = { background: 'var(--bg-surface-muted)', border: '1px solid var(--border-default)', borderRadius: '6px', padding: '5px 11px', fontSize: '12px', color: 'var(--text-secondary)', cursor: 'pointer', fontFamily: 'inherit', whiteSpace: 'nowrap' };

  return (
    <div>
      <div style={{ display: 'flex', gap: '20px', flexWrap: 'wrap', alignItems: 'center', justifyContent: 'space-between', marginBottom: '16px', background: 'var(--bg-surface)', border: '1px solid var(--border-soft)', borderRadius: '10px', padding: '12px 16px' }}>
        <div style={{ display: 'flex', gap: '28px', flexWrap: 'wrap' }}>
          {metric('Action needed', total, total ? '#C05656' : '#4A9B7E')}
          {metric('No invoice yet', uninv.length, 'var(--text-primary)')}
          {metric('Created, not emailed', unsent.length, 'var(--text-primary)')}
        </div>
        <div style={{ fontSize: '11px', color: 'var(--text-muted)', maxWidth: 320, textAlign: 'right' }}>Enrolled students for {term}. Clear any row that's intentionally left (e.g. on hold, billed elsewhere).</div>
      </div>

      {total === 0 ? (
        <div style={card}><div style={{ ...inner, maxHeight: 'none', padding: '48px 16px', textAlign: 'center' }}>
          <div style={{ fontSize: '13px', color: '#4A9B7E', fontWeight: 600 }}>✓ All caught up</div>
          <div style={{ fontSize: '12px', color: 'var(--text-muted)', marginTop: '6px' }}>Every enrolled student has an invoice this term, and all invoices have been emailed.</div>
        </div></div>
      ) : (<>
        {uninv.length > 0 && (
          <div style={card}>
            <div style={inner}>
              <div style={secHead}>No invoice for {term} <span style={{ color: 'var(--text-muted)', fontWeight: 400 }}>· {uninv.length}</span></div>
              {uninv.map((s, i) => (
                <div key={s.id} style={{ display: 'grid', gridTemplateColumns: 'minmax(160px,1.4fr) 120px 130px 110px 220px', columnGap: '12px', alignItems: 'center', padding: '9px 14px', borderBottom: i < uninv.length - 1 ? '1px solid var(--border-soft)' : 'none' }}>
                  <div style={{ minWidth: 0 }}>
                    <div style={{ fontSize: '13px', fontWeight: 500, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{s.firstName} {s.lastName}</div>
                    <div style={{ fontSize: '11px', color: 'var(--text-muted)', fontFamily: 'ui-monospace, monospace' }}>{customerNo(s)}</div>
                  </div>
                  <span style={{ fontSize: '12px', color: 'var(--text-secondary)' }}>{s.course}</span>
                  <span style={{ fontSize: '12px', color: 'var(--text-muted)' }}>{s.campus}</span>
                  <span>{pill('No invoice', '#C05656')}</span>
                  <div style={{ display: 'flex', gap: '6px', justifyContent: 'flex-end' }}>
                    <button style={{ ...aBtn, color: '#4A9B7E', borderColor: 'rgba(74,155,126,0.4)' }} onClick={() => onNewFor(s.id)}>New invoice</button>
                    <button style={aBtn} onClick={() => clearStudent(s)}>Clear</button>
                  </div>
                </div>
              ))}
            </div>
          </div>
        )}
        {unsent.length > 0 && (
          <div style={card}>
            <div style={inner}>
              <div style={secHead}>Created but not emailed <span style={{ color: 'var(--text-muted)', fontWeight: 400 }}>· {unsent.length}</span></div>
              {unsent.map((inv, i) => { const s = invoicePrimaryStudent(inv); return (
                <div key={inv.id} style={{ display: 'grid', gridTemplateColumns: '120px minmax(150px,1.3fr) 120px 96px 110px 200px', columnGap: '12px', alignItems: 'center', padding: '9px 14px', borderBottom: i < unsent.length - 1 ? '1px solid var(--border-soft)' : 'none' }}>
                  <span style={{ fontSize: '12px', fontFamily: 'ui-monospace, monospace', color: 'var(--text-secondary)' }}>{inv.number}</span>
                  <span style={{ fontSize: '13px', color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{s ? `${s.firstName} ${s.lastName}` : '—'}</span>
                  <span style={{ fontSize: '12px', color: 'var(--text-secondary)' }}>{invoiceCoursesLabel(inv)}</span>
                  <span style={{ fontSize: '13px', fontWeight: 600, color: 'var(--text-primary)', fontVariantNumeric: 'tabular-nums' }}>{fmtAUD(inv.totalInc)}</span>
                  <span>{pill('Not emailed', '#B8922A')}</span>
                  <div style={{ display: 'flex', gap: '6px', justifyContent: 'flex-end' }}>
                    <button style={{ ...aBtn, color: '#4A9B7E', borderColor: 'rgba(74,155,126,0.4)' }} onClick={() => emailNow(inv)}>✉ Email now</button>
                    <button style={aBtn} onClick={() => clearInvoice(inv)}>Clear</button>
                  </div>
                </div>
              ); })}
            </div>
          </div>
        )}
      </>)}
    </div>
  );
}

function InvoicesSection({ showData }) {
  const [version, setVersion] = useOS(0);
  const force = () => setVersion(v => v + 1);
  const [view, setView] = useOS('invoices');           // invoices | adjustments
  const [adjNewOpen, setAdjNewOpen] = useOS(false);
  const [q, setQ] = useOS('');
  const [filter, setFilter] = useOS('all');           // all | unpaid | overdue | paid
  const [fCampus, setFCampus] = useOS('all');
  const [fCourse, setFCourse] = useOS('all');
  const [fTerm, setFTerm] = useOS('all');
  const [selId, setSelId] = useOS(null);
  const [selRows, setSelRows] = useOS(() => new Set());   // bulk-select invoice ids
  const [drawerOpen, setDrawerOpen] = useOS(false);
  const [newOpen, setNewOpen] = useOS(false);
  const [editingInv, setEditingInv] = useOS(null);
  const [initialStudentId, setInitialStudentId] = useOS(null);
  const [batchOpen, setBatchOpen] = useOS(false);
  const [toast, setToast] = useOS('');
  const flash = m => { setToast(m); setTimeout(() => setToast(''), 2600); };

  if (!showData) {
    return (
      <div>
        <SectionHeader title="Invoices" subtitle="Create, send and track tuition invoices" />
        <EmptyState message="Enable sample data to see invoices" />
      </div>
    );
  }

  const COURSES = ['YR09', 'YR10', 'YR11 STD', 'YR11 ADVN', 'YR11 EXT1', 'YR12 STD', 'YR12 ADVN', 'YR12 EXT1', 'YR12 EXT2'];
  const counts = {
    all: SAMPLE_INVOICES.filter(i => !i.void).length,
    paid: SAMPLE_INVOICES.filter(i => paymentStatus(i) === 'paid').length,
    overdue: SAMPLE_INVOICES.filter(i => invoiceOverdueInfo(i).overdue).length,
    unpaid: SAMPLE_INVOICES.filter(i => { const s = paymentStatus(i); return (s === 'unpaid' || s === 'partial') && !invoiceOverdueInfo(i).overdue; }).length,
    adjusted: SAMPLE_INVOICES.filter(i => ['credited', 'refunded'].indexOf(paymentStatus(i)) !== -1).length,
    writtenOff: SAMPLE_INVOICES.filter(i => paymentStatus(i) === 'written-off').length,
    drafts: SAMPLE_INVOICES.filter(i => !i.void && !i.sent).length,
  };
  const adjCount = SAMPLE_INVOICES.reduce((a, i) => a + (i.creditNotes || []).length, 0);
  const gapCount = uninvoicedStudents().list.length + unsentInvoices().length;
  const claimsCount = pendingCreditClaims().length;
  const query = q.trim().toLowerCase();
  const matchesFilter = inv => {
    if (inv.void) return false;
    const ov = invoiceOverdueInfo(inv), stt = paymentStatus(inv);
    if (filter === 'paid') return stt === 'paid';
    if (filter === 'overdue') return ov.overdue;
    if (filter === 'unpaid') return (stt === 'unpaid' || stt === 'partial') && !ov.overdue;
    if (filter === 'adjusted') return ['credited', 'refunded'].indexOf(stt) !== -1;
    if (filter === 'written-off') return stt === 'written-off';
    if (filter === 'drafts') return !inv.sent;
    return true;
  };
  const visible = SAMPLE_INVOICES.filter(inv => {
    if (!matchesFilter(inv)) return false;
    const s = invoicePrimaryStudent(inv) || {};
    const p = invoiceBillTo(inv) || {};
    if (fCampus !== 'all' && s.campus !== fCampus) return false;
    if (fCourse !== 'all' && invoiceCourses(inv).indexOf(fCourse) === -1) return false;
    if (fTerm !== 'all' && inv.term !== fTerm) return false;
    if (query) {
      const names = (inv.studentIds || []).map(id => { const x = SAMPLE_STUDENTS.find(y => y.id === id); return x ? `${x.firstName} ${x.lastName}` : ''; }).join(' ');
      const hay = `${inv.number} ${names} ${p.firstName || ''} ${p.lastName || ''} ${inv.customerNo} ${invoiceCourses(inv).join(' ')} ${inv.term} ${paymentStatus(inv)}`.toLowerCase();
      if (!hay.includes(query)) return false;
    }
    return true;
  });

  const outstanding = SAMPLE_INVOICES.filter(i => { const s = paymentStatus(i); return s === 'unpaid' || s === 'partial'; }).reduce((a, i) => a + invoiceBalance(i), 0);
  const dueSoon = SAMPLE_INVOICES.filter(i => { const s = paymentStatus(i), ov = invoiceOverdueInfo(i); return (s === 'unpaid' || s === 'partial') && !ov.overdue && ov.dueInDays <= 7; }).length;
  const visibleTotal = visible.reduce((a, i) => a + ((filter === 'paid' || filter === 'adjusted' || filter === 'drafts') ? i.totalInc : invoiceBalance(i)), 0);
  const totLabel = filter === 'paid' ? 'Collected' : filter === 'overdue' ? 'Overdue' : filter === 'unpaid' ? 'Owing' : filter === 'adjusted' ? 'Adjusted' : filter === 'written-off' ? 'Written off' : filter === 'drafts' ? 'Drafts' : 'Outstanding';

  const COLS = '34px 132px 1.5fr 1.1fr 104px 1.3fr 96px 96px 96px 150px 110px';
  const hcell = { fontSize: '11px', fontWeight: 500, color: 'var(--text-secondary)', letterSpacing: '0.05em', textTransform: 'uppercase' };
  const sel = SAMPLE_INVOICES.find(i => i.id === selId);

  const openInvoice = inv => { setSelId(inv.id); setDrawerOpen(true); };
  const toggleRow = id => setSelRows(prev => { const n = new Set(prev); n.has(id) ? n.delete(id) : n.add(id); return n; });
  const allVisibleSelected = visible.length > 0 && visible.every(i => selRows.has(i.id));
  const toggleAll = () => setSelRows(allVisibleSelected ? new Set() : new Set(visible.map(i => i.id)));
  const selectedInvs = SAMPLE_INVOICES.filter(i => selRows.has(i.id));
  const emailSelected = () => {
    let ok = 0, blocked = 0;
    selectedInvs.forEach(i => { const r = emailInvoice(i); if (r.ok) ok++; else blocked++; });
    flash(`${ok} invoice${ok === 1 ? '' : 's'} emailed${blocked ? ` · ${blocked} need a contact (see Tasks)` : ''}`);
    setSelRows(new Set()); force();
  };

  const seg = (id, label, n) => (
    <button key={id} onClick={() => setView(id)} style={{
      background: view === id ? 'var(--bg-surface)' : 'transparent',
      border: view === id ? '1px solid var(--border-default)' : '1px solid transparent',
      boxShadow: view === id ? '0 1px 2px rgba(0,0,0,0.18)' : 'none',
      color: view === id ? 'var(--text-primary)' : 'var(--text-secondary)',
      borderRadius: '6px', padding: '6px 14px', fontSize: '13px', fontWeight: view === id ? 600 : 500,
      cursor: 'pointer', fontFamily: 'inherit', display: 'inline-flex', alignItems: 'center', gap: '7px', transition: '120ms ease',
    }}>{label}{n != null && <span style={{ fontSize: '11px', fontWeight: 700, color: view === id ? '#4A9B7E' : 'var(--text-muted)' }}>{n}</span>}</button>
  );

  return (
    <div>
      <SectionHeader title="Invoices"
        subtitle={view === 'invoices' ? 'Create, send and track tuition invoices'
          : view === 'adjustments' ? 'Issue and track customer adjustment (credit) notes — synced to ReckonOne'
          : view === 'claims' ? 'Review and decide absence-credit requests'
          : 'Enrolled students missing an invoice or an email this term'}
        action={view === 'invoices'
          ? <div style={{ display: 'flex', gap: '8px' }}>
              <Button variant="secondary" onClick={() => setBatchOpen(true)}>Batch create</Button>
              <Button variant="primary" icon={<Icon name="plus" size={14} color="#FBFAF6" />} onClick={() => { setEditingInv(null); setInitialStudentId(null); setNewOpen(true); }}>New invoice</Button>
            </div>
          : view === 'adjustments'
            ? <Button variant="primary" icon={<Icon name="plus" size={14} color="#FBFAF6" />} onClick={() => setAdjNewOpen(true)}>New adjustment note</Button>
            : null} />

      {/* View toggle */}
      <div style={{ display: 'inline-flex', background: 'var(--bg-surface-muted)', border: '1px solid var(--border-default)', borderRadius: '8px', padding: '3px', gap: '2px', marginBottom: '18px', flexWrap: 'wrap' }}>
        {seg('invoices', 'Invoices', counts.all)}
        {seg('adjustments', 'Adjustment notes', adjCount)}
        {seg('claims', 'Credit claims', claimsCount)}
        {seg('gaps', 'Billing gaps', gapCount)}
      </div>

      {view === 'invoices' && (<>
      {/* Outstanding strip */}
      <div style={{ display: 'flex', gap: '20px', flexWrap: 'wrap', marginBottom: '18px', background: 'var(--bg-surface)', border: '1px solid var(--border-soft)', borderRadius: '10px', padding: '12px 16px' }}>
        {[['Outstanding', fmtAUD(outstanding), 'var(--text-primary)'], ['Overdue', counts.overdue, counts.overdue ? '#C05656' : 'var(--text-primary)'], ['Due ≤7 days', dueSoon, '#D4A03A'], ['Paid', counts.paid, '#4A9B7E']].map(([l, v, c]) => (
          <div key={l} style={{ display: 'flex', flexDirection: 'column' }}>
            <span style={{ fontSize: '11px', color: 'var(--text-muted)', letterSpacing: '0.04em', textTransform: 'uppercase' }}>{l}</span>
            <span style={{ fontSize: '18px', fontWeight: 700, color: c, fontVariantNumeric: 'tabular-nums' }}>{v}</span>
          </div>
        ))}
      </div>

      {/* Filters + search */}
      <div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap', alignItems: 'center', marginBottom: '16px' }}>
        <Chip label="All" count={counts.all} active={filter === 'all'} onClick={() => setFilter('all')} />
        <Chip label="Unpaid" count={counts.unpaid} active={filter === 'unpaid'} onClick={() => setFilter('unpaid')} />
        <Chip label="Overdue" count={counts.overdue} active={filter === 'overdue'} onClick={() => setFilter('overdue')} />
        <Chip label="Paid" count={counts.paid} active={filter === 'paid'} onClick={() => setFilter('paid')} />
        {counts.drafts > 0 && <Chip label="Drafts" count={counts.drafts} active={filter === 'drafts'} onClick={() => setFilter('drafts')} />}
        {counts.adjusted > 0 && <Chip label="Adjusted" count={counts.adjusted} active={filter === 'adjusted'} onClick={() => setFilter('adjusted')} />}
        {counts.writtenOff > 0 && <Chip label="Written off" count={counts.writtenOff} active={filter === 'written-off'} onClick={() => setFilter('written-off')} />}
        <span style={{ display: 'inline-flex', alignItems: 'center', gap: '6px', marginLeft: '4px', fontSize: '13px', fontWeight: 600, whiteSpace: 'nowrap', background: 'rgba(192,86,86,0.1)', border: '1px solid rgba(192,86,86,0.45)', borderRadius: '6px', padding: '5px 11px', color: '#C05656' }}>{totLabel}: <span style={{ fontVariantNumeric: 'tabular-nums' }}>{fmtAUD(visibleTotal)}</span></span>
        <div style={{ display: 'flex', gap: '8px', alignItems: 'center', marginLeft: 'auto', flexWrap: 'wrap' }}>
          <div style={{ width: 150 }}><FormSelect value={fCampus} onChange={setFCampus} buttonStyle={{ padding: '6px 10px' }} options={[{ value: 'all', label: 'All campuses' }, { value: 'Parramatta', label: 'Parramatta' }, { value: 'Bella Vista', label: 'Bella Vista' }]} /></div>
          <div style={{ width: 150 }}><FormSelect value={fCourse} onChange={setFCourse} buttonStyle={{ padding: '6px 10px' }} options={[{ value: 'all', label: 'All courses' }, ...COURSES.map(c => ({ value: c, label: c }))]} /></div>
          <div style={{ width: 160 }}><FormSelect value={fTerm} onChange={setFTerm} buttonStyle={{ padding: '6px 10px' }} options={[{ value: 'all', label: 'All terms' }, ...TERM_SCHEDULE.map(t => ({ value: t.name, label: t.name }))]} /></div>
          <div style={{ position: 'relative', minWidth: 240 }}>
            <Icon name="search" size={14} color="var(--text-muted)" style={{ position: 'absolute', left: 10, top: '50%', transform: 'translateY(-50%)' }} />
            <input value={q} onChange={e => setQ(e.target.value)} placeholder="Search #, student, parent, ID…" style={{ width: '100%', boxSizing: 'border-box', background: 'var(--bg-surface-muted)', border: '1px solid var(--border-default)', borderRadius: '6px', padding: '7px 12px 7px 32px', fontSize: '13px', color: 'var(--text-primary)', fontFamily: 'inherit', outline: 'none' }} />
          </div>
        </div>
      </div>

      {/* Bulk-action bar */}
      {selRows.size > 0 && (
        <div style={{ display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '12px', background: 'rgba(31,77,61,0.12)', border: '1px solid rgba(31,77,61,0.4)', borderRadius: '8px', padding: '8px 14px' }}>
          <span style={{ fontSize: '13px', fontWeight: 600, color: 'var(--text-primary)' }}>{selRows.size} selected</span>
          <Button size="sm" variant="primary" onClick={emailSelected}>✉ Email selected</Button>
          <button onClick={() => setSelRows(new Set())} style={{ background: 'none', border: 'none', color: 'var(--text-muted)', fontSize: '12px', cursor: 'pointer', fontFamily: 'inherit', marginLeft: 'auto' }}>Clear selection</button>
        </div>
      )}

      {/* Table */}
      <div style={{ border: '1px solid var(--border-default)', borderRadius: '10px', background: 'var(--bg-surface-muted)', padding: '8px' }}>
        <div style={{ overflow: 'auto', border: '1px solid var(--border-soft)', borderRadius: '8px', background: 'var(--bg-surface)' }}>
          <div style={{ minWidth: 'max-content' }}>
            <div style={{ position: 'sticky', top: 0, zIndex: 5, display: 'grid', gridTemplateColumns: COLS, columnGap: '14px', background: 'var(--bg-surface-muted)', padding: '9px 16px', borderBottom: '1px solid var(--border-soft)', alignItems: 'center' }}>
              <span onClick={toggleAll} style={{ cursor: 'pointer', display: 'inline-flex' }}><Checkbox checked={allVisibleSelected} onChange={toggleAll} /></span>
              {['#', 'Customer', 'Bill to', 'Course', 'Term · weeks', 'Issued', 'Due', 'Total', 'Status', 'Method'].map(h => <span key={h} style={hcell}>{h}</span>)}
            </div>
            {visible.map((inv, i) => {
              const s = invoicePrimaryStudent(inv) || {};
              const p = invoiceBillTo(inv);
              const extra = (inv.studentIds || []).length - 1;
              return (
                <div key={inv.id} onClick={() => openInvoice(inv)}
                  style={{ display: 'grid', gridTemplateColumns: COLS, columnGap: '14px', padding: '10px 16px', alignItems: 'center', cursor: 'pointer', background: 'var(--bg-surface)', borderBottom: i < visible.length - 1 ? '1px solid var(--border-soft)' : 'none', opacity: inv.void ? 0.5 : 1 }}
                  onMouseEnter={e => e.currentTarget.style.background = 'var(--bg-hover)'} onMouseLeave={e => e.currentTarget.style.background = 'var(--bg-surface)'}>
                  <span onClick={e => e.stopPropagation()} style={{ display: 'inline-flex' }}><Checkbox checked={selRows.has(inv.id)} onChange={() => toggleRow(inv.id)} /></span>
                  <div style={{ minWidth: 0 }}>
                    <div style={{ fontSize: '12px', fontFamily: 'ui-monospace, monospace', color: 'var(--text-secondary)', whiteSpace: 'nowrap' }}>{inv.number}</div>
                    {!inv.sent ? <span style={{ fontSize: '9px', fontWeight: 700, letterSpacing: '0.05em', textTransform: 'uppercase', color: '#B8922A' }}>Draft</span>
                      : <span style={{ fontSize: '9px', color: 'var(--text-muted)', letterSpacing: '0.04em', textTransform: 'uppercase' }}>Sent</span>}
                  </div>
                  <div style={{ minWidth: 0 }}>
                    <div style={{ fontSize: '13px', fontWeight: 500, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{s.firstName} {s.lastName}{extra > 0 ? <span style={{ color: 'var(--text-muted)', fontWeight: 400 }}> +{extra}</span> : ''}</div>
                    <div style={{ fontSize: '11px', color: 'var(--text-muted)', fontFamily: 'ui-monospace, monospace' }}>{inv.customerNo}</div>
                  </div>
                  <span style={{ fontSize: '12px', color: 'var(--text-secondary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{p ? `${p.firstName} ${p.lastName}` : '—'}</span>
                  <span style={{ fontSize: '12px', color: 'var(--text-secondary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{invoiceCoursesLabel(inv)}</span>
                  <span style={{ fontSize: '12px', color: 'var(--text-secondary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{inv.term} · {invoiceWeeksLabel(inv)}</span>
                  <span style={{ fontSize: '12px', color: 'var(--text-muted)', whiteSpace: 'nowrap' }}>{fmtAUDate(inv.issueDate)}</span>
                  <span style={{ fontSize: '12px', color: 'var(--text-muted)', whiteSpace: 'nowrap' }}>{fmtAUDate(inv.dueDate)}</span>
                  <span style={{ fontSize: '13px', fontWeight: 600, color: 'var(--text-primary)', fontVariantNumeric: 'tabular-nums' }}>{fmtAUD(inv.totalInc)}</span>
                  <span><InvoiceStatusPill inv={inv} /></span>
                  <span style={{ fontSize: '12px', color: 'var(--text-secondary)' }}>{invPayLabel(inv.paymentMethod)}</span>
                </div>
              );
            })}
            {visible.length === 0 && <div style={{ padding: '40px', textAlign: 'center', fontSize: '13px', color: 'var(--text-muted)' }}>No invoices match your filters</div>}
          </div>
        </div>
      </div>
      </>)}

      {view === 'adjustments' && <AdjustmentNotesView flash={flash} notify={force} newOpen={adjNewOpen} onCloseNew={() => setAdjNewOpen(false)} />}

      {view === 'claims' && <CreditClaimsView flash={flash} notify={force} />}

      {view === 'gaps' && <BillingGapsView flash={flash} notify={force} onNewFor={sid => { setEditingInv(null); setInitialStudentId(sid); setNewOpen(true); }} />}

      <InvoiceViewDrawer inv={sel} open={drawerOpen} onClose={() => setDrawerOpen(false)} onChange={force} flash={flash}
        onEdit={inv => { setDrawerOpen(false); setEditingInv(inv); setInitialStudentId(null); setNewOpen(true); }} />
      <NewInvoiceModal open={newOpen} editingInv={editingInv} initialStudentId={initialStudentId} onClose={() => { setNewOpen(false); setEditingInv(null); setInitialStudentId(null); }} onSaved={force} flash={flash} />
      <BatchInvoiceModal open={batchOpen} onClose={() => setBatchOpen(false)} onSaved={force} flash={flash} />

      {toast && (
        <div style={{ position: 'fixed', bottom: '24px', left: '50%', transform: 'translateX(-50%)', zIndex: 2000,
          background: 'var(--bg-elevated)', border: '1px solid rgba(74,155,126,0.4)', color: '#4A9B7E', borderRadius: '8px',
          padding: '10px 18px', fontSize: '13px', fontWeight: 500, boxShadow: '0 8px 24px rgba(0,0,0,0.4)' }}>
          ✓ {toast}
        </div>
      )}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// SETTINGS
// ─────────────────────────────────────────────────────────────

// Business identity / tax / pricing / bank — edits INVOICE_SETTINGS in place.
function BusinessSettings() {
  const [v, setV] = useOS(0);
  const S = INVOICE_SETTINGS;
  const force = () => setV(x => x + 1);
  const card = { background: 'var(--bg-surface)', border: '1px solid var(--border-soft)', borderRadius: '10px', padding: '16px', marginBottom: '14px' };
  const head = { fontSize: '11px', fontWeight: 600, letterSpacing: '0.05em', textTransform: 'uppercase', color: 'var(--text-muted)', marginBottom: '12px' };
  const lbl = { fontSize: '12px', color: 'var(--text-muted)', display: 'block', marginBottom: '5px' };
  const inp = { width: '100%', boxSizing: 'border-box', background: 'var(--bg-surface-muted)', border: '1px solid var(--border-default)', borderRadius: '4px', padding: '8px 10px', fontSize: '13px', color: 'var(--text-primary)', fontFamily: 'inherit', outline: 'none' };
  const field = (label, key, opts = {}) => (
    <div><label style={lbl}>{label}</label>
      <input type={opts.num ? 'number' : 'text'} value={opts.bank ? S.bank[key] : S[key]} placeholder={opts.ph}
        onChange={e => { const val = opts.num ? (parseFloat(e.target.value) || 0) : e.target.value; if (opts.bank) S.bank[key] = val; else S[key] = val; force(); }} style={inp} /></div>
  );
  const toggle = (label, key, desc) => (
    <label style={{ display: 'flex', alignItems: 'flex-start', gap: '10px', padding: '8px 0', cursor: 'pointer' }}>
      <Checkbox checked={!!S[key]} onChange={val => { S[key] = val; force(); }} />
      <span><span style={{ fontSize: '13px', color: 'var(--text-primary)' }}>{label}</span><div style={{ fontSize: '11px', color: 'var(--text-muted)', marginTop: '2px' }}>{desc}</div></span>
    </label>
  );
  return (
    <div>
      <div style={card}>
        <div style={head}>Business identity (shown on every invoice)</div>
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px' }}>
          {field('Business name', 'businessName')}{field('ABN', 'abn')}{field('Email', 'email')}{field('Phone', 'phone')}
        </div>
        <div style={{ marginTop: '12px' }}>{field('Address', 'address')}</div>
      </div>
      <div style={card}>
        <div style={head}>Tax</div>
        {toggle('Registered for GST', 'gstRegistered', 'On → compliant "Tax Invoice" with 10% GST + ABN. Off → plain "Invoice", no GST.')}
        {toggle('Treat tuition as GST-free', 'gstFree', 'Leave OFF — 10% GST applies to ALL services, including private tutoring. Only enable for a genuinely GST-free supply (confirm with your accountant).')}
      </div>
      <div style={card}>
        <div style={head}>Pricing & terms</div>
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', gap: '12px' }}>
          {field('Base hourly rate ($)', 'hourlyRate', { num: true })}{field('Cash discount (%)', 'cashDiscountPct', { num: true })}
          {field('Payment terms (days)', 'paymentTermsDays', { num: true })}{field('Overdue task after (days)', 'overdueTaskDays', { num: true })}
        </div>
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px', marginTop: '12px' }}>
          {field('Private 1-on-1 rate ($/h)', 'privateRate', { num: true })}
          {field('Enrolment fee ($, one-off)', 'enrolmentFee', { num: true })}
        </div>
        <div style={{ marginTop: '16px' }}>
          <div style={{ fontSize: '12px', fontWeight: 600, color: 'var(--text-secondary)', marginBottom: '8px' }}>Per-course rates <span style={{ fontWeight: 400, color: 'var(--text-muted)' }}>— leave blank to use the base ${S.hourlyRate}/h. Set a premium for Yr12 / Extension here.</span></div>
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '10px' }}>
            {INVOICE_COURSES.map(c => (
              <div key={c} style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
                <span style={{ fontSize: '12px', color: 'var(--text-secondary)', width: 84, whiteSpace: 'nowrap' }}>{c}</span>
                <input type="number" value={S.courseRates[c] != null ? S.courseRates[c] : ''} placeholder={String(S.hourlyRate)}
                  onChange={e => { const val = e.target.value; if (val === '') delete S.courseRates[c]; else S.courseRates[c] = parseFloat(val) || 0; force(); }}
                  style={{ ...inp, width: 70, padding: '6px 8px' }} />
                <span style={{ fontSize: '11px', color: 'var(--text-muted)' }}>/h</span>
              </div>
            ))}
          </div>
        </div>
      </div>
      <div style={card}>
        <div style={head}>Automated reminders (dunning) &amp; receipts</div>
        {toggle('Run the reminder sequence', 'remindersEnabled', 'Emails / texts the parent on the schedule below; the whole sequence auto-cancels the moment the invoice is paid.')}
        <div style={{ marginTop: '4px', display: 'flex', flexDirection: 'column' }}>
          {(S.reminderSequence || []).map(step => (
            <label key={step.id} style={{ display: 'flex', alignItems: 'center', gap: '10px', padding: '6px 0', cursor: 'pointer' }}>
              <Checkbox checked={step.on !== false} onChange={val => { step.on = val; force(); }} />
              <span style={{ fontSize: '12px', width: 34, color: 'var(--text-muted)', textAlign: 'right', fontVariantNumeric: 'tabular-nums' }}>{step.offset > 0 ? `+${step.offset}` : step.offset}d</span>
              <span style={{ fontSize: '13px', color: 'var(--text-primary)', flex: 1 }}>{step.label}</span>
              <span style={{ fontSize: '11px', color: 'var(--text-muted)' }}>{step.tone} · {step.channels.map(ch => ch === 'sms' ? 'SMS' : 'Email').join(' + ')}</span>
            </label>
          ))}
        </div>
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px', marginTop: '10px' }}>{field('Send time', 'reminderHour')}</div>
        <div style={{ fontSize: '11px', color: 'var(--text-muted)', marginTop: '8px' }}>Offsets are days relative to the due date (negative = before due). A receipt (PAID) is auto-emailed the moment an invoice is paid in full.</div>
      </div>
      <div style={card}>
        <div style={head}>Stripe &amp; payments</div>
        <div style={{ display: 'flex', alignItems: 'center', gap: '10px', marginBottom: '12px' }}>
          <span style={{ display: 'inline-flex', alignItems: 'center', gap: '6px', fontSize: '12px', fontWeight: 600, color: S.stripeConnected ? '#4A9B7E' : '#B8922A', background: S.stripeConnected ? 'rgba(74,155,126,0.12)' : 'rgba(184,146,42,0.12)', border: `1px solid ${S.stripeConnected ? 'rgba(74,155,126,0.35)' : 'rgba(184,146,42,0.35)'}`, borderRadius: '5px', padding: '3px 10px' }}>{S.stripeConnected ? '● Connected' : '○ Not connected'}</span>
          <span style={{ fontSize: '12px', color: 'var(--text-muted)' }}>{S.stripeAccountName}</span>
        </div>
        {field('Stripe payment link', 'stripeBaseUrl')}
        <div style={{ marginTop: '12px', maxWidth: '340px' }}>{field('Dishonour fee ($) — passed on for failed direct debits', 'dishonourFee', { num: true })}</div>
        {toggle('Absorb card fees (no surcharge)', 'cardFeesAbsorbed', 'We pay Stripe’s card fees ourselves — customers are never surcharged.')}
        <div style={{ fontSize: '11px', color: 'var(--text-muted)', marginTop: '8px' }}>Card + direct-debit payments run through Stripe; when a payment clears, the invoice auto-marks PAID and the receipt is emailed. (Live keys/webhooks are wired at production.)</div>
      </div>
      <div style={{ fontSize: '11px', color: 'var(--text-muted)' }}>These are placeholders — edit freely. Changes flow into new invoices and previews; existing invoices keep their stored totals.</div>
    </div>
  );
}

// Reusable discount type CRUD — types are assigned to customers from their profile.
function DiscountsSettings() {
  const [v, setV] = useOS(0);
  const [editing, setEditing] = useOS(null);
  const force = () => setV(x => x + 1);
  const lbl = { fontSize: '12px', color: 'var(--text-muted)', display: 'block', marginBottom: '5px' };
  const inp = { width: '100%', boxSizing: 'border-box', background: 'var(--bg-surface-muted)', border: '1px solid var(--border-default)', borderRadius: '4px', padding: '8px 10px', fontSize: '13px', color: 'var(--text-primary)', fontFamily: 'inherit', outline: 'none' };
  const save = () => {
    if (!editing.name.trim()) return;
    if (editing.id) { const t = SAMPLE_DISCOUNT_TYPES.find(x => x.id === editing.id); if (t) Object.assign(t, { name: editing.name, kind: editing.kind, value: parseFloat(editing.value) || 0 }); }
    else SAMPLE_DISCOUNT_TYPES.push({ id: `d-${Date.now()}`, name: editing.name, kind: editing.kind, value: parseFloat(editing.value) || 0 });
    setEditing(null); force();
  };
  const remove = id => {
    const t = SAMPLE_DISCOUNT_TYPES.find(x => x.id === id); if (!t) return;
    const assigned = SAMPLE_STUDENTS.filter(s => (s.discounts || []).some(d => d.typeId === id)).length;
    const msg = assigned > 0
      ? `“${t.name}” is currently assigned to ${assigned} active customer${assigned === 1 ? '' : 's'}. Deleting it keeps it on invoices already sent, but it won’t apply to any new invoices. Delete it?`
      : `Delete “${t.name}”?`;
    if (!window.confirm(msg)) return;
    const i = SAMPLE_DISCOUNT_TYPES.findIndex(x => x.id === id); if (i >= 0) SAMPLE_DISCOUNT_TYPES.splice(i, 1);
    SAMPLE_STUDENTS.forEach(s => { if (s.discounts) s.discounts = s.discounts.filter(d => d.typeId !== id); });
    logActivity({ action: 'Deleted discount type', record: t.name, detail: assigned ? `was on ${assigned} customers` : '' });
    force();
  };
  return (
    <div>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '14px', gap: '12px' }}>
        <div style={{ fontSize: '12px', color: 'var(--text-muted)' }}>Define discounts once, then assign them to a customer (ongoing or once-off) from their profile. Assigned discounts auto-apply to invoices.</div>
        <Button size="sm" variant="primary" onClick={() => setEditing({ name: '', kind: 'percent', value: 10 })}>+ New discount</Button>
      </div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
        {SAMPLE_DISCOUNT_TYPES.map(t => (
          <div key={t.id} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '12px 16px', background: 'var(--bg-surface)', borderRadius: '8px', border: '1px solid var(--border-soft)' }}>
            <div><div style={{ fontSize: '13px', fontWeight: 500, color: 'var(--text-primary)' }}>{t.name}</div>
              <div style={{ fontSize: '12px', color: 'var(--text-muted)' }}>{t.kind === 'percent' ? `${t.value}% off` : `${fmtAUD(t.value)} off`}</div></div>
            <div style={{ display: 'flex', gap: '8px' }}>
              <Button size="sm" variant="ghost" onClick={() => setEditing({ id: t.id, name: t.name, kind: t.kind, value: t.value })}>Edit</Button>
              <Button size="sm" variant="ghost" onClick={() => remove(t.id)}>Delete</Button>
            </div>
          </div>
        ))}
        {SAMPLE_DISCOUNT_TYPES.length === 0 && <EmptyState message="No discount types yet." />}
      </div>
      {editing && (
        <Modal open={true} onClose={() => setEditing(null)} title={editing.id ? 'Edit discount' : 'New discount'}>
          <div style={{ display: 'flex', flexDirection: 'column', gap: '14px' }}>
            <div><label style={lbl}>Name</label><input value={editing.name} onChange={e => setEditing({ ...editing, name: e.target.value })} placeholder="e.g. Sibling discount" style={inp} /></div>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px' }}>
              <div><label style={lbl}>Type</label><FormSelect value={editing.kind} onChange={k => setEditing({ ...editing, kind: k })} options={[{ value: 'percent', label: 'Percentage (%)' }, { value: 'fixed', label: 'Fixed ($)' }]} /></div>
              <div><label style={lbl}>{editing.kind === 'percent' ? 'Percent' : 'Amount ($)'}</label><input type="number" value={editing.value} onChange={e => setEditing({ ...editing, value: e.target.value })} style={inp} /></div>
            </div>
            <div style={{ display: 'flex', gap: '8px', justifyContent: 'flex-end' }}>
              <Button variant="secondary" onClick={() => setEditing(null)}>Cancel</Button>
              <Button variant="primary" onClick={save}>{editing.id ? 'Save' : 'Create'}</Button>
            </div>
          </div>
        </Modal>
      )}
    </div>
  );
}

// ── Mini calendar date picker (mirrors the student trial-date picker) ──────────
function MiniDatePicker({ value, onChange }) {
  const [open, setOpen] = useOS(false);
  const [viewDate, setViewDate] = useOS(() => { const d = value ? new Date(value + 'T00:00:00') : new Date(); return { year: d.getFullYear(), month: d.getMonth() }; });
  const ref = React.useRef(null);
  const TODAY = new Date();
  const todayStr = `${TODAY.getFullYear()}-${String(TODAY.getMonth() + 1).padStart(2, '0')}-${String(TODAY.getDate()).padStart(2, '0')}`;
  React.useEffect(() => { if (!open) return; const close = e => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); }; document.addEventListener('mousedown', close); return () => document.removeEventListener('mousedown', close); }, [open]);
  const { year, month } = viewDate;
  const firstDay = new Date(year, month, 1).getDay();
  const daysInMonth = new Date(year, month + 1, 0).getDate();
  const monthName = new Date(year, month, 1).toLocaleDateString('en-AU', { month: 'long', year: 'numeric' });
  const prevMonth = () => setViewDate(v => { const d = new Date(v.year, v.month - 1, 1); return { year: d.getFullYear(), month: d.getMonth() }; });
  const nextMonth = () => setViewDate(v => { const d = new Date(v.year, v.month + 1, 1); return { year: d.getFullYear(), month: d.getMonth() }; });
  const cells = [];
  for (let i = 0; i < firstDay; i++) cells.push(null);
  for (let d = 1; d <= daysInMonth; d++) cells.push(d);
  while (cells.length % 7 !== 0) cells.push(null);
  const label = value ? (() => { const d = new Date(value + 'T00:00:00'); return `${String(d.getDate()).padStart(2, '0')}/${String(d.getMonth() + 1).padStart(2, '0')}/${d.getFullYear()}`; })() : 'Set date';
  return (
    <div ref={ref} style={{ position: 'relative' }} onClick={e => e.stopPropagation()}>
      <span onClick={() => { if (!open) { const d = value ? new Date(value + 'T00:00:00') : new Date(); setViewDate({ year: d.getFullYear(), month: d.getMonth() }); } setOpen(o => !o); }}
        title="Click to set the date"
        style={{ fontSize: '12px', color: value ? 'var(--text-secondary)' : 'var(--text-muted)', cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: '6px',
          borderRadius: '5px', padding: '4px 9px', border: '1px solid var(--border-default)', background: 'var(--bg-surface)' }}>
        <Icon name="calendar" size={13} color="var(--text-muted)" />{label}
      </span>
      {open && (
        <div style={{ position: 'absolute', top: 'calc(100% + 4px)', left: 0, zIndex: 400, background: 'var(--bg-elevated)', border: '1px solid var(--border-default)', borderRadius: '8px', boxShadow: '0 8px 24px rgba(0,0,0,0.45)', padding: '12px', width: '214px' }}>
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '10px' }}>
            <button onClick={prevMonth} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-muted)', padding: '2px 7px', borderRadius: '3px', fontSize: '15px', fontFamily: 'inherit', lineHeight: 1 }}>‹</button>
            <span style={{ fontSize: '12px', fontWeight: 600, color: 'var(--text-primary)' }}>{monthName}</span>
            <button onClick={nextMonth} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-muted)', padding: '2px 7px', borderRadius: '3px', fontSize: '15px', fontFamily: 'inherit', lineHeight: 1 }}>›</button>
          </div>
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: '2px', marginBottom: '3px' }}>
            {['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'].map(d => <div key={d} style={{ textAlign: 'center', fontSize: '10px', fontWeight: 500, color: 'var(--text-muted)', padding: '1px 0' }}>{d}</div>)}
          </div>
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: '2px' }}>
            {cells.map((day, idx) => {
              if (!day) return <div key={`e${idx}`} />;
              const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
              const isToday = dateStr === todayStr, isSelected = dateStr === value;
              return (
                <button key={day} onClick={() => { onChange(dateStr); setOpen(false); }}
                  style={{ background: isSelected ? 'var(--brand)' : isToday ? 'rgba(31,77,61,0.22)' : 'none', border: isToday && !isSelected ? '1px solid rgba(31,77,61,0.55)' : '1px solid transparent',
                    borderRadius: '4px', padding: '3px 0', fontSize: '11px', color: isSelected ? '#fff' : isToday ? '#4A9B7E' : 'var(--text-secondary)', cursor: 'pointer', fontFamily: 'inherit', textAlign: 'center' }}
                  onMouseEnter={e => { if (!isSelected) e.currentTarget.style.background = 'rgba(255,255,255,0.08)'; }}
                  onMouseLeave={e => { if (!isSelected) e.currentTarget.style.background = isToday ? 'rgba(31,77,61,0.22)' : 'none'; }}>{day}</button>
              );
            })}
          </div>
        </div>
      )}
    </div>
  );
}

// ── Terms: editable dates → auto weeks; holidays auto-derived from the gaps ─────
const TERMS_LS = 'tm_terms_v2';
const TERMS_YEARS = [2025, 2026, 2027, 2028];
const _emptyTerms = y => [1, 2, 3, 4].map(n => ({ year: y, name: `Term ${n}, ${y}`, start: '', end: '' }));
const TERMS_SEED = [
  ..._emptyTerms(2025),
  { year: 2026, name: 'Term 1, 2026', start: '2026-01-29', end: '2026-04-10' },
  { year: 2026, name: 'Term 2, 2026', start: '2026-04-28', end: '2026-07-03' },
  { year: 2026, name: 'Term 3, 2026', start: '2026-07-21', end: '2026-09-25' },
  { year: 2026, name: 'Term 4, 2026', start: '2026-10-13', end: '2026-12-18' },
  ..._emptyTerms(2027),
  ..._emptyTerms(2028),
];
function loadTerms() {
  try { const r = localStorage.getItem(TERMS_LS); if (r) { const p = JSON.parse(r); if (Array.isArray(p) && p.length) return p; } } catch (e) {}
  const seed = JSON.parse(JSON.stringify(TERMS_SEED));
  // Migrate any earlier single-year (v1) edits into the new multi-year seed.
  try { const r1 = localStorage.getItem('tm_terms_v1'); if (r1) { const p1 = JSON.parse(r1); if (Array.isArray(p1) && p1.length) return seed.map(t => t.year === 2026 ? { ...t, ...(p1.find(o => o.name === t.name) || {}) } : t); } } catch (e) {}
  return seed;
}

function TermsSettings() {
  const [terms, setTerms] = useOS(loadTerms);
  const [year, setYear] = useOS(() => { const y = new Date().getFullYear(); return TERMS_YEARS.includes(y) ? y : 2026; });
  const [showHol, setShowHol] = useOS(true);
  const save = next => { setTerms(next); try { localStorage.setItem(TERMS_LS, JSON.stringify(next)); } catch (e) {} };
  const setField = (gi, field, val) => save(terms.map((t, j) => j === gi ? { ...t, [field]: val } : t));

  const d0 = iso => iso ? new Date(iso + 'T00:00:00') : null;
  const daysBetween = (a, b) => Math.round((d0(b) - d0(a)) / 86400000);
  const fmt = iso => { const d = d0(iso); return d ? `${String(d.getDate()).padStart(2, '0')}/${String(d.getMonth() + 1).padStart(2, '0')}/${d.getFullYear()}` : '—'; };
  const addDays = (iso, n) => { const d = d0(iso); d.setDate(d.getDate() + n); return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`; };
  const weeksOf = t => (t.start && t.end && d0(t.end) >= d0(t.start)) ? Math.ceil((daysBetween(t.start, t.end) + 1) / 7) : null;
  const holiday = (t1, t2) => { if (!t1.end || !t2.start) return null; const g = daysBetween(t1.end, t2.start) - 1; if (g <= 0) return null; return { start: addDays(t1.end, 1), end: addDays(t2.start, -1), days: g, weeks: Math.round(g / 7) }; };
  const now = new Date();
  const COLS = '1.3fr 1.15fr 1.15fr 92px';
  const rows = terms.map((t, gi) => ({ t, gi })).filter(x => x.t.year === year);

  return (
    <div>
      <div style={{ display: 'flex', alignItems: 'center', gap: '12px', flexWrap: 'wrap', marginBottom: '12px' }}>
        <span style={{ fontSize: '11px', color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.05em' }}>Year</span>
        <span style={{ display: 'inline-flex', gap: '5px' }}>
          {TERMS_YEARS.map(y => (
            <button key={y} onClick={() => setYear(y)} style={{ border: '1px solid var(--border-default)', borderRadius: '20px', padding: '4px 13px', cursor: 'pointer', fontFamily: 'inherit',
              fontSize: '12px', background: year === y ? '#1F4D3D' : 'var(--bg-surface)', color: year === y ? '#fff' : 'var(--text-secondary)', fontWeight: year === y ? 600 : 400 }}>{y}</button>
          ))}
        </span>
        <span style={{ flex: 1 }} />
        <span onClick={() => setShowHol(s => !s)} style={{ display: 'inline-flex', alignItems: 'center', gap: '8px', cursor: 'pointer', fontSize: '12px', color: 'var(--text-secondary)' }}>
          <span style={{ width: 34, height: 19, borderRadius: 10, background: showHol ? '#1F4D3D' : 'var(--border-default)', position: 'relative', transition: '120ms ease', flexShrink: 0 }}>
            <span style={{ position: 'absolute', top: 2, left: showHol ? 17 : 2, width: 15, height: 15, borderRadius: '50%', background: '#fff', transition: '120ms ease' }} />
          </span>
          Show holidays
        </span>
      </div>

      <div style={{ borderRadius: '8px', border: '1px solid var(--border-soft)', overflow: 'hidden' }}>
        <div style={{ display: 'grid', gridTemplateColumns: COLS, background: 'var(--bg-surface-muted)', padding: '9px 16px', borderBottom: '1px solid var(--border-soft)',
          fontSize: '11px', fontWeight: 500, color: 'var(--text-secondary)', letterSpacing: '0.05em', textTransform: 'uppercase' }}>
          {['Term', 'Start date', 'End date', 'Weeks'].map(h => <span key={h}>{h}</span>)}
        </div>
        {rows.map(({ t, gi }, k) => {
          const cur = t.start && t.end && d0(t.start) <= now && now <= d0(t.end);
          const w = weeksOf(t);
          const h = (showHol && k < rows.length - 1) ? holiday(t, rows[k + 1].t) : null;
          return (
            <React.Fragment key={gi}>
              <div style={{ display: 'grid', gridTemplateColumns: COLS, padding: '10px 16px', alignItems: 'center', background: cur ? 'rgba(31,77,61,0.10)' : 'var(--bg-surface)', borderBottom: '1px solid var(--border-soft)' }}>
                <span style={{ fontSize: '13px', fontWeight: cur ? 600 : 400, color: cur ? '#4A9B7E' : 'var(--text-primary)', display: 'inline-flex', alignItems: 'center', gap: '8px' }}>
                  {t.name}{cur && <span style={{ fontSize: '9px', fontWeight: 700, color: '#4A9B7E', background: 'rgba(31,77,61,0.18)', borderRadius: '4px', padding: '1px 7px', textTransform: 'uppercase', letterSpacing: '0.04em' }}>current</span>}
                </span>
                <span><MiniDatePicker value={t.start} onChange={v => setField(gi, 'start', v)} /></span>
                <span><MiniDatePicker value={t.end} onChange={v => setField(gi, 'end', v)} /></span>
                <span style={{ fontSize: '12px', fontWeight: 700, color: w == null ? 'var(--text-muted)' : 'var(--text-primary)', fontVariantNumeric: 'tabular-nums' }}>{w == null ? '—' : `${w} weeks`}</span>
              </div>
              {h && (
                <div style={{ display: 'flex', alignItems: 'center', gap: '10px', padding: '7px 16px', background: 'var(--bg-surface-muted)', borderBottom: '1px solid var(--border-soft)', fontSize: '11.5px', color: 'var(--text-muted)' }}>
                  <span style={{ fontSize: '10px', fontWeight: 700, color: '#B8922A', background: 'rgba(184,146,42,0.14)', border: '1px solid rgba(184,146,42,0.3)', borderRadius: '4px', padding: '2px 8px', whiteSpace: 'nowrap' }}>School holidays</span>
                  <span>{fmt(h.start)} – {fmt(h.end)}</span>
                  <span style={{ marginLeft: 'auto', fontVariantNumeric: 'tabular-nums' }}>{h.days} days · ~{h.weeks} {h.weeks === 1 ? 'week' : 'weeks'}</span>
                </div>
              )}
            </React.Fragment>
          );
        })}
      </div>
      <div style={{ marginTop: '10px', fontSize: '11.5px', color: 'var(--text-muted)' }}>
        Pick a <strong style={{ color: 'var(--text-secondary)' }}>year</strong> to view or set its terms. Click a date for the mini calendar; <strong style={{ color: 'var(--text-secondary)' }}>weeks</strong> are auto-computed, and <strong style={{ color: 'var(--text-secondary)' }}>holidays</strong> are derived from the gaps between terms (toggle with "Show holidays").
      </div>
    </div>
  );
}

// ── Term calendar modal (week-by-week rows: the "which week are we in" view) ──
const CAL_MON = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
function TermCalendarModal({ open, onClose }) {
  const terms = loadTerms().filter(t => t.start && t.end);
  const d0 = iso => iso ? new Date(iso + 'T00:00:00') : null;
  const toISO = d => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
  const now = new Date(); now.setHours(0, 0, 0, 0);
  const todayStr = toISO(now);
  const startIdx = (() => { const c = terms.findIndex(t => t.start && t.end && d0(t.start) <= now && now <= d0(t.end)); return c < 0 ? 0 : c; })();
  const [idx, setIdx] = useOS(startIdx);
  if (!open) return null;

  const i = Math.max(0, Math.min(idx, terms.length - 1));
  const t = terms[i];
  const next = terms[i + 1];
  const termStart = d0(t.start), termEnd = d0(t.end);
  let hol = null;
  if (termEnd && next && next.start) { const s = new Date(termEnd); s.setDate(s.getDate() + 1); const e = d0(next.start); e.setDate(e.getDate() - 1); if (e >= s) hol = { start: s, end: e }; }

  // Snap to whole weeks: each row is ENTIRELY a term week (green) or a whole holiday week (yellow).
  // No partial/greyed cells, no spillover into the next term — stray holiday days are noted below instead.
  const mondayOf = d => { const x = new Date(d); const dow = (x.getDay() + 6) % 7; x.setDate(x.getDate() - dow); x.setHours(0, 0, 0, 0); return x; };
  const addD = (d, n) => { const x = new Date(d); x.setDate(x.getDate() + n); x.setHours(0, 0, 0, 0); return x; };
  const buildRow = (monday, rowKind, label, isHol) => {
    const days = [];
    for (let k = 0; k < 7; k++) { const d = addD(monday, k); days.push({ d, kind: toISO(d) === todayStr ? 'today' : rowKind }); }
    return { label, isHol, days };
  };
  const rows = []; let footnote = null;
  if (termStart && termEnd) {
    // Term weeks: every week that contains a teaching day → fully green, numbered 1..N.
    let cur = mondayOf(termStart); const lastTermMon = mondayOf(termEnd); let wk = 0;
    while (cur <= lastTermMon) { rows.push(buildRow(cur, 'term', String(++wk), false)); cur = addD(cur, 7); }
    // Holiday weeks: only WHOLE weeks that fit inside the break → fully yellow.
    if (hol) {
      while (addD(cur, 6) <= hol.end) { rows.push(buildRow(cur, 'holiday', 'Hol', true)); cur = addD(cur, 7); }
      // Leftover holiday days that don't fill a whole week aren't shown as a partial row — note them.
      if (cur <= hol.end) {
        const DOW = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
        const fmtShort = d => `${DOW[(d.getDay() + 6) % 7]} ${d.getDate()} ${CAL_MON[d.getMonth()]}`;
        const nextName = next ? next.name.split(',')[0] : 'the next term';
        const span = cur.getTime() === hol.end.getTime() ? fmtShort(cur) : `${fmtShort(cur)} – ${fmtShort(hol.end)}`;
        footnote = `School holidays also include ${span} — ${nextName} begins ${fmtShort(addD(hol.end, 1))}.`;
      }
    }
  }
  const styleFor = kind => {
    if (kind === 'today' || kind === 'today-hol') return { background: 'rgba(74,155,126,0.22)', color: 'var(--text-primary)', fontWeight: 700, boxShadow: 'inset 0 0 0 2px #4A9B7E' };
    if (kind === 'term')    return { background: 'rgba(74,155,126,0.14)', color: 'var(--text-primary)' };
    if (kind === 'holiday') return { background: 'rgba(184,146,42,0.16)', color: '#B8922A', fontWeight: 600 };
    return { color: 'var(--text-muted)', opacity: 0.4 };
  };
  const arrow = (dir, disabled) => (
    <button onClick={() => !disabled && setIdx(p => Math.max(0, Math.min(terms.length - 1, p + dir)))} disabled={disabled}
      style={{ background: 'var(--bg-surface)', border: '1px solid var(--border-default)', borderRadius: '6px', width: '32px', height: '32px',
        cursor: disabled ? 'default' : 'pointer', color: 'var(--text-secondary)', fontSize: '17px', fontFamily: 'inherit', opacity: disabled ? 0.3 : 1, flexShrink: 0 }}>
      {dir < 0 ? '‹' : '›'}</button>
  );
  const COLS = '46px repeat(7, 1fr)';

  return (
    <Modal open onClose={onClose} title="Term calendar" width={900}>
      <div>
        <div style={{ display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '18px' }}>
          {arrow(-1, i === 0)}
          <div style={{ flex: 1, textAlign: 'center', fontSize: '22px', fontWeight: 800, color: 'var(--text-primary)' }}>{t.name}</div>
          {arrow(1, i === terms.length - 1)}
        </div>
        {rows.length === 0
          ? <div style={{ textAlign: 'center', color: 'var(--text-muted)', fontSize: '12.5px', padding: '20px' }}>Set this term's start &amp; end dates in Settings → Terms to see its calendar.</div>
          : <div style={{ border: '1px solid var(--border-soft)', borderRadius: '8px', overflow: 'hidden' }}>
              <div style={{ display: 'grid', gridTemplateColumns: COLS, background: 'var(--bg-surface-muted)', borderBottom: '1px solid var(--border-soft)' }}>
                <div style={{ padding: '8px 6px', fontSize: '10px', fontWeight: 600, color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.04em', textAlign: 'center' }}>Wk</div>
                {['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'].map(d => (
                  <div key={d} style={{ padding: '8px 10px', fontSize: '11px', fontWeight: 600, color: 'var(--text-secondary)', textAlign: 'center', borderLeft: '1px solid var(--border-soft)' }}>{d}</div>
                ))}
              </div>
              {rows.map((row, ri) => (
                <div key={ri} style={{ display: 'grid', gridTemplateColumns: COLS, borderTop: ri ? '1px solid var(--border-soft)' : 'none' }}>
                  <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '12px', fontWeight: 700,
                    color: row.isHol ? '#B8922A' : 'var(--text-secondary)', background: row.isHol ? 'rgba(184,146,42,0.10)' : 'var(--bg-surface-muted)' }}>{row.label}</div>
                  {row.days.map((cell, ci) => (
                    <div key={ci} style={{ borderLeft: '1px solid var(--border-soft)', minHeight: '34px', padding: '6px 9px', textAlign: 'right',
                      fontSize: '11.5px', fontVariantNumeric: 'tabular-nums', ...styleFor(cell.kind) }}>{CAL_MON[cell.d.getMonth()]} {cell.d.getDate()}</div>
                  ))}
                </div>
              ))}
            </div>}
        {footnote && <div style={{ marginTop: '12px', fontSize: '11.5px', color: '#B8922A', display: 'flex', alignItems: 'center', gap: '7px' }}>
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ flexShrink: 0 }}>
            <path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
            <line x1="12" y1="9" x2="12" y2="13" /><line x1="12" y1="17" x2="12.01" y2="17" />
          </svg>{footnote}</div>}
      </div>
    </Modal>
  );
}

function SettingsSection() {
  const [tab, setTab] = useOS('Campuses');
  const TABS = ['Campuses', 'Courses', 'Business', 'Discounts', 'Terms', 'Users', 'Integrations', 'Audit log'];

  return (
    <div>
      <SectionHeader title="Settings" subtitle="Super-admin access only" />
      <Tabs tabs={TABS} active={tab} onChange={setTab} />

      {tab === 'Campuses' && (
        <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
          {['Parramatta', 'Bella Vista'].map(c => (
            <div key={c} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between',
              padding: '14px 16px', background: 'var(--bg-surface)', borderRadius: '8px', border: '1px solid var(--border-soft)' }}>
              <span style={{ fontSize: '14px', fontWeight: 500, color: 'var(--text-primary)' }}>{c}</span>
              <div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
                <Badge status="enrolled" label="Active" />
                <Button size="sm" variant="ghost">Edit</Button>
              </div>
            </div>
          ))}
          <Button variant="secondary" style={{ alignSelf: 'flex-start', marginTop: '4px' }}>+ Add campus</Button>
        </div>
      )}

      {tab === 'Courses' && (
        <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
          {['YR10','YR11 ADVN','YR11 EXT1','YR12 ADVN','YR12 EXT1','YR12 EXT2'].map(c => (
            <div key={c} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between',
              padding: '12px 16px', background: 'var(--bg-surface)', borderRadius: '8px', border: '1px solid var(--border-soft)' }}>
              <span style={{ fontSize: '13px', fontWeight: 600, color: 'var(--text-primary)', fontFamily: 'monospace' }}>{c}</span>
              <Button size="sm" variant="ghost">Edit</Button>
            </div>
          ))}
        </div>
      )}

      {tab === 'Business' && <BusinessSettings />}

      {tab === 'Discounts' && <DiscountsSettings />}

      {tab === 'Terms' && <TermsSettings />}

      {tab === 'Curriculum' && (
        <div>
          <div style={{ marginBottom: '16px' }}>
            <TMSelect label="Course" value="YR11 ADVN" onChange={() => {}} options={['YR10','YR11 ADVN','YR11 EXT1','YR12 ADVN','YR12 EXT1','YR12 EXT2']} style={{ width: '220px' }} />
          </div>
          <div style={{ borderRadius: '8px', border: '1px solid var(--border-soft)', overflow: 'hidden' }}>
            <div style={{ display: 'grid', gridTemplateColumns: '48px 130px 1fr 1fr 120px', background: 'var(--bg-surface-muted)',
              padding: '9px 16px', borderBottom: '1px solid var(--border-soft)', fontSize: '11px', fontWeight: 500,
              color: 'var(--text-secondary)', letterSpacing: '0.05em', textTransform: 'uppercase' }}>
              {['Wk','Lesson code','Topic','Subtopic','Booklet'].map(h => <span key={h}>{h}</span>)}
            </div>
            {['Limits & Continuity','Derivatives (E)','Rates of Change (E)','Integration Basics','Applications of Calc','Related Rates','Newton\'s Method','Review','Exam Prep','Trial Exam'].map((sub, i) => (
              <div key={i} style={{ display: 'grid', gridTemplateColumns: '48px 130px 1fr 1fr 120px', padding: '10px 16px',
                alignItems: 'center', background: 'var(--bg-surface)', borderBottom: i < 9 ? '1px solid var(--border-soft)' : 'none', fontSize: '12px' }}>
                <span style={{ color: 'var(--text-muted)', fontWeight: 500 }}>{i + 1}</span>
                <span style={{ color: 'var(--text-muted)', fontFamily: 'monospace', fontSize: '11px' }}>ADVN-REG-B1-{14 + i + 1}</span>
                <span style={{ color: 'var(--text-secondary)' }}>Calculus</span>
                <span style={{ color: 'var(--text-primary)' }}>{sub}</span>
                <span style={{ color: 'var(--text-muted)' }}>Booklet 5 of 6</span>
              </div>
            ))}
          </div>
        </div>
      )}

      {tab === 'Users' && (
        <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
          {[
            { name: 'Maya Rodriguez', email: 'maya@themathologists.com.au', role: 'Reception', campus: 'Parramatta' },
            { name: 'Jamie Liu',      email: 'jamie@themathologists.com.au', role: 'Super-admin', campus: 'Both' },
            { name: 'Taylor Kim',     email: 'taylor@themathologists.com.au', role: 'Reception', campus: 'Bella Vista' },
          ].map(u => (
            <div key={u.email} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between',
              padding: '12px 16px', background: 'var(--bg-surface)', borderRadius: '8px', border: '1px solid var(--border-soft)' }}>
              <div style={{ display: 'flex', gap: '12px', alignItems: 'center' }}>
                <Avatar name={u.name} size={32} />
                <div>
                  <div style={{ fontSize: '13px', fontWeight: 500, color: 'var(--text-primary)' }}>{u.name}</div>
                  <div style={{ fontSize: '12px', color: 'var(--text-muted)' }}>{u.email}</div>
                </div>
              </div>
              <div style={{ display: 'flex', gap: '10px', alignItems: 'center' }}>
                <span style={{ fontSize: '12px', color: 'var(--text-secondary)' }}>{u.role} · {u.campus}</span>
                <Button size="sm" variant="ghost">Edit</Button>
              </div>
            </div>
          ))}
          <Button variant="secondary" style={{ alignSelf: 'flex-start', marginTop: '4px' }}>+ Invite user</Button>
        </div>
      )}

      {tab === 'Integrations' && (
        <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
          {[
            { name: 'Google Drive',  status: 'Connected',  detail: 'TM Workspace · Last synced 4 min ago', ok: true },
            { name: 'PrintNode',     status: 'Connected',  detail: '2 printers active (Parra + Bella Vista)', ok: true },
            { name: 'MessageMedia',  status: 'Connected',  detail: 'AU SMS gateway active', ok: true },
            { name: 'Stripe',        status: 'Phase 2',    detail: 'Not configured — Invoice automation in Phase 2', ok: false },
            { name: 'ReckonOne',     status: 'Phase 2',    detail: 'Not configured — Invoice sync in Phase 2', ok: false },
          ].map(item => (
            <div key={item.name} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between',
              padding: '14px 16px', background: 'var(--bg-surface)', borderRadius: '8px', border: '1px solid var(--border-soft)' }}>
              <div>
                <div style={{ fontSize: '13px', fontWeight: 500, color: 'var(--text-primary)' }}>{item.name}</div>
                <div style={{ fontSize: '12px', color: 'var(--text-muted)', marginTop: '2px' }}>{item.detail}</div>
              </div>
              <div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
                <span style={{ fontSize: '12px', fontWeight: 500, color: item.ok ? '#4A9B7E' : '#7A7367' }}>{item.status}</span>
                {item.ok && <Button size="sm" variant="ghost">Configure</Button>}
              </div>
            </div>
          ))}
        </div>
      )}

      {tab === 'Audit log' && (
        <div>
          <div style={{ marginBottom: '14px', position: 'relative', display: 'inline-block' }}>
            <Icon name="search" size={14} color="var(--text-muted)" style={{ position: 'absolute', left: 10, top: '50%', transform: 'translateY(-50%)' }} />
            <input placeholder="Search audit log…" style={{ background: 'var(--bg-surface-muted)', border: '1px solid var(--border-default)',
              borderRadius: '4px', padding: '7px 12px 7px 32px', fontSize: '13px', color: 'var(--text-primary)',
              fontFamily: 'inherit', outline: 'none', width: '320px' }} />
          </div>
          <div style={{ borderRadius: '8px', border: '1px solid var(--border-soft)', overflow: 'hidden' }}>
            {SAMPLE_AUDIT.map((e, i) => (
              <div key={i} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center',
                padding: '12px 16px', borderBottom: i < SAMPLE_AUDIT.length - 1 ? '1px solid var(--border-soft)' : 'none',
                background: 'var(--bg-surface)', fontSize: '13px' }}>
                <div style={{ display: 'flex', gap: '10px', alignItems: 'center' }}>
                  <Avatar name={e.user} size={24} />
                  <span>
                    <strong style={{ color: 'var(--text-primary)', fontWeight: 500 }}>{e.user}</strong>
                    <span style={{ color: 'var(--text-secondary)' }}> {e.action} </span>
                    <strong style={{ color: 'var(--text-primary)', fontWeight: 500 }}>{e.record}</strong>
                    <span style={{ color: 'var(--text-muted)' }}>  — {e.detail}</span>
                  </span>
                </div>
                <span style={{ fontSize: '12px', color: 'var(--text-muted)', whiteSpace: 'nowrap', marginLeft: '16px' }}>{e.time}</span>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// FORMS — public sign-up forms that feed into Customers
// ─────────────────────────────────────────────────────────────

let _formFieldSeq = 1000;
const newFieldId = () => `ff${++_formFieldSeq}`;

// Locked minimum contact fields every holiday form must capture (can't be edited or removed).
const MIN_HOLIDAY_FIELDS = () => [
  { id: newFieldId(), type: 'heading', label: 'Your details', locked: true },
  { id: newFieldId(), type: 'text',   label: 'First name',  required: true, enabled: true, locked: true },
  { id: newFieldId(), type: 'text',   label: 'Last name',   required: true, enabled: true, locked: true },
  { id: newFieldId(), type: 'select', label: 'Year level',  required: true, enabled: true, locked: true, options: ['Year 9', 'Year 10', 'Year 11', 'Year 12'] },
  { id: newFieldId(), type: 'tel',    label: 'Mobile',      required: true, enabled: true, locked: true },
  { id: newFieldId(), type: 'email',  label: 'Email',       required: true, enabled: true, locked: true },
];

const slugifyForm = s => (s || '').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');

// The single, standalone trial form.
const TRIAL_FORM = {
  id: 'form-trial', kind: 'trial', name: 'Book a Free Trial', hosted: true, slug: 'book-trial',
  description: '',
  submitLabel: 'Book my free trial',
  fields: [
    { id: 't1',  type: 'heading', label: 'Student details' },
    { id: 't2',  type: 'text',   label: 'First name',        required: true,  enabled: true, role: 'firstname' },
    { id: 't3',  type: 'text',   label: 'Last name',         required: true,  enabled: true, role: 'lastname' },
    { id: 't9',  type: 'tel',    label: 'Mobile',            required: true,  enabled: true },
    { id: 't10', type: 'email',  label: 'Email',             required: true,  enabled: true },
    { id: 't6',  type: 'text',   label: 'School',            required: false, enabled: true },
    { id: 't4',  type: 'select', label: 'Year level',        required: true,  enabled: true, role: 'year',   options: ['Year 9', 'Year 10', 'Year 11', 'Year 12'] },
    { id: 't5',  type: 'select', label: 'Course',            required: true,  enabled: true, role: 'course', options: ['Standard', 'Advanced', 'Extension 1', 'Extension 2'] },
    { id: 't11', type: 'heading', label: 'Parent / guardian details' },
    { id: 't12', type: 'text',   label: 'Parent first name', required: true,  enabled: true },
    { id: 't13', type: 'text',   label: 'Parent last name',  required: true,  enabled: true },
    { id: 't14', type: 'tel',    label: 'Parent mobile',     required: true,  enabled: true },
    { id: 't15', type: 'email',  label: 'Parent email',      required: true,  enabled: true },
    { id: 't16', type: 'heading', label: 'Trial' },
    { id: 't7',  type: 'select', label: 'Preferred campus',  required: true,  enabled: true, role: 'campus', options: ['Parramatta', 'Bella Vista'] },
    { id: 't8',  type: 'date',   label: 'Preferred trial date', required: false, enabled: true, role: 'trialdate' },
  ],
};

// The standalone absence-credit request form (F7). Parents request a credit for a missed
// lesson; reception approves it (and the system flags claims where attendance shows present).
const CREDIT_FORM = {
  id: 'form-credit', kind: 'credit', name: 'Request a Lesson Credit', hosted: true, slug: 'request-credit',
  description: 'For families requesting a credit for a missed lesson. One credit per term; each request is reviewed by reception before approval.',
  submitLabel: 'Submit credit request',
  fields: [
    { id: 'cr1', type: 'heading', label: 'Student details' },
    { id: 'cr2', type: 'text',   label: 'First name', required: true, enabled: true, role: 'firstname' },
    { id: 'cr3', type: 'text',   label: 'Last name',  required: true, enabled: true, role: 'lastname' },
    { id: 'cr11', type: 'heading', label: 'Parent details' },
    { id: 'cr12', type: 'text',  label: 'First name', required: true, enabled: true },
    { id: 'cr13', type: 'text',  label: 'Last name',  required: true, enabled: true },
    { id: 'cr5', type: 'heading', label: 'Absence details' },
    { id: 'cr8', type: 'date',   label: 'Date of missed lesson', required: true, enabled: true },
    { id: 'cr9', type: 'select', label: 'Reason for absence',    required: true, enabled: true, options: ['Illness', 'Family emergency', 'School commitment', 'Other'] },
  ],
};

const HOLIDAY_FORM_META = {
  submitLabel: 'Sign up for the program',
  description: 'Sign-up form for this holiday program. The minimum contact fields are locked; add your own program-specific questions below.',
};

// Each holiday program owns its sign-up form: a URL slug, an editable description,
// and the locked minimum contact fields plus any program-specific questions.
// Programs themselves are created/managed on the Classes page.
// Module-level stores so form edits persist across navigation / FormsSection remounts.
// The program *catalogue* lives in the shared SAMPLE_HOLIDAY_PROGRAMS global (managed on
// the Classes page); each program's form config is keyed by program id here.
const HOLIDAY_FORMS = {};
function holidayFormFor(p) {
  if (!HOLIDAY_FORMS[p.id]) {
    const fields = MIN_HOLIDAY_FIELDS();
    if (p.id === 'hp1') fields.push({ id: newFieldId(), type: 'select', label: 'Which exam(s)?', required: true, enabled: true, options: ['Advanced', 'Extension 1', 'Both'] });
    HOLIDAY_FORMS[p.id] = { slug: slugifyForm(p.shortName || p.name), description: HOLIDAY_FORM_META.description, fields };
  }
  return HOLIDAY_FORMS[p.id];
}

let TRIAL_FORM_STORE = null;
function trialFormStore() {
  if (!TRIAL_FORM_STORE) TRIAL_FORM_STORE = { ...TRIAL_FORM, fields: TRIAL_FORM.fields.map(x => ({ ...x })) };
  return TRIAL_FORM_STORE;
}
let CREDIT_FORM_STORE = null;
function creditFormStore() {
  if (!CREDIT_FORM_STORE) CREDIT_FORM_STORE = { ...CREDIT_FORM, fields: CREDIT_FORM.fields.map(x => ({ ...x })) };
  return CREDIT_FORM_STORE;
}

const FIELD_TYPES = ['text', 'email', 'tel', 'select', 'date'];

// Interactive dropdown for the live preview — opens and lets you pick a choice.
function PreviewSelect({ options, placeholder, value, onChange }) {
  const controlled = value !== undefined;
  const [open, setOpen] = React.useState(false);
  const [internal, setInternal] = React.useState('');
  const sel = controlled ? value : internal;
  const setSel = v => { if (controlled) { onChange && onChange(v); } else setInternal(v); };
  const ref = React.useRef(null);
  const opts = options || [];
  React.useEffect(() => {
    if (!open) return;
    const close = e => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', close);
    return () => document.removeEventListener('mousedown', close);
  }, [open]);
  React.useEffect(() => { if (!controlled && internal && !opts.includes(internal)) setInternal(''); }, [options]);
  return (
    <div ref={ref} style={{ position: 'relative' }}>
      <div onClick={() => setOpen(o => !o)}
        style={{ background: 'var(--bg-surface-muted)', border: `1px solid ${open ? '#1F4D3D' : 'var(--border-default)'}`, borderRadius: '4px', height: '34px',
          display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0 10px', fontSize: '12px', cursor: 'pointer',
          color: sel ? 'var(--text-primary)' : 'var(--text-muted)', boxShadow: open ? '0 0 0 3px rgba(31,77,61,0.2)' : 'none' }}>
        {sel || placeholder || 'Select…'}
        <DropdownChevron open={open} />
      </div>
      {open && (
        <div style={{ position: 'absolute', top: 'calc(100% + 4px)', left: 0, right: 0, zIndex: 50, background: 'var(--bg-elevated)',
          border: '1px solid var(--border-default)', borderRadius: '6px', boxShadow: '0 8px 24px rgba(0,0,0,0.5)', padding: '4px', maxHeight: '200px', overflowY: 'auto' }}>
          {opts.length === 0 && <div style={{ padding: '7px 10px', fontSize: '12px', color: 'var(--text-muted)' }}>No options yet</div>}
          {opts.map(o => {
            const isSel = o === sel;
            return (
              <div key={o} onClick={() => { setSel(o); setOpen(false); }}
                style={{ padding: '7px 10px', fontSize: '12px', borderRadius: '4px', cursor: 'pointer', display: 'flex', justifyContent: 'space-between', gap: '8px',
                  color: isSel ? '#4A9B7E' : 'var(--text-primary)', background: isSel ? 'rgba(74,155,126,0.1)' : 'transparent' }}
                onMouseEnter={e => { if (!isSel) e.currentTarget.style.background = 'rgba(255,255,255,0.06)'; }}
                onMouseLeave={e => { if (!isSel) e.currentTarget.style.background = 'transparent'; }}>
                <span>{o}</span>
                {isSel && <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" style={{ flexShrink: 0 }}><path d="M20 6 9 17l-5-5" /></svg>}
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

// ── Trial form: conditional fields + a class-aware date picker (live preview) ──
const TRIAL_YEAR_CODE   = { 'Year 9': 'YR09', 'Year 10': 'YR10', 'Year 11': 'YR11', 'Year 12': 'YR12' };
const TRIAL_COURSE_CODE = { 'Standard': 'STD', 'Advanced': 'ADVN', 'Extension 1': 'EXT1', 'Extension 2': 'EXT2' };
// Does a term class suit this student's year (+ course for Year 11/12)?
function trialClassMatches(cls, year, course) {
  const yc = TRIAL_YEAR_CODE[year];
  if (!yc) return true;                                   // no year chosen yet → any class at the campus
  if (yc === 'YR09' || yc === 'YR10') return cls.course === yc;
  const cc = TRIAL_COURSE_CODE[course];
  if (!cc) return cls.course.indexOf(yc) === 0;           // year chosen, course not → any of that year
  return cls.course === `${yc} ${cc}`;
}
const trialClassLabel = c => `${(c.course || '').replace('YR', 'Y')} · ${c.day.slice(0, 3)} ${c.startTime}–${c.endTime}`;
const DOW_INDEX = { Sunday: 0, Monday: 1, Tuesday: 2, Wednesday: 3, Thursday: 4, Friday: 5, Saturday: 6 };
// '4:30pm' → minutes since midnight, for sorting time slots earliest-first.
function trialTimeMins(t) {
  const mt = /^(\d+):(\d+)\s*(am|pm)$/i.exec((t || '').trim());
  if (!mt) return 0;
  let h = parseInt(mt[1], 10) % 12;
  if (/pm/i.test(mt[3])) h += 12;
  return h * 60 + parseInt(mt[2], 10);
}

// Calendar from the current week to month-end. Days are greyed unless a suitable
// class runs that weekday at the chosen campus; today is ringed.
function TrialDatePicker({ campus, year, course, picked, onPick, onSlot, onClear }) {
  const today = new Date(); today.setHours(0, 0, 0, 0);
  const m = today.getMonth(), y = today.getFullYear();
  const suitable = SAMPLE_CLASSES.filter(c => c.type === 'term' && !c.archived && c.campus === campus && trialClassMatches(c, year, course));
  const byDow = {};
  suitable.forEach(c => { const d = DOW_INDEX[c.day]; if (d != null) (byDow[d] = byDow[d] || []).push(c); });

  if (picked) {
    const pd = new Date(picked.iso + 'T00:00:00');
    return (
      <div style={{ border: '1px solid rgba(74,155,126,0.45)', borderRadius: '8px', padding: '12px 14px' }}>
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '10px' }}>
          <span style={{ display: 'inline-flex', alignItems: 'center', gap: '7px', fontSize: '13px', fontWeight: 600, color: 'var(--text-primary)' }}>
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#4A9B7E" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"><path d="M20 6 9 17l-5-5" /></svg>
            {pd.toLocaleDateString('en-AU', { weekday: 'long', day: 'numeric', month: 'long' })}
          </span>
          <button onClick={onClear} style={{ background: 'none', border: 'none', cursor: 'pointer', fontFamily: 'inherit', fontSize: '12px', fontWeight: 600, color: '#4A9B7E', textDecoration: 'underline' }}>Change date</button>
        </div>
        <div style={{ fontSize: '11px', fontWeight: 600, letterSpacing: '0.03em', textTransform: 'uppercase', color: 'var(--text-muted)', margin: '12px 0 7px' }}>Choose a time</div>
        <div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
          {picked.classes.map(c => {
            const on = c.id === picked.slotId;
            return (
              <button key={c.id} type="button" onClick={() => onSlot(c.id)} style={{
                display: 'flex', alignItems: 'center', gap: '10px', width: '100%', textAlign: 'left', fontFamily: 'inherit', cursor: 'pointer', transition: '100ms ease',
                background: on ? 'rgba(74,155,126,0.16)' : 'var(--bg-elevated)',
                border: `1px solid ${on ? '#4A9B7E' : 'var(--border-default)'}`, borderRadius: '7px', padding: '9px 11px' }}>
                <span style={{ width: 16, height: 16, flexShrink: 0, borderRadius: '50%', border: `2px solid ${on ? '#4A9B7E' : 'var(--border-default)'}`, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                  {on && <span style={{ width: 8, height: 8, borderRadius: '50%', background: '#4A9B7E' }} />}
                </span>
                <span style={{ flex: 1, minWidth: 0, fontSize: '13px', fontWeight: 600, color: 'var(--text-primary)' }}>{c.startTime} – {c.endTime}</span>
              </button>
            );
          })}
        </div>
      </div>
    );
  }

  const startOfWeek = new Date(today);
  const wd = today.getDay();
  startOfWeek.setDate(today.getDate() + (wd === 0 ? -6 : 1 - wd));   // back to Monday
  const monthEnd = new Date(y, m + 1, 0);
  const cells = [];
  for (let cur = new Date(startOfWeek); cur <= monthEnd; cur.setDate(cur.getDate() + 1)) cells.push(new Date(cur));
  while (cells.length % 7 !== 0) cells.push(null);

  return (
    <div style={{ border: '1px solid var(--border-default)', background: 'var(--bg-surface-muted)', borderRadius: '10px', padding: '12px' }}>
      <div style={{ fontSize: '13px', fontWeight: 700, color: 'var(--text-primary)', textAlign: 'center', marginBottom: '10px' }}>{today.toLocaleDateString('en-AU', { month: 'long', year: 'numeric' })}</div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: '4px' }}>
        {['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'].map(h => (
          <div key={h} style={{ fontSize: '10px', fontWeight: 700, color: 'var(--text-muted)', textAlign: 'center', padding: '2px 0' }}>{h}</div>
        ))}
        {cells.map((d, i) => {
          if (!d || d.getMonth() !== m) return <div key={i} />;
          const classes = byDow[d.getDay()] || [];
          const enabled = d > today && classes.length > 0;   // tomorrow onwards only
          const iso = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
          return (
            <button key={i} disabled={!enabled} onClick={() => enabled && onPick({ iso, classes: classes.slice().sort((a, b) => trialTimeMins(a.startTime) - trialTimeMins(b.startTime)) })}
              title={enabled ? classes.map(trialClassLabel).join('\n') : undefined}
              style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: '3px', height: '40px',
                background: 'transparent', border: 'none', borderRadius: '7px', fontFamily: 'inherit', cursor: enabled ? 'pointer' : 'default', padding: 0 }}>
              <span style={{ width: 26, height: 26, borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '12.5px',
                border: `1.5px solid ${enabled ? '#4A9B7E' : 'transparent'}`,
                color: enabled ? '#4A9B7E' : 'var(--text-muted)', fontWeight: enabled ? 700 : 400 }}>{d.getDate()}</span>
              <span style={{ width: 4, height: 4, borderRadius: '50%', background: enabled ? '#4A9B7E' : 'transparent' }} />
            </button>
          );
        })}
      </div>
      {suitable.length === 0 && <div style={{ fontSize: '12px', color: '#C9A227', marginTop: '10px' }}>No suitable classes at this campus — try the other campus.</div>}
    </div>
  );
}

// Single-click option chips (no dropdown) for year/course/campus on the public form.
function OptionPills({ options, value, onChange }) {
  return (
    <div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
      {(options || []).map(o => {
        const on = value === o;
        return (
          <button key={o} type="button" onClick={() => onChange(o)} style={{
            fontFamily: 'inherit', cursor: 'pointer', fontSize: '12.5px', fontWeight: on ? 600 : 500, transition: '100ms ease',
            padding: '7px 13px', borderRadius: '999px',
            border: `1px solid ${on ? '#4A9B7E' : 'var(--border-default)'}`,
            background: on ? 'rgba(74,155,126,0.15)' : 'var(--bg-surface-muted)',
            color: on ? '#4A9B7E' : 'var(--text-secondary)' }}>{o}</button>
        );
      })}
    </div>
  );
}

// Interactive public preview of the trial form: a two-step wizard (Student → Parent)
// with a progress stepper, single-click option chips, conditional Course (Yr 11/12 only)
// and a class-aware date picker that appears once a campus is chosen.
function TrialFormPreview({ fields, submitLabel }) {
  const [vals, setVals]     = React.useState({});
  const [picked, setPicked] = React.useState(null);
  const [dateChoice, setDateChoice] = React.useState('');   // '' (choose how) | 'pick' | 'contact'
  const [step, setStep]     = React.useState(0);
  const [submitted, setSubmitted] = React.useState(false);
  const get = id => vals[id] || '';
  const set = (id, v) => setVals(s => ({ ...s, [id]: v }));
  const onPickDate = ({ iso, classes }) => setPicked({ iso, classes, slotId: classes[0] ? classes[0].id : null });
  const onSlot = id => setPicked(p => (p ? { ...p, slotId: id } : p));

  const yearField   = fields.find(f => f.role === 'year');
  const campusField = fields.find(f => f.role === 'campus');
  const courseField = fields.find(f => f.role === 'course');
  const firstField  = fields.find(f => f.role === 'firstname');
  const lastField   = fields.find(f => f.role === 'lastname');
  const year   = yearField   ? get(yearField.id)   : '';
  const campus = campusField ? get(campusField.id) : '';
  const course = courseField ? get(courseField.id) : '';
  const showCourse = year === 'Year 11' || year === 'Year 12';
  const isRole = fl => fl.role === 'year' || fl.role === 'campus' || fl.role === 'course';

  React.useEffect(() => { setPicked(null); setDateChoice(''); }, [campus, year, course]);

  // Group fields into steps, one per section heading (Student / Parent / Trial).
  const steps = [];
  fields.forEach(f => {
    if (f.type === 'heading') steps.push({ heading: f, fields: [] });
    else { if (!steps.length) steps.push({ heading: null, fields: [] }); steps[steps.length - 1].fields.push(f); }
  });
  const safeStep = Math.max(0, Math.min(step, steps.length - 1));
  const cur = steps[safeStep] || { fields: [] };
  const isLast = safeStep === steps.length - 1;

  // Smart field gating — a required, visible field must have a value.
  const fieldOk = fl => {
    if (fl.enabled === false) return true;
    if (!fl.required) return true;
    if (fl.role === 'course' && !showCourse) return true;   // hidden for Yr 9/10
    return !!get(fl.id);
  };
  const curComplete = cur.fields.every(fieldOk);

  // Recap data
  const studentName = `${firstField ? get(firstField.id) : ''} ${lastField ? get(lastField.id) : ''}`.trim();
  const yearText = year ? `${year}${showCourse && course ? ` · ${course}` : ''}` : '';
  const slot = picked ? picked.classes.find(c => c.id === picked.slotId) : null;
  const trialSummary = slot
    ? `${new Date(picked.iso + 'T00:00:00').toLocaleDateString('en-AU', { weekday: 'short', day: 'numeric', month: 'short' })} · ${slot.startTime}–${slot.endTime}`
    : 'We’ll contact you to arrange a time';

  const inputStyle = { width: '100%', background: 'var(--bg-surface-muted)', border: '1px solid var(--border-default)', borderRadius: '4px', height: '34px', padding: '0 10px', fontSize: '13px', color: 'var(--text-primary)', fontFamily: 'inherit', outline: 'none' };
  const recapRow = (label, value) => (
    <div style={{ display: 'flex', justifyContent: 'space-between', gap: '12px', padding: '5px 0', fontSize: '12.5px' }}>
      <span style={{ color: 'var(--text-muted)' }}>{label}</span>
      <span style={{ color: 'var(--text-primary)', fontWeight: 600, textAlign: 'right' }}>{value || '—'}</span>
    </div>
  );
  const recapCard = (
    <div style={{ border: '1px solid var(--border-default)', background: 'var(--bg-surface-muted)', borderRadius: '8px', padding: '6px 14px' }}>
      {recapRow('Student', studentName)}
      {recapRow('Year', yearText)}
      {recapRow('Campus', campus)}
      {recapRow('Time', trialSummary)}
    </div>
  );

  // Success screen after submitting.
  if (submitted) {
    return (
      <div style={{ textAlign: 'center', padding: '14px 6px' }}>
        <span style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 52, height: 52, borderRadius: '50%', background: 'rgba(74,155,126,0.15)', border: '1px solid rgba(74,155,126,0.4)', marginBottom: '14px' }}>
          <svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="#4A9B7E" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"><path d="M20 6 9 17l-5-5" /></svg>
        </span>
        <div style={{ fontSize: '17px', fontWeight: 700, color: 'var(--text-primary)' }}>{slot ? 'You’re booked in! 🎉' : 'Request received! 🎉'}</div>
        <div style={{ fontSize: '13px', color: 'var(--text-secondary)', margin: '7px auto 18px', maxWidth: '320px', lineHeight: 1.5 }}>
          {slot
            ? `We’ve saved ${studentName || 'your'} trial for ${trialSummary} at ${campus}. A confirmation email is on its way.`
            : `Thanks${studentName ? `, ${studentName.split(' ')[0]}` : ''}! We’ll be in touch shortly to arrange a trial time${campus ? ` at ${campus}` : ''}.`}
        </div>
        <div style={{ textAlign: 'left', maxWidth: '320px', margin: '0 auto' }}>{recapCard}</div>
        <button type="button" onClick={() => setSubmitted(false)} style={{ marginTop: '16px', background: 'none', border: 'none', cursor: 'pointer', fontFamily: 'inherit', fontSize: '12px', fontWeight: 600, color: 'var(--text-muted)', textDecoration: 'underline' }}>Back to form</button>
      </div>
    );
  }

  const renderField = fl => {
    if (fl.enabled === false) return null;
    if (fl.role === 'course' && !showCourse) return null;        // Course hidden for Year 9/10
    if (fl.role === 'trialdate' && !campus) return null;         // date appears only after campus chosen

    // Preferred trial date — optional. After campus, the customer first chooses how:
    // pick a time now (→ calendar + time slots) or be contacted instead.
    if (fl.role === 'trialdate') {
      const choiceCard = (onClick, icon, iconColor, title) => (
        <button type="button" onClick={onClick}
          onMouseEnter={e => { e.currentTarget.style.borderColor = 'rgba(74,155,126,0.5)'; }}
          onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--border-default)'; }}
          style={{ display: 'flex', alignItems: 'center', gap: '9px', textAlign: 'left', cursor: 'pointer', fontFamily: 'inherit', transition: '100ms ease',
            background: 'var(--bg-surface-muted)', border: '1px solid var(--border-default)', borderRadius: '8px', padding: '12px 13px' }}>
          <Icon name={icon} size={17} color={iconColor} />
          <span style={{ fontSize: '13px', fontWeight: 600, color: 'var(--text-primary)' }}>{title}</span>
        </button>
      );
      return (
        <div key={fl.id} style={{ marginBottom: '14px' }}>
          {dateChoice === '' && (
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '8px' }}>
              {choiceCard(() => setDateChoice('pick'), 'calendar', '#4A9B7E', 'Pick a date now')}
              {choiceCard(() => setDateChoice('contact'), 'emails', 'var(--text-muted)', 'I’m not sure yet')}
            </div>
          )}

          {dateChoice === 'pick' && (
            <React.Fragment>
              <TrialDatePicker campus={campus} year={year} course={course} picked={picked} onPick={onPickDate} onSlot={onSlot} onClear={() => setPicked(null)} />
              {!picked && (
                <button type="button" onClick={() => { setPicked(null); setDateChoice('contact'); }}
                  style={{ marginTop: '10px', width: '100%', background: 'rgba(201,162,39,0.08)', border: '1px dashed #C9A227', borderRadius: '8px', padding: '8px', fontSize: '12px', fontWeight: 600, color: '#C9A227', cursor: 'pointer', fontFamily: 'inherit' }}>
                  Not sure? We’ll contact you instead
                </button>
              )}
            </React.Fragment>
          )}

          {dateChoice === 'contact' && (
            <div style={{ display: 'flex', alignItems: 'flex-start', gap: '9px', border: '1px solid var(--border-default)', background: 'var(--bg-surface-muted)', borderRadius: '8px', padding: '12px 14px' }}>
              <Icon name="emails" size={15} color="#4A9B7E" style={{ marginTop: '1px' }} />
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontSize: '12.5px', color: 'var(--text-primary)' }}>No problem — we’ll contact you to arrange a time!</div>
                <button type="button" onClick={() => setDateChoice('pick')} style={{ background: 'none', border: 'none', cursor: 'pointer', fontFamily: 'inherit', fontSize: '12px', fontWeight: 600, color: '#4A9B7E', textDecoration: 'underline', padding: '6px 0 0' }}>Pick a time now instead</button>
              </div>
            </div>
          )}
        </div>
      );
    }

    return (
      <div key={fl.id} style={{ marginBottom: '14px' }}>
        <label style={{ display: 'block', fontSize: '12px', color: 'var(--text-secondary)', marginBottom: '6px' }}>
          {fl.label}{fl.required && <span style={{ color: '#C05656' }}> *</span>}
        </label>
        {isRole(fl)
          ? <OptionPills options={fl.options} value={get(fl.id)} onChange={v => set(fl.id, v)} />
          : fl.type === 'select'
            ? <PreviewSelect options={fl.options} placeholder="Select…" />
            : <input type={fl.type === 'email' ? 'email' : fl.type === 'tel' ? 'tel' : 'text'} value={get(fl.id)} onChange={e => set(fl.id, e.target.value)}
                placeholder={fl.type === 'tel' ? '04XX XXX XXX' : fl.type === 'email' ? 'name@email.com' : ''} style={inputStyle} />}
      </div>
    );
  };

  const navBtn = { background: 'var(--bg-surface-muted)', border: '1px solid var(--border-default)', borderRadius: '6px', padding: '7px 14px', fontSize: '13px', fontWeight: 600, color: 'var(--text-secondary)', cursor: 'pointer', fontFamily: 'inherit', flexShrink: 0 };
  const primaryBtn = { flex: 1, background: 'var(--brand)', border: 'none', borderRadius: '6px', padding: '7px', fontSize: '13px', fontWeight: 600, color: '#fff', fontFamily: 'inherit' };

  return (
    <React.Fragment>
      {/* Progress stepper */}
      <div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '20px' }}>
        {steps.map((s, i) => (
          <React.Fragment key={i}>
            <button type="button" onClick={() => setStep(i)} title={s.heading ? s.heading.label : `Step ${i + 1}`}
              style={{ display: 'flex', alignItems: 'center', gap: '7px', background: 'none', border: 'none', padding: 0, cursor: 'pointer', fontFamily: 'inherit', minWidth: 0, flexShrink: 1 }}>
              <span style={{ width: 22, height: 22, flexShrink: 0, borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '11px', fontWeight: 700,
                background: i <= safeStep ? 'var(--brand)' : 'var(--bg-surface-muted)', color: i <= safeStep ? '#FBFAF6' : 'var(--text-muted)',
                border: i <= safeStep ? 'none' : '1px solid var(--border-default)' }}>{i + 1}</span>
              <span style={{ fontSize: '12px', fontWeight: i === safeStep ? 700 : 500, color: i === safeStep ? 'var(--text-primary)' : 'var(--text-muted)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{s.heading ? s.heading.label : `Step ${i + 1}`}</span>
            </button>
            {i < steps.length - 1 && <span style={{ flex: 1, height: '2px', minWidth: '10px', borderRadius: '1px', background: i < safeStep ? 'var(--brand)' : 'var(--border-default)' }} />}
          </React.Fragment>
        ))}
      </div>

      {cur.fields.map(renderField)}

      {isLast && (picked || dateChoice === 'contact') && <div style={{ marginTop: '4px' }}>{recapCard}</div>}

      <div style={{ display: 'flex', gap: '8px', marginTop: '18px' }}>
        {safeStep > 0 && <button type="button" onClick={() => setStep(safeStep - 1)} style={navBtn}>← Back</button>}
        {isLast
          ? <button type="button" disabled={!curComplete} onClick={() => setSubmitted(true)}
              style={{ ...primaryBtn, cursor: curComplete ? 'pointer' : 'not-allowed', opacity: curComplete ? 1 : 0.5 }}>{submitLabel || 'Submit'}</button>
          : <button type="button" disabled={!curComplete} onClick={() => setStep(safeStep + 1)}
              style={{ ...primaryBtn, cursor: curComplete ? 'pointer' : 'not-allowed', opacity: curComplete ? 1 : 0.5 }}>Next →</button>}
      </div>
      {!curComplete && (
        <div style={{ fontSize: '11px', color: 'var(--text-muted)', marginTop: '8px', textAlign: 'center' }}>
          Complete the required fields (<span style={{ color: '#C05656' }}>*</span>) to continue.
        </div>
      )}
    </React.Fragment>
  );
}

// Drag-to-reorder grip handle (6-dot). Disabled handles (locked rows) show but don't drag.
function DragHandle({ disabled, onDragStart, onDragEnd }) {
  return (
    <span draggable={!disabled} onDragStart={disabled ? undefined : onDragStart} onDragEnd={disabled ? undefined : onDragEnd}
      title={disabled ? 'Locked — can’t be moved' : 'Drag to reorder'}
      style={{ width: 16, flexShrink: 0, display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
        cursor: disabled ? 'default' : 'grab', color: disabled ? 'var(--border-default)' : 'var(--text-muted)' }}>
      <svg width="11" height="15" viewBox="0 0 24 24" fill="currentColor" style={{ display: 'block' }}>
        <circle cx="9" cy="5" r="1.7" /><circle cx="15" cy="5" r="1.7" />
        <circle cx="9" cy="12" r="1.7" /><circle cx="15" cy="12" r="1.7" />
        <circle cx="9" cy="19" r="1.7" /><circle cx="15" cy="19" r="1.7" />
      </svg>
    </span>
  );
}

// Comma-separated options editor. Keeps the raw text locally so commas and
// spaces can be typed freely; only the parsed array is pushed upward.
function OptionsInput({ initial, onChange, style, placeholder }) {
  const [text, setText] = React.useState(initial);
  return (
    <input value={text} placeholder={placeholder} style={style}
      onChange={e => { const v = e.target.value; setText(v); onChange(v.split(',').map(s => s.trim()).filter(Boolean)); }} />
  );
}

function FormsSection() {
  // Live from the shared catalogue (Classes mutates this global, so new/renamed/archived programs flow through here).
  const programs = SAMPLE_HOLIDAY_PROGRAMS.filter(p => !p.archived);
  const [trialForm, setTrialForm] = useOS(trialFormStore);   // persisted in TRIAL_FORM_STORE
  const [creditForm, setCreditForm] = useOS(creditFormStore); // persisted in CREDIT_FORM_STORE
  const [view, setView]           = useOS('');               // '' | 'trial' | 'credit' | 'holiday' (nothing selected on first load)
  const [programId, setProgramId] = useOS('');               // no default — the user must pick a program to unlock the editor
  const [, setTick]               = useOS(0);                // bump to re-render after a holiday-form edit
  const bump = () => setTick(t => t + 1);
  const [copied, setCopied]     = useOS('');
  const [dragId, setDragId]     = useOS(null);   // field id being dragged
  const [dragOver, setDragOver] = useOS(null);   // { id, after } drop target

  // Selected program — null until the user explicitly chooses one (or if the chosen one was archived/removed in Classes).
  const program = view === 'holiday' ? (programs.find(p => p.id === programId) || null) : null;
  const hd = program ? holidayFormFor(program) : null;   // lazily created + persisted per program
  // The form currently being edited — the trial form, or the selected program's holiday form.
  const active = view === 'trial'
    ? trialForm
    : view === 'credit'
      ? creditForm
      : (program && hd)
        ? { id: `holiday:${program.id}`, kind: 'holiday', programId: program.id, name: program.name, hosted: true, slug: hd.slug, description: hd.description, submitLabel: HOLIDAY_FORM_META.submitLabel, fields: hd.fields }
        : null;

  // Number the section headings so the preview reads as distinct, ordered steps.
  const headingNums = {};
  { let n = 0; (active ? active.fields : []).forEach(fl => { if (fl.type === 'heading') headingNums[fl.id] = ++n; }); }

  const slugify  = slugifyForm;
  const copyLink = url => { try { navigator.clipboard.writeText(url); } catch (e) { /* clipboard unavailable */ } setCopied(url); setTimeout(() => setCopied(''), 1500); };

  // Slug is a draft: switching form/program auto-sets it; manual edits need a Confirm click.
  const [slugDraft, setSlugDraft] = useOS('');
  React.useEffect(() => { setSlugDraft(active ? (active.slug || '') : ''); }, [view, programId, active && active.slug]);
  const slugDirty = !!active && slugDraft !== (active.slug || '');
  const publicUrl = active ? `https://themathologists.com.au/${slugDraft || ''}` : '';

  // Route edits to the trial form, or the selected program's holiday form (both persisted in module stores).
  const setActiveForm = fn => {
    if (view === 'trial') { setTrialForm(f => { const n = fn(f); TRIAL_FORM_STORE = n; return n; }); return; }
    if (view === 'credit') { setCreditForm(f => { const n = fn(f); CREDIT_FORM_STORE = n; return n; }); return; }
    if (view === 'holiday' && program) {
      const cur = holidayFormFor(program);
      const next = fn({ kind: 'holiday', programId: program.id, name: program.name, hosted: true, slug: cur.slug, description: cur.description, submitLabel: HOLIDAY_FORM_META.submitLabel, fields: cur.fields });
      HOLIDAY_FORMS[program.id] = { slug: next.slug, description: next.description, fields: next.fields };
      bump();
    }
  };
  const updateField   = (fid, patch) => setActiveForm(f => ({ ...f, fields: f.fields.map(x => x.id === fid ? { ...x, ...patch } : x) }));
  const removeField   = fid => setActiveForm(f => ({ ...f, fields: f.fields.filter(x => x.id !== fid) }));
  // Drag-and-drop reordering ────────────────────────────────────────────────
  const reorderFields = (fromId, toId, after) => setActiveForm(f => {
    if (fromId === toId) return f;
    const arr = [...f.fields];
    const from = arr.findIndex(x => x.id === fromId);
    let to = arr.findIndex(x => x.id === toId);
    if (from < 0 || to < 0) return f;
    const [moved] = arr.splice(from, 1);
    to = arr.findIndex(x => x.id === toId);   // recompute after removal
    arr.splice(after ? to + 1 : to, 0, moved);
    return { ...f, fields: arr };
  });
  const onHandleDragStart = (e, id) => {
    setDragId(id);
    e.dataTransfer.effectAllowed = 'move';
    try { e.dataTransfer.setData('text/plain', id); } catch (_) { /* some browsers */ }
    const row = e.currentTarget.closest('[data-fieldrow]');
    if (row) { try { e.dataTransfer.setDragImage(row, 14, 14); } catch (_) {} }
  };
  const dropPos = e => { const r = e.currentTarget.getBoundingClientRect(); return (e.clientY - r.top) > r.height / 2; };
  const onRowDragOver = (e, fl) => {
    if (dragId == null || fl.locked || fl.id === dragId) return;
    e.preventDefault();
    e.dataTransfer.dropEffect = 'move';
    const after = dropPos(e);
    if (!dragOver || dragOver.id !== fl.id || dragOver.after !== after) setDragOver({ id: fl.id, after });
  };
  const onRowDrop = (e, fl) => {
    if (dragId != null && !fl.locked) { e.preventDefault(); reorderFields(dragId, fl.id, dropPos(e)); }
    setDragId(null); setDragOver(null);
  };
  const onAnyDragEnd = () => { setDragId(null); setDragOver(null); };
  const dropShadow = id => (dragOver && dragOver.id === id)
    ? (dragOver.after ? 'inset 0 -2px 0 0 var(--brand)' : 'inset 0 2px 0 0 var(--brand)') : undefined;
  const addField = () => setActiveForm(f => ({ ...f, fields: [...f.fields, { id: newFieldId(), type: 'select', label: 'New question', required: false, enabled: true, options: ['Option 1', 'Option 2'] }] }));
  const addHeading = () => setActiveForm(f => ({ ...f, fields: [...f.fields, { id: newFieldId(), type: 'heading', label: 'New section' }] }));

  const labelInput = {
    flex: 1, minWidth: 0, background: 'var(--bg-surface-muted)', border: '1px solid var(--border-default)',
    borderRadius: '4px', padding: '6px 9px', fontSize: '13px', color: 'var(--text-primary)', fontFamily: 'inherit', outline: 'none',
  };
  const iconBtn = { background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-muted)', padding: '4px 8px', fontSize: '19px', lineHeight: 1, fontFamily: 'inherit' };
  const previewBox = { background: 'var(--bg-surface-muted)', border: '1px solid var(--border-default)', borderRadius: '4px', height: '34px', display: 'flex', alignItems: 'center', padding: '0 10px', fontSize: '12px' };

  return (
    <div>
      <SectionHeader title="Forms" />

      {/* Pick which form to set up — the chevron + hover make the selection obvious */}
      <div style={{ display: 'flex', gap: '10px', marginBottom: '24px', flexWrap: 'wrap' }}>
        {[
          { key: 'trial',   name: 'Book a Free Trial', sub: `${trialForm.fields.filter(x => x.type !== 'heading' && x.enabled !== false).length} fields · Hosted` },
          { key: 'holiday', name: 'Holiday Program',   sub: `${programs.length} program${programs.length === 1 ? '' : 's'} · Hosted` },
          { key: 'credit',  name: 'Request a Lesson Credit', sub: `${creditForm.fields.filter(x => x.type !== 'heading' && x.enabled !== false).length} fields · Hosted` },
        ].map(t => {
          const on = view === t.key;
          return (
            <button key={t.key} onClick={() => setView(t.key)}
              onMouseEnter={e => { if (!on) { e.currentTarget.style.borderColor = 'rgba(31,77,61,0.5)'; e.currentTarget.style.background = 'var(--bg-elevated)'; } }}
              onMouseLeave={e => { if (!on) { e.currentTarget.style.borderColor = 'var(--border-soft)'; e.currentTarget.style.background = 'var(--bg-surface)'; } }}
              style={{
                display: 'flex', alignItems: 'center', gap: '11px', textAlign: 'left', cursor: 'pointer', fontFamily: 'inherit', transition: '120ms ease',
                background: on ? 'rgba(31,77,61,0.12)' : 'var(--bg-surface)',
                border: `1px solid ${on ? 'rgba(31,77,61,0.5)' : 'var(--border-soft)'}`,
                borderRadius: '8px', padding: '14px 16px', minWidth: '256px',
              }}>
              <span style={{ width: 36, height: 36, borderRadius: '8px', background: on ? 'var(--brand)' : 'var(--bg-surface-muted)',
                display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
                <Icon name="forms" size={18} color={on ? '#FBFAF6' : 'var(--text-muted)'} />
              </span>
              <span style={{ minWidth: 0, flex: 1 }}>
                <span style={{ display: 'block', fontSize: '14px', fontWeight: 600, color: 'var(--text-primary)' }}>{t.name}</span>
                <span style={{ display: 'block', fontSize: '11px', color: 'var(--text-muted)', marginTop: '2px' }}>{t.sub}</span>
              </span>
              <Icon name="chevronR" size={16} color={on ? '#4A9B7E' : 'var(--text-muted)'} style={{ flexShrink: 0 }} />
            </button>
          );
        })}
      </div>

      {/* Holiday view, no program chosen yet — editor is locked until a program is picked */}
      {view === 'holiday' && !active && (
        <div style={{ display: 'grid', gridTemplateColumns: '1.05fr 0.95fr', gap: '20px', alignItems: 'start' }}>
          {/* Editor shell with the program picker emphasised + locked fields below */}
          <div style={{ background: 'var(--bg-surface)', border: '1px solid var(--border-soft)', borderRadius: '8px', padding: '18px' }}>
            <div style={{ fontSize: '11px', fontWeight: 600, letterSpacing: '0.05em', textTransform: 'uppercase', color: 'var(--text-muted)', marginBottom: '12px' }}>Edit form</div>

            <label style={{ display: 'block', fontSize: '13px', fontWeight: 600, color: 'var(--text-primary)', marginBottom: '6px' }}>Choose the holiday program this form is for</label>
            {programs.length === 0 ? (
              <div style={{ fontSize: '13px', color: 'var(--text-secondary)', background: 'var(--bg-surface-muted)', border: '1px solid var(--border-default)', borderRadius: '6px', padding: '12px', lineHeight: 1.5 }}>
                No holiday programs yet. Create one on the <strong style={{ color: 'var(--text-primary)' }}>Classes</strong> page and it’ll appear here automatically.
              </div>
            ) : (
              <React.Fragment>
                <div style={{ borderRadius: '6px', boxShadow: '0 0 0 3px rgba(31,77,61,0.3)' }}>
                  <FormSelect value="" placeholder="Select a holiday program…" onChange={v => setProgramId(v)}
                    options={programs.map(p => ({ value: p.id, label: p.name }))} />
                </div>
              </React.Fragment>
            )}

            {/* Locked fields — greyed with a padlock */}
            <div style={{ marginTop: '18px', border: '1px dashed var(--border-default)', borderRadius: '10px', background: 'var(--bg-surface-muted)', padding: '40px 24px', textAlign: 'center', opacity: 0.9 }}>
              <span style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 46, height: 46, borderRadius: '12px', background: 'var(--bg-elevated)', border: '1px solid var(--border-default)', marginBottom: '12px' }}>
                <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--text-muted)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" /><path d="M7 11V7a5 5 0 0 1 10 0v4" /></svg>
              </span>
              <div style={{ fontSize: '14px', fontWeight: 600, color: 'var(--text-secondary)' }}>Form is locked</div>
              <div style={{ fontSize: '12px', color: 'var(--text-muted)', marginTop: '5px' }}>Select a holiday program above to edit its URL and questions.</div>
            </div>
          </div>

          {/* Locked preview */}
          <div>
            <div style={{ border: '1px dashed var(--border-default)', borderRadius: '10px', background: 'var(--bg-surface)', padding: '56px 24px', textAlign: 'center' }}>
              <span style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 46, height: 46, borderRadius: '12px', background: 'var(--bg-surface-muted)', marginBottom: '12px' }}>
                <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--text-muted)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" /><path d="M7 11V7a5 5 0 0 1 10 0v4" /></svg>
              </span>
              <div style={{ fontSize: '13px', color: 'var(--text-muted)' }}>Select a program to preview its form</div>
            </div>
          </div>
        </div>
      )}

      {active && (
        <div style={{ display: 'grid', gridTemplateColumns: '1.05fr 0.95fr', gap: '20px', alignItems: 'start' }}>

          {/* ── Editor ── */}
          <div style={{ background: 'var(--bg-surface)', border: '1px solid var(--border-soft)', borderRadius: '8px', padding: '18px' }}>
            <div style={{ fontSize: '11px', fontWeight: 600, letterSpacing: '0.05em', textTransform: 'uppercase', color: 'var(--text-muted)', marginBottom: '12px' }}>Edit form</div>

            {active.kind === 'holiday' ? (
              <div style={{ marginBottom: '12px' }}>
                <label style={{ fontSize: '12px', color: 'var(--text-muted)', display: 'block', marginBottom: '4px' }}>Holiday program</label>
                <FormSelect value={program.id} onChange={v => setProgramId(v)}
                  options={programs.map(p => ({ value: p.id, label: p.name }))} />
                <div style={{ fontSize: '11px', color: 'var(--text-muted)', marginTop: '6px' }}>Programs are managed on the Classes page. Each one keeps its own URL and the questions you add below.</div>
              </div>
            ) : (
              <input value={active.name} onChange={e => setActiveForm(f => ({ ...f, name: e.target.value }))} style={{ ...labelInput, width: '100%', marginBottom: '12px', fontWeight: 600, fontSize: '15px' }} />
            )}

            {active.kind === 'holiday' && (
              <React.Fragment>
                <label style={{ fontSize: '12px', color: 'var(--text-muted)', display: 'block', marginBottom: '4px' }}>Description</label>
                <textarea value={active.description} onChange={e => setActiveForm(f => ({ ...f, description: e.target.value }))} rows={2}
                  style={{ ...labelInput, width: '100%', marginBottom: '8px', resize: 'vertical', lineHeight: 1.45 }} />
              </React.Fragment>
            )}

            {active.hosted && (
              <div style={{ marginBottom: '16px' }}>
                {active.kind === 'holiday' && <label style={{ fontSize: '12px', color: 'var(--text-muted)', display: 'block', marginBottom: '4px' }}>Public URL — edit the address, then Confirm to save</label>}
                <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
                  {/* Whole URL shown, with the slug editable inline */}
                  <div style={{ flex: 1, minWidth: 0, display: 'flex', alignItems: 'center', background: 'var(--bg-surface-muted)', border: `1px solid ${slugDirty ? '#1F4D3D' : 'var(--border-default)'}`, borderRadius: '6px', overflow: 'hidden' }}>
                    <span style={{ fontSize: '13px', color: 'var(--text-muted)', padding: '8px 0 8px 11px', whiteSpace: 'nowrap', fontFamily: 'ui-monospace, monospace' }}>https://themathologists.com.au/</span>
                    <input value={slugDraft} onChange={e => setSlugDraft(e.target.value.replace(/[^a-z0-9-]/gi, '').toLowerCase())}
                      onKeyDown={e => { if (e.key === 'Enter' && slugDirty) setActiveForm(f => ({ ...f, slug: slugDraft })); }}
                      style={{ flex: 1, minWidth: 0, background: 'transparent', border: 'none', outline: 'none', padding: '8px 8px 8px 0', fontSize: '13px', fontWeight: 600, color: 'var(--text-primary)', fontFamily: 'ui-monospace, monospace' }} />
                  </div>
                  {slugDirty && (
                    <button onClick={() => setActiveForm(f => ({ ...f, slug: slugDraft }))}
                      style={{ flexShrink: 0, background: 'var(--brand)', border: 'none', borderRadius: '6px', padding: '8px 14px', fontSize: '12px', fontWeight: 600, color: '#fff', cursor: 'pointer', fontFamily: 'inherit' }}>Confirm</button>
                  )}
                  <button onClick={() => copyLink(publicUrl)} title="Copy URL" style={{ flexShrink: 0,
                    background: copied === publicUrl ? 'rgba(74,155,126,0.15)' : 'var(--bg-surface)', cursor: 'pointer', fontFamily: 'inherit',
                    border: `1px solid ${copied === publicUrl ? 'rgba(74,155,126,0.4)' : 'var(--border-default)'}`, borderRadius: '6px', padding: '8px 13px',
                    fontSize: '12px', fontWeight: 500, color: copied === publicUrl ? '#4A9B7E' : 'var(--text-secondary)' }}>
                    {copied === publicUrl ? '✓ Copied' : 'Copy'}
                  </button>
                </div>
              </div>
            )}

            {active.kind === 'holiday' && <div style={{ fontSize: '12px', color: 'var(--text-muted)', marginBottom: '8px' }}>Fields — locked ones are always captured; add your own program questions below</div>}

            <div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
              {active.fields.map((fl, idx) => {
                // Section heading — a bold brand-tinted band with a numbered badge,
                // so the form visibly splits into sections (Student / Parent / …).
                if (fl.type === 'heading') {
                  const num = headingNums[fl.id];
                  const band = { display: 'flex', alignItems: 'center', gap: '10px', marginTop: idx ? '18px' : '6px', marginBottom: '2px',
                    background: 'rgba(31,77,61,0.12)', border: '1px solid rgba(31,77,61,0.35)', borderLeft: '3px solid var(--brand)', borderRadius: '7px', padding: '9px 11px',
                    boxShadow: dropShadow(fl.id), opacity: dragId === fl.id ? 0.4 : 1 };
                  const badge = { width: 22, height: 22, borderRadius: '50%', background: 'var(--brand)', color: '#FBFAF6', fontSize: '11px', fontWeight: 700, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 };
                  if (fl.locked) return (
                    <div key={fl.id} data-fieldrow="1" style={band}>
                      <DragHandle disabled />
                      <span style={badge}>{num}</span>
                      <span style={{ flex: 1, fontWeight: 700, fontSize: '13px', letterSpacing: '0.04em', textTransform: 'uppercase', color: 'var(--text-primary)' }}>{fl.label}</span>
                      <span style={{ fontSize: '10px', color: 'var(--text-muted)', flexShrink: 0 }}>Locked</span>
                    </div>
                  );
                  return (
                    <div key={fl.id} data-fieldrow="1" style={band} onDragOver={e => onRowDragOver(e, fl)} onDrop={e => onRowDrop(e, fl)}>
                      <DragHandle onDragStart={e => onHandleDragStart(e, fl.id)} onDragEnd={onAnyDragEnd} />
                      <span style={badge}>{num}</span>
                      <input value={fl.label} onChange={e => updateField(fl.id, { label: e.target.value })}
                        style={{ flex: 1, minWidth: 0, background: 'transparent', border: 'none', outline: 'none', fontWeight: 700, fontSize: '13px', letterSpacing: '0.04em', textTransform: 'uppercase', color: 'var(--text-primary)', fontFamily: 'inherit' }} />
                      <button title="Remove section" onClick={() => removeField(fl.id)} style={iconBtn}>×</button>
                    </div>
                  );
                }
                // Locked minimum fields — read-only
                if (fl.locked) return (
                  <div key={fl.id} style={{ display: 'flex', alignItems: 'center', gap: '8px', borderRadius: '6px', padding: '8px 9px',
                    background: 'var(--bg-surface-muted)', border: '1px dashed var(--border-default)' }}>
                    <DragHandle disabled />
                    <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="var(--text-muted)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ flexShrink: 0 }}><rect x="3" y="11" width="18" height="11" rx="2" /><path d="M7 11V7a5 5 0 0 1 10 0v4" /></svg>
                    <span style={{ flex: 1, fontSize: '13px', fontWeight: 500, color: 'var(--text-primary)' }}>{fl.label}{fl.required ? ' *' : ''}</span>
                    <span style={{ fontSize: '10px', color: 'var(--text-muted)', flexShrink: 0 }}>Locked</span>
                  </div>
                );
                // Editable custom field
                return (
                  <div key={fl.id} data-fieldrow="1" onDragOver={e => onRowDragOver(e, fl)} onDrop={e => onRowDrop(e, fl)}
                    style={{ background: 'var(--bg-surface-muted)', border: '1px solid var(--border-soft)', borderRadius: '6px', padding: '7px 8px',
                      opacity: dragId === fl.id ? 0.4 : (fl.enabled === false ? 0.5 : 1), boxShadow: dropShadow(fl.id) }}>
                    <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
                      <DragHandle onDragStart={e => onHandleDragStart(e, fl.id)} onDragEnd={onAnyDragEnd} />
                      <Checkbox checked={fl.enabled !== false} onChange={v => updateField(fl.id, { enabled: v })} title="Show on form" />
                      <input value={fl.label} onChange={e => updateField(fl.id, { label: e.target.value })} style={labelInput} />
                      <div style={{ width: '96px', flexShrink: 0 }}>
                        <FormSelect value={fl.type} onChange={v => updateField(fl.id, { type: v })} buttonStyle={{ padding: '6px 9px' }}
                          options={FIELD_TYPES.map(t => ({ value: t, label: t.charAt(0).toUpperCase() + t.slice(1) }))} />
                      </div>
                      <label style={{ display: 'flex', alignItems: 'center', gap: '5px', fontSize: '11px', color: 'var(--text-secondary)', flexShrink: 0, whiteSpace: 'nowrap', cursor: 'pointer' }}>
                        <Checkbox checked={!!fl.required} onChange={v => updateField(fl.id, { required: v })} color="#C05656" /> Required
                      </label>
                      <button title="Remove field" onClick={() => removeField(fl.id)} style={iconBtn}>×</button>
                    </div>
                    {fl.type === 'select' && (
                      // Indent so the input's left edge lines up with the label box above
                      // (past the 16px handle + 8px gap + 16px checkbox + 8px gap = 48px).
                      <OptionsInput key={fl.id} initial={(fl.options || []).join(', ')} onChange={opts => updateField(fl.id, { options: opts })}
                        placeholder="Dropdown items, comma-separated (e.g. Advanced, Extension 1, Both)" style={{ ...labelInput, width: 'calc(100% - 48px)', marginTop: '6px', marginLeft: '48px' }} />
                    )}
                  </div>
                );
              })}
            </div>

            <div style={{ display: 'flex', gap: '8px', marginTop: '12px' }}>
              <button onClick={addField}   style={{ ...iconBtn, border: '1px solid var(--border-default)', borderRadius: '6px', padding: '7px 12px', fontSize: '12px', color: 'var(--text-secondary)' }}>+ Add field</button>
              <button onClick={addHeading} style={{ ...iconBtn, border: '1px solid var(--border-default)', borderRadius: '6px', padding: '7px 12px', fontSize: '12px', color: 'var(--text-secondary)' }}>+ Add section</button>
            </div>
          </div>

          {/* ── Live preview (top-aligned with the editor; "Live" cue lives in the browser chrome) ── */}
          <div>
            <div style={{ border: '1px solid var(--border-default)', borderRadius: '10px', overflow: 'hidden', boxShadow: '0 10px 32px rgba(0,0,0,0.38)' }}>
              {/* Browser chrome */}
              <div style={{ display: 'flex', alignItems: 'center', gap: '7px', padding: '9px 12px', background: 'var(--bg-surface-muted)', borderBottom: '1px solid var(--border-soft)' }}>
                <span style={{ width: 9, height: 9, borderRadius: '50%', background: '#E0685E' }} />
                <span style={{ width: 9, height: 9, borderRadius: '50%', background: '#E0B33E' }} />
                <span style={{ width: 9, height: 9, borderRadius: '50%', background: '#5EB07E' }} />
                <div style={{ marginLeft: '6px', flex: 1, background: 'var(--bg-body)', border: '1px solid var(--border-soft)', borderRadius: '5px', padding: '4px 10px', fontSize: '11px', color: 'var(--text-muted)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                  {active.hosted ? `themathologists.com.au/${active.slug || ''}` : 'draft — not published'}
                </div>
                <span style={{ flexShrink: 0, display: 'inline-flex', alignItems: 'center', gap: '6px', background: 'rgba(94,176,126,0.14)', border: '1px solid rgba(94,176,126,0.45)', borderRadius: '999px', padding: '3px 9px 3px 8px', fontSize: '9.5px', fontWeight: 700, letterSpacing: '0.07em', textTransform: 'uppercase', color: '#5EB07E' }}>
                  <span className="tm-live-dot" style={{ width: 7, height: 7, borderRadius: '50%', background: '#5EB07E', flexShrink: 0 }} />
                  Live preview
                </span>
              </div>
              {/* Page backdrop */}
              <div style={{ background: 'var(--bg-body)', padding: '24px' }}>
                <div style={{ maxWidth: '430px', margin: '0 auto', background: 'var(--bg-surface)', border: '1px solid var(--border-soft)', borderRadius: '10px', padding: '22px', boxShadow: '0 1px 3px rgba(0,0,0,0.25)' }}>
              <div style={{ fontSize: '17px', fontWeight: 700, color: 'var(--text-primary)' }}>{active.name}</div>
              {active.description && <div style={{ fontSize: '12px', color: 'var(--text-muted)', marginTop: '5px', lineHeight: 1.5 }}>{active.description}</div>}
              {program && (
                <div style={{ marginTop: '12px', display: 'flex', alignItems: 'center', gap: '7px', background: 'rgba(184,146,42,0.12)', border: '1px solid rgba(184,146,42,0.3)', borderRadius: '6px', padding: '8px 10px', fontSize: '12px', color: '#B8922A' }}>
                  <span style={{ width: 7, height: 7, borderRadius: '50%', background: '#B8922A', flexShrink: 0 }} />
                  Holiday program sign-up
                </div>
              )}
              <div style={{ marginTop: '14px' }}>
                {active.kind === 'trial' ? <TrialFormPreview fields={active.fields} submitLabel={active.submitLabel} /> : active.fields.map(fl => {
                  if (fl.type === 'heading') {
                    const num = headingNums[fl.id];
                    return (
                      <div key={fl.id} style={{ display: 'flex', alignItems: 'center', gap: '10px', margin: num === 1 ? '4px 0 14px' : '26px 0 14px' }}>
                        <span style={{ width: 24, height: 24, borderRadius: '50%', background: 'var(--brand)', color: '#FBFAF6', fontSize: '12px', fontWeight: 700, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>{num}</span>
                        <span style={{ fontSize: '14px', fontWeight: 700, color: 'var(--text-primary)', letterSpacing: '-0.01em', flexShrink: 0 }}>{fl.label}</span>
                        <span style={{ flex: 1, height: '1px', background: 'var(--border-soft)' }} />
                      </div>
                    );
                  }
                  if (fl.enabled === false) return null;
                  return (
                    <div key={fl.id} style={{ marginBottom: '12px' }}>
                      <label style={{ display: 'block', fontSize: '12px', color: 'var(--text-secondary)', marginBottom: '5px' }}>
                        {fl.label}{fl.required && <span style={{ color: '#C05656' }}> *</span>}
                      </label>
                      {fl.type === 'select' ? (
                        <PreviewSelect options={fl.options} placeholder="Select…" />
                      ) : (
                        <div style={{ ...previewBox, color: 'var(--text-muted)' }}>{fl.type === 'date' ? 'dd / mm / yyyy' : ''}</div>
                      )}
                    </div>
                  );
                })}
                {active.kind !== 'trial' && (
                  <button disabled style={{ marginTop: '8px', width: '100%', background: 'var(--brand)', border: 'none', borderRadius: '6px',
                    padding: '11px', fontSize: '14px', fontWeight: 600, color: '#fff', fontFamily: 'inherit', cursor: 'default', opacity: 0.9 }}>
                    {active.submitLabel || 'Submit'}
                  </button>
                )}
              </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

Object.assign(window, { ClassesSection, PrintingSection, InvoicesSection, SettingsSection, FormsSection });
