(function () {
  'use strict';

  if (typeof window === 'undefined') return;

  function escapeHtml(value) {
    return String(value == null ? '' : value)
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;')
      .replace(/'/g, '&#39;');
  }

  const { createClient } = window.api;
  const config = window.appConfig;
  const {
    formatDate,
    getWeekStart,
    getWeekDates,
    addDays
  } = window.dates;
  const {
    computeRange,
    computeLayout,
    groupByDay,
    describeWeek
  } = window.timeline;
  const {
    buildMappings,
    classLabel,
    scheduleForm,
    validateForm
  } = window.mapping;
  const {
    cleanTeacherFields,
    validateTeacher,
    summarizeReferences
  } = window.teachers;
  const {
    cleanClassFields,
    validateClass,
    summarizeReferences: summarizeClassReferences
  } = window.classes;
  const {
    cleanClassroomFields,
    validateClassroom,
    summarizeReferences: summarizeClassroomReferences
  } = window.classrooms;

  const client = createClient();
  const state = {
    weekStart: getWeekStart(new Date()),
    selectedDay: null,
    filters: { class_id: '', teacher_id: '', classroom_id: '', status: '', topic: '' },
    mappings: buildMappings({ classes: [], courses: [], teachers: [], classrooms: [] }),
    schedules: [],
    loading: false,
    drawer: { open: false, mode: 'create', record: null },
    teachers: {
      drawerOpen: false,
      list: [],
      keyword: '',
      loading: false,
      submitting: false,
      formOpen: false,
      error: ''
    },
    classes: {
      drawerOpen: false,
      list: [],
      keyword: '',
      loading: false,
      submitting: false,
      formOpen: false,
      editingId: '',
      error: ''
    },
    classrooms: {
      drawerOpen: false,
      list: [],
      keyword: '',
      loading: false,
      submitting: false,
      formOpen: false,
      error: ''
    }
  };

  const elements = {
    weekLabel: document.getElementById('week-label'),
    timeline: document.querySelector('.timeline'),
    filters: document.querySelectorAll('[data-filter]'),
    dayTabs: document.querySelector('[data-role="day-tabs"]'),
    axis: document.querySelector('[data-role="axis"]'),
    grid: document.querySelector('[data-role="grid"]'),
    state: document.querySelector('[data-role="state"]'),
    drawer: document.querySelector('[data-role="drawer"]'),
    drawerTitle: document.querySelector('[data-role="drawer-title"]'),
    form: document.querySelector('[data-role="schedule-form"]'),
    formError: document.querySelector('[data-role="form-error"]'),
    submit: document.querySelector('[data-role="submit"]'),
    confirm: document.querySelector('[data-role="confirm"]'),
    confirmMessage: document.querySelector('[data-role="confirm-message"]'),
    toast: document.querySelector('[data-role="toast"]'),
    teachersDrawer: document.querySelector('[data-role="teachers-drawer"]'),
    teacherSearchForm: document.querySelector('[data-role="teacher-search"]'),
    teacherSearchInput: document.querySelector('[data-role="teacher-search-input"]'),
    teacherForm: document.querySelector('[data-role="teacher-form"]'),
    teacherFormError: document.querySelector('[data-role="teacher-form-error"]'),
    teacherSubmit: document.querySelector('[data-role="teacher-submit"]'),
    teacherState: document.querySelector('[data-role="teacher-state"]'),
    teacherList: document.querySelector('[data-role="teacher-list"]'),
    teacherConfirm: document.querySelector('[data-role="teacher-confirm"]'),
    teacherConfirmMessage: document.querySelector('[data-role="teacher-confirm-message"]'),
    classesDrawer: document.querySelector('[data-role="classes-drawer"]'),
    classSearchForm: document.querySelector('[data-role="class-search"]'),
    classSearchInput: document.querySelector('[data-role="class-search-input"]'),
    classForm: document.querySelector('[data-role="class-form"]'),
    classFormError: document.querySelector('[data-role="class-form-error"]'),
    classSubmit: document.querySelector('[data-role="class-submit"]'),
    classState: document.querySelector('[data-role="class-state"]'),
    classList: document.querySelector('[data-role="class-list"]'),
    classConfirm: document.querySelector('[data-role="class-confirm"]'),
    classConfirmMessage: document.querySelector('[data-role="class-confirm-message"]'),
    classroomsDrawer: document.querySelector('[data-role="classrooms-drawer"]'),
    classroomSearchForm: document.querySelector('[data-role="classroom-search"]'),
    classroomSearchInput: document.querySelector('[data-role="classroom-search-input"]'),
    classroomForm: document.querySelector('[data-role="classroom-form"]'),
    classroomFormError: document.querySelector('[data-role="classroom-form-error"]'),
    classroomSubmit: document.querySelector('[data-role="classroom-submit"]'),
    classroomState: document.querySelector('[data-role="classroom-state"]'),
    classroomList: document.querySelector('[data-role="classroom-list"]'),
    classroomConfirm: document.querySelector('[data-role="classroom-confirm"]'),
    classroomConfirmMessage: document.querySelector('[data-role="classroom-confirm-message"]')
  };

  function showToast(message, variant = 'info') {
    elements.toast.textContent = message;
    elements.toast.dataset.variant = variant;
    elements.toast.hidden = false;
    clearTimeout(showToast._timer);
    showToast._timer = setTimeout(() => { elements.toast.hidden = true; }, 2500);
  }

  function setStateMessage(message) {
    if (!message) { elements.state.hidden = true; elements.state.textContent = ''; return; }
    elements.state.hidden = false;
    elements.state.textContent = message;
  }

  function setLoading(loading) {
    state.loading = loading;
    document.querySelectorAll('button').forEach((btn) => {
      if (btn.dataset.role === 'submit') {
        btn.disabled = loading;
      }
    });
  }

  async function loadBaseData() {
    const data = await fetchBaseData();
    state.mappings = buildMappings(data);
    renderFilters();
    if (!data.classes.length || !data.teachers.length || !data.classrooms.length) {
      showToast('部分基础资料加载失败', 'warning');
    }
  }

  async function fetchBaseData() {
    const [classes, courses, teachers, classrooms] = await Promise.all([
      client.listAll('classes', {}).catch(() => []),
      client.listAll('courses', {}).catch(() => []),
      client.listAll('teachers', {}).catch(() => []),
      client.listAll('classrooms', {}).catch(() => [])
    ]);
    return { classes, courses, teachers, classrooms };
  }

  async function reloadBaseData() {
    const data = await fetchBaseData();
    state.mappings = buildMappings(data);
    renderFilters();
  }

  function renderFilters() {
    const setOptions = (select, items, mapper) => {
      const current = select.value;
      select.innerHTML = '<option value="">全部</option>' + items.map(mapper).join('');
      if (current) select.value = current;
    };
    const classSelect = document.querySelector('[data-filter="class_id"]');
    setOptions(classSelect, [...state.mappings.classes.values()], (c) => `<option value="${escapeHtml(c.id)}">${escapeHtml(classLabel(state.mappings, c.id))}</option>`);
    const teacherSelect = document.querySelector('[data-filter="teacher_id"]');
    setOptions(teacherSelect, [...state.mappings.teachers.values()], (t) => `<option value="${escapeHtml(t.id)}">${escapeHtml(t.name)}</option>`);
    const classroomSelect = document.querySelector('[data-filter="classroom_id"]');
    setOptions(classroomSelect, [...state.mappings.classrooms.values()], (r) => `<option value="${escapeHtml(r.id)}">${escapeHtml(r.name)}</option>`);
  }

  async function refreshSchedules() {
    setLoading(true);
    setStateMessage('正在加载排期…');
    const filters = {};
    if (state.filters.class_id) filters.class_id = state.filters.class_id;
    if (state.filters.teacher_id) filters.teacher_id = state.filters.teacher_id;
    if (state.filters.classroom_id) filters.classroom_id = state.filters.classroom_id;
    if (state.filters.status) filters.status = state.filters.status;
    if (state.filters.topic) filters.topic = `like.*${state.filters.topic}*`;
    try {
      state.schedules = await client.listSchedulesForWeek(state.weekStart, filters);
      renderTimeline();
      if (state.schedules.length === 0) {
        setStateMessage('当前筛选条件下没有排期');
      } else {
        setStateMessage('');
      }
    } catch (err) {
      setStateMessage(`加载失败：${err.message}`);
    } finally {
      setLoading(false);
    }
  }

  function renderTimeline() {
    elements.weekLabel.textContent = describeWeek(state.weekStart);
    const dates = getWeekDates(state.weekStart);
    const records = state.schedules;
    const range = computeRange(records, config.defaultTimelineStart, config.defaultTimelineEnd, config.slotMinutes);
    const layout = computeLayout({ ...range, slot: config.slotMinutes }, records);
    const grouped = groupByDay(layout);
    const startMin = (function () { const [h, m] = range.start.split(':').map(Number); return h * 60 + m; })();
    const endMin = (function () { const [h, m] = range.end.split(':').map(Number); return h * 60 + m; })();
    const totalSlots = (endMin - startMin) / config.slotMinutes;

    // Sync the row height between the axis column and the grid so each
    // axis label aligns with the corresponding time slot in every day column.
    elements.timeline.style.setProperty('--slot-rows', String(totalSlots));
    elements.timeline.style.setProperty('--slot-height', `${config.slotMinutes}px`);

    const slots = [];
    for (let i = 0; i < totalSlots; i++) {
      const minutes = startMin + i * config.slotMinutes;
      const label = `${String(Math.floor(minutes / 60)).padStart(2, '0')}:${String(minutes % 60).padStart(2, '0')}`;
      const isHour = minutes % 60 === 0;
      const isLunch = label >= '12:00' && label < '14:00';
      const isTimePoint = config.availableTimePoints.includes(label);
      slots.push({ label, isHour, isLunch, isTimePoint });
    }

    const dateHeaders = dates
      .map((d) => {
        const date = new Date(d);
        const weekday = ['日', '一', '二', '三', '四', '五', '六'][date.getDay()];
        return `<div class="timeline-day-header">${date.getMonth() + 1}/${date.getDate()} 周${weekday}</div>`;
      })
      .join('');

    const axisCells = ['<div class="timeline-label">时间</div>']
      .concat(slots.map((slot) => `<div class="timeline-label">${slot.label}</div>`))
      .join('');

    const dayColumns = dates
      .map((d) => {
        const cells = slots
          .map((slot) => {
            const classes = ['timeline-cell'];
            if (slot.isHour) classes.push('is-hour');
            if (slot.isLunch) classes.push('is-lunch');
            else if (slot.isTimePoint) classes.push('is-time-point');
            else classes.push('is-disabled');
            return `<div class="${classes.join(' ')}" data-day="${d}" data-slot="${slot.label}" title="${slot.label}"></div>`;
          })
          .join('');
        const cards = (grouped[d] || [])
          .map((card) => renderCard(card))
          .join('');
        return `<div class="timeline-day-column" data-day="${d}" data-active="true" style="grid-row: 2 / span ${totalSlots}">${cells}${cards}</div>`;
      })
      .join('');

    elements.axis.innerHTML = axisCells;
    elements.grid.innerHTML = dateHeaders + dayColumns;
    bindTimelineEvents();
    renderDayTabs(dates);
  }

  function renderCard(card) {
    const { start_time, end_time } = card;
    const form = scheduleForm(state.mappings, card);
    return `<div class="schedule-card is-${escapeHtml(form.status)}" data-id="${escapeHtml(card.id)}" style="top:${card.top}px;height:${card.height}px" title="${escapeHtml(form.topic)}">
    <div class="topic">${escapeHtml(form.topic)}</div>
    <div class="meta">${escapeHtml(start_time)}-${escapeHtml(end_time)}</div>
    <div class="meta">${escapeHtml(form.className)}</div>
    <div class="meta">${escapeHtml(form.teacherName)} · ${escapeHtml(form.classroomName)}</div>
  </div>`;
  }

  function renderDayTabs(dates) {
    elements.dayTabs.innerHTML = dates
      .map((d) => {
        const date = new Date(d);
        const weekday = ['日', '一', '二', '三', '四', '五', '六'][date.getDay()];
        return `<button type="button" data-day="${d}">${date.getMonth() + 1}/${date.getDate()} 周${weekday}</button>`;
      })
      .join('');
    elements.dayTabs.querySelectorAll('button').forEach((btn) => {
      btn.addEventListener('click', () => activateDay(btn.dataset.day));
    });
    activateDay(dates[0]);
  }

  function activateDay(day) {
    state.selectedDay = day;
    elements.grid.querySelectorAll('.timeline-day-column').forEach((col) => {
      col.dataset.active = col.dataset.day === day ? 'true' : 'false';
    });
    elements.dayTabs.querySelectorAll('button').forEach((btn) => {
      btn.classList.toggle('primary', btn.dataset.day === day);
    });
  }

  function bindTimelineEvents() {
    elements.grid.querySelectorAll('.timeline-cell.is-time-point').forEach((cell) => {
      cell.addEventListener('click', () => openCreateDrawer(cell.dataset.day, cell.dataset.slot));
    });
    elements.grid.querySelectorAll('.schedule-card').forEach((cardEl) => {
      cardEl.addEventListener('click', (event) => {
        event.stopPropagation();
        openEditDrawer(cardEl.dataset.id);
      });
    });
  }

  function openCreateDrawer(date, slot) {
    state.drawer = { open: true, mode: 'create', record: null };
    elements.drawerTitle.textContent = '新增报备';
    elements.form.reset();
    elements.form.elements.id.value = '';
    elements.form.elements.schedule_date.value = date;
    populateFormSelects();
    const initial = (slot && config.availableTimePoints.includes(slot))
      ? slot
      : config.availableTimePoints[0];
    elements.form.elements.start_time.value = initial;
    elements.form.elements.end_time.value = addMinutes(initial, config.slotMinutes);
    elements.form.elements.status.value = 'pending';
    showDrawer();
  }

  function addMinutes(time, delta) {
    const [h, m] = time.split(':').map(Number);
    const total = h * 60 + m + delta;
    const hh = Math.floor(total / 60) % 24;
    const mm = total % 60;
    return `${String(hh).padStart(2, '0')}:${String(mm).padStart(2, '0')}`;
  }

  function openEditDrawer(id) {
    const record = state.schedules.find((r) => r.id === id);
    if (!record) return;
    state.drawer = { open: true, mode: 'edit', record };
    elements.drawerTitle.textContent = '编辑报备';
    populateFormSelects();
    elements.form.reset();
    for (const [key, value] of Object.entries(record)) {
      if (elements.form.elements[key]) elements.form.elements[key].value = value || '';
    }
    showDrawer();
  }

  function populateFormSelects() {
    const fill = (name, items, mapper) => {
      const select = elements.form.elements[name];
      const current = select.value;
      select.innerHTML = '<option value="">请选择</option>' + items.map(mapper).join('');
      if (current) select.value = current;
    };
    fill('class_id', [...state.mappings.classes.values()], (c) => `<option value="${escapeHtml(c.id)}">${escapeHtml(classLabel(state.mappings, c.id))}</option>`);
    fill('classroom_id', [...state.mappings.classrooms.values()], (r) => `<option value="${escapeHtml(r.id)}">${escapeHtml(r.name)}</option>`);
    fill('teacher_id', [...state.mappings.teachers.values()], (t) => `<option value="${escapeHtml(t.id)}">${escapeHtml(t.name)}</option>`);
    populateStartTimeSelect();
  }

  function populateStartTimeSelect() {
    const select = elements.form.elements.start_time;
    const current = select.value;
    const groups = [
      { label: '上午', points: config.availableTimePoints.filter((t) => parseInt(t, 10) < 12) },
      { label: '下午', points: config.availableTimePoints.filter((t) => parseInt(t, 10) >= 12) }
    ];
    select.innerHTML = '<option value="">请选择时间点</option>' +
      groups.map((g) => g.points.length
        ? `<optgroup label="${g.label}">${g.points.map((p) => `<option value="${p}">${p}</option>`).join('')}</optgroup>`
        : '').join('');
    if (current && config.availableTimePoints.includes(current)) select.value = current;
  }

  function showDrawer() {
    elements.drawer.hidden = false;
    elements.drawer.setAttribute('aria-hidden', 'false');
  }

  function closeDrawer() {
    elements.drawer.hidden = true;
    elements.drawer.setAttribute('aria-hidden', 'true');
    clearFormErrors();
  }

  function clearFormErrors() {
    elements.form.querySelectorAll('.error').forEach((el) => { el.textContent = ''; });
    elements.formError.hidden = true;
    elements.formError.textContent = '';
  }

  function readForm() {
    const data = {};
    for (const el of elements.form.elements) {
      if (!el.name) continue;
      data[el.name] = el.value;
    }
    return data;
  }

  async function handleFormSubmit(event) {
    event.preventDefault();
    const data = readForm();
    const errors = validateForm(data, { availableTimePoints: config.availableTimePoints });
    clearFormErrors();
    for (const [field, message] of Object.entries(errors)) {
      const target = elements.form.querySelector(`[data-error-for="${field}"]`);
      if (target) target.textContent = message;
    }
    if (Object.keys(errors).length) {
      elements.formError.hidden = false;
      elements.formError.textContent = '请修正表单错误后再提交';
      return;
    }
    setLoading(true);
    try {
      if (state.drawer.mode === 'edit' && data.id) {
        await client.updateSchedule(data);
        showToast('已保存修改');
      } else {
        delete data.id;
        await client.createSchedule(data);
        showToast('已新增报备');
      }
      closeDrawer();
      await refreshSchedules();
    } catch (err) {
      elements.formError.hidden = false;
      elements.formError.textContent = err.message;
    } finally {
      setLoading(false);
    }
  }

  function confirmDelete(id) {
    const record = state.schedules.find((r) => r.id === id);
    if (!record) return;
    const form = scheduleForm(state.mappings, record);
    elements.confirmMessage.textContent = `确认删除「${form.topic}」 ${form.schedule_date} ${form.start_time}-${form.end_time}？`;
    elements.confirm.hidden = false;
    const onYes = async () => {
      elements.confirm.hidden = true;
      elements.confirm.removeEventListener('click', handleClick);
      setLoading(true);
      try {
        await client.deleteSchedule(id);
        showToast('已删除');
        await refreshSchedules();
      } catch (err) {
        showToast(err.message, 'error');
      } finally {
        setLoading(false);
      }
    };
    const onNo = () => {
      elements.confirm.hidden = true;
      elements.confirm.removeEventListener('click', handleClick);
    };
    function handleClick(event) {
      if (event.target.dataset.action === 'confirm-yes') onYes();
      if (event.target.dataset.action === 'confirm-no') onNo();
    }
    elements.confirm.addEventListener('click', handleClick);
  }

  // ---------- Class management ----------

  function setClassStateMessage(message) {
    const node = elements.classState;
    if (!message) { node.hidden = true; node.textContent = ''; return; }
    node.hidden = false;
    node.textContent = message;
  }

  function renderClassList() {
    const list = state.classes.list;
    if (state.classes.loading) {
      elements.classList.innerHTML = '';
      setClassStateMessage('正在加载班级…');
      return;
    }
    if (!list.length) {
      elements.classList.innerHTML = '';
      setClassStateMessage(state.classes.keyword ? '没有匹配的班级' : '暂无班级');
      return;
    }
    setClassStateMessage('');
    const coursesById = state.mappings.courses;
    const teachersById = state.mappings.teachers;
    const classroomsById = state.mappings.classrooms;
    elements.classList.innerHTML = list.map((cls) => {
      const course = coursesById.get(cls.course_id);
      const teacher = teachersById.get(cls.teacher_id);
      const classroom = classroomsById.get(cls.classroom_id);
      const status = Number(cls.status) === 0 ? 'off' : 'on';
      const statusLabel = status === 'on' ? '启用' : '停用';
      const dates = `${cls.start_date || ''} ~ ${cls.end_date || ''}`;
      return `
        <div class="class-row" data-id="${escapeHtml(cls.id)}">
          <div class="class-cell class-name">${escapeHtml(cls.name || '')}</div>
          <div class="class-cell">${escapeHtml(course ? course.name : '—')}</div>
          <div class="class-cell">${escapeHtml(teacher ? teacher.name : '—')}</div>
          <div class="class-cell">${escapeHtml(classroom ? classroom.name : '—')}</div>
          <div class="class-cell class-meta">${escapeHtml(dates)}</div>
          <div class="class-cell">${escapeHtml(String(cls.max_students ?? '—'))}</div>
          <div class="class-cell"><span class="class-status is-${status}">${statusLabel}</span></div>
          <div class="class-cell class-actions">
            <button type="button" data-action="edit-class" data-id="${escapeHtml(cls.id)}">编辑</button>
            <button type="button" class="danger" data-action="delete-class" data-id="${escapeHtml(cls.id)}" data-name="${escapeHtml(cls.name || '')}">删除</button>
          </div>
        </div>
      `;
    }).join('');
  }

  function openClassesDrawer() {
    state.classes.drawerOpen = true;
    elements.classesDrawer.hidden = false;
    elements.classesDrawer.setAttribute('aria-hidden', 'false');
    if (!state.classes.list.length && !state.classes.loading) {
      void loadClassList('');
    } else {
      renderClassList();
    }
  }

  function closeClassesDrawer() {
    state.classes.drawerOpen = false;
    elements.classesDrawer.hidden = true;
    elements.classesDrawer.setAttribute('aria-hidden', 'true');
    elements.classConfirm.hidden = true;
  }

  async function loadClassList(keyword) {
    state.classes.loading = true;
    state.classes.keyword = keyword || '';
    renderClassList();
    try {
      const list = await client.listClasses(state.classes.keyword);
      if (!state.classes.drawerOpen) return;
      if ((keyword || '') !== state.classes.keyword) return;
      state.classes.list = list;
      state.classes.error = '';
    } catch (err) {
      if (!state.classes.drawerOpen) return;
      state.classes.list = [];
      state.classes.error = err.message || '加载失败';
      setClassStateMessage(`加载失败：${state.classes.error}`);
      elements.classList.innerHTML = '';
    } finally {
      if (state.classes.drawerOpen) {
        state.classes.loading = false;
        if (!state.classes.error) {
          renderClassList();
        }
      }
    }
  }

  function clearClassFilterIfDeleted(classId) {
    if (state.filters.class_id === classId) {
      state.filters.class_id = '';
      const classSelect = document.querySelector('[data-filter="class_id"]');
      if (classSelect) classSelect.value = '';
    }
  }

  function populateClassFormSelects() {
    const fill = (name, items, mapper) => {
      const select = elements.classForm.elements[name];
      const current = select.value;
      select.innerHTML = '<option value="">请选择</option>' + items.map(mapper).join('');
      if (current) select.value = current;
    };
    fill('course_id', [...state.mappings.courses.values()], (c) => `<option value="${escapeHtml(c.id)}">${escapeHtml(c.name || '')}</option>`);
    fill('teacher_id', [...state.mappings.teachers.values()], (t) => `<option value="${escapeHtml(t.id)}">${escapeHtml(t.name || '')}</option>`);
    fill('classroom_id', [...state.mappings.classrooms.values()], (r) => `<option value="${escapeHtml(r.id)}">${escapeHtml(r.name || '')}</option>`);
  }

  function openClassForm() {
    state.classes.formOpen = true;
    state.classes.editingId = '';
    state.classes.error = '';
    elements.classForm.hidden = false;
    elements.classFormError.hidden = true;
    elements.classFormError.textContent = '';
    elements.classForm.querySelectorAll('.error').forEach((el) => { el.textContent = ''; });
    elements.classForm.reset();
    populateClassFormSelects();
    elements.classForm.elements.status.value = '1';
    elements.classForm.elements.name.focus();
  }

  function openEditClassForm(id) {
    const record = state.classes.list.find((c) => c.id === id);
    if (!record) return;
    state.classes.formOpen = true;
    state.classes.editingId = id;
    elements.classForm.hidden = false;
    elements.classFormError.hidden = true;
    elements.classFormError.textContent = '';
    elements.classForm.querySelectorAll('.error').forEach((el) => { el.textContent = ''; });
    elements.classForm.reset();
    populateClassFormSelects();
    for (const [key, value] of Object.entries(record)) {
      if (elements.classForm.elements[key]) {
        elements.classForm.elements[key].value = value == null ? '' : value;
      }
    }
    elements.classForm.elements.name.focus();
  }

  function closeClassForm() {
    state.classes.formOpen = false;
    state.classes.editingId = '';
    elements.classForm.hidden = true;
    elements.classForm.reset();
    elements.classForm.querySelectorAll('.error').forEach((el) => { el.textContent = ''; });
    elements.classFormError.hidden = true;
    elements.classFormError.textContent = '';
  }

  function readClassForm() {
    const data = {};
    for (const el of elements.classForm.elements) {
      if (!el.name) continue;
      data[el.name] = el.value;
    }
    return data;
  }

  function paintClassFormErrors(errors) {
    elements.classForm.querySelectorAll('.error').forEach((el) => { el.textContent = ''; });
    elements.classFormError.hidden = true;
    elements.classFormError.textContent = '';
    for (const [field, message] of Object.entries(errors)) {
      const target = elements.classForm.querySelector(`[data-error-for="${field}"]`);
      if (target) target.textContent = message;
    }
    if (Object.keys(errors).length) {
      elements.classFormError.hidden = false;
      elements.classFormError.textContent = '请修正表单错误后再提交';
    }
  }

  async function handleClassSubmit(event) {
    event.preventDefault();
    if (state.classes.submitting) return;
    const raw = readClassForm();
    const cleaned = cleanClassFields(raw);
    const errors = validateClass(cleaned, { mappings: state.mappings });
    paintClassFormErrors(errors);
    if (Object.keys(errors).length) return;

    state.classes.submitting = true;
    elements.classSubmit.disabled = true;
    try {
      const editingId = state.classes.editingId;
      if (editingId) {
        await client.updateClass({ id: editingId, ...cleaned });
        showToast('已保存班级修改');
      } else {
        await client.createClass(cleaned);
        showToast('已新增班级');
      }
      closeClassForm();
      await reloadBaseData();
      await loadClassList(state.classes.keyword);
    } catch (err) {
      paintClassFormErrors({ _form: err.message });
      elements.classFormError.hidden = false;
      elements.classFormError.textContent = err.message;
    } finally {
      state.classes.submitting = false;
      elements.classSubmit.disabled = false;
    }
  }

  async function handleClassDelete(id, name) {
    let refs;
    try {
      refs = await client.findClassReferences(id);
    } catch (err) {
      showToast('无法确认班级是否被引用，未执行删除', 'error');
      return;
    }
    const summary = summarizeClassReferences(refs);
    if (summary.hasReferences) {
      showToast(`该班级已被${summary.message}引用，无法删除`, 'error');
      return;
    }
    elements.classConfirmMessage.textContent = `确认删除班级「${name || ''}」？`;
    elements.classConfirm.hidden = false;

    const onYes = async () => {
      elements.classConfirm.hidden = true;
      elements.classConfirm.removeEventListener('click', handleClick);
      try {
        await client.deleteClass(id);
        showToast('已删除班级');
        clearClassFilterIfDeleted(id);
        await reloadBaseData();
        await loadClassList(state.classes.keyword);
        if (!state.filters.class_id) {
          await refreshSchedules();
        }
      } catch (err) {
        showToast(err.message, 'error');
      }
    };
    const onNo = () => {
      elements.classConfirm.hidden = true;
      elements.classConfirm.removeEventListener('click', handleClick);
    };
    function handleClick(event) {
      if (event.target.dataset.action === 'class-confirm-yes') onYes();
      if (event.target.dataset.action === 'class-confirm-no') onNo();
    }
    elements.classConfirm.addEventListener('click', handleClick);
  }

  function bindClassEvents() {
    document.querySelector('[data-action="open-classes"]').addEventListener('click', openClassesDrawer);
    elements.classesDrawer.querySelectorAll('[data-action="close-classes"]').forEach((el) => {
      el.addEventListener('click', closeClassesDrawer);
    });
    elements.classSearchForm.addEventListener('submit', (event) => {
      event.preventDefault();
      const keyword = elements.classSearchInput.value.trim();
      void loadClassList(keyword);
    });
    document.querySelector('[data-action="open-class-form"]').addEventListener('click', openClassForm);
    elements.classForm.addEventListener('submit', handleClassSubmit);
    elements.classForm.querySelectorAll('[data-action="cancel-class-form"]').forEach((el) => {
      el.addEventListener('click', closeClassForm);
    });
    elements.classList.addEventListener('click', (event) => {
      const editBtn = event.target.closest('[data-action="edit-class"]');
      if (editBtn) {
        void openEditClassForm(editBtn.dataset.id);
        return;
      }
      const delBtn = event.target.closest('[data-action="delete-class"]');
      if (delBtn) {
        void handleClassDelete(delBtn.dataset.id, delBtn.dataset.name);
      }
    });
  }

  // ---------- Classroom management ----------

  function setClassroomStateMessage(message) {
    const node = elements.classroomState;
    if (!message) { node.hidden = true; node.textContent = ''; return; }
    node.hidden = false;
    node.textContent = message;
  }

  function renderClassroomList() {
    const list = state.classrooms.list;
    if (state.classrooms.loading) {
      elements.classroomList.innerHTML = '';
      setClassroomStateMessage('正在加载教室…');
      return;
    }
    if (!list.length) {
      elements.classroomList.innerHTML = '';
      setClassroomStateMessage(state.classrooms.keyword ? '没有匹配的教室' : '暂无教室');
      return;
    }
    setClassroomStateMessage('');
    elements.classroomList.innerHTML = list.map((r) => `
      <div class="classroom-row" data-id="${escapeHtml(r.id)}">
        <div class="classroom-cell classroom-name">${escapeHtml(r.name || '')}</div>
        <div class="classroom-cell">${escapeHtml(r.capacity != null && r.capacity !== 0 ? String(r.capacity) : '—')}</div>
        <div class="classroom-cell">${escapeHtml(r.location || '—')}</div>
        <div class="classroom-cell classroom-actions">
          <button type="button" class="danger" data-action="delete-classroom" data-id="${escapeHtml(r.id)}" data-name="${escapeHtml(r.name || '')}">删除</button>
        </div>
      </div>
    `).join('');
  }

  function openClassroomsDrawer() {
    state.classrooms.drawerOpen = true;
    elements.classroomsDrawer.hidden = false;
    elements.classroomsDrawer.setAttribute('aria-hidden', 'false');
    if (!state.classrooms.list.length && !state.classrooms.loading) {
      void loadClassroomList('');
    }
  }

  function closeClassroomsDrawer() {
    state.classrooms.drawerOpen = false;
    elements.classroomsDrawer.hidden = true;
    elements.classroomsDrawer.setAttribute('aria-hidden', 'true');
    elements.classroomConfirm.hidden = true;
  }

  async function loadClassroomList(keyword) {
    state.classrooms.loading = true;
    state.classrooms.keyword = keyword || '';
    renderClassroomList();
    try {
      const list = await client.listClassrooms(state.classrooms.keyword);
      if (!state.classrooms.drawerOpen) return;
      if ((keyword || '') !== state.classrooms.keyword) return;
      state.classrooms.list = list;
      state.classrooms.error = '';
    } catch (err) {
      if (!state.classrooms.drawerOpen) return;
      state.classrooms.list = [];
      state.classrooms.error = err.message || '加载失败';
      setClassroomStateMessage(`加载失败：${state.classrooms.error}`);
      elements.classroomList.innerHTML = '';
    } finally {
      if (state.classrooms.drawerOpen) {
        state.classrooms.loading = false;
        renderClassroomList();
      }
    }
  }

  function openClassroomForm() {
    state.classrooms.formOpen = true;
    state.classrooms.error = '';
    elements.classroomForm.hidden = false;
    elements.classroomFormError.hidden = true;
    elements.classroomFormError.textContent = '';
    elements.classroomForm.querySelectorAll('.error').forEach((el) => { el.textContent = ''; });
    elements.classroomForm.reset();
    elements.classroomForm.elements.name.focus();
  }

  function closeClassroomForm() {
    state.classrooms.formOpen = false;
    elements.classroomForm.hidden = true;
    elements.classroomForm.reset();
    elements.classroomForm.querySelectorAll('.error').forEach((el) => { el.textContent = ''; });
    elements.classroomFormError.hidden = true;
    elements.classroomFormError.textContent = '';
  }

  function readClassroomForm() {
    const data = {};
    for (const el of elements.classroomForm.elements) {
      if (!el.name) continue;
      data[el.name] = el.value;
    }
    return data;
  }

  function paintClassroomFormErrors(errors) {
    elements.classroomForm.querySelectorAll('.error').forEach((el) => { el.textContent = ''; });
    elements.classroomFormError.hidden = true;
    elements.classroomFormError.textContent = '';
    for (const [field, message] of Object.entries(errors)) {
      const target = elements.classroomForm.querySelector(`[data-error-for="${field}"]`);
      if (target) target.textContent = message;
    }
    if (Object.keys(errors).length) {
      elements.classroomFormError.hidden = false;
      elements.classroomFormError.textContent = '请修正表单错误后再提交';
    }
  }

  async function handleClassroomSubmit(event) {
    event.preventDefault();
    if (state.classrooms.submitting) return;
    const raw = readClassroomForm();
    const cleaned = cleanClassroomFields(raw);
    const errors = validateClassroom(cleaned);
    paintClassroomFormErrors(errors);
    if (Object.keys(errors).length) return;

    state.classrooms.submitting = true;
    elements.classroomSubmit.disabled = true;
    try {
      await client.createClassroom({
        name: cleaned.name,
        capacity: cleaned.capacity,
        location: cleaned.location
      });
      closeClassroomForm();
      showToast('已新增教室');
      await reloadClassroomData();
      await loadClassroomList(state.classrooms.keyword);
    } catch (err) {
      paintClassroomFormErrors({ _form: err.message });
      elements.classroomFormError.hidden = false;
      elements.classroomFormError.textContent = err.message;
    } finally {
      state.classrooms.submitting = false;
      elements.classroomSubmit.disabled = false;
    }
  }

  async function reloadClassroomData() {
    try {
      const classrooms = await client.listClassrooms('');
      const classroomsById = new Map(classrooms.map((c) => [c.id, c]));
      state.mappings = { ...state.mappings, classrooms: classroomsById };
      renderFilters();
    } catch (err) {
      showToast(`教室资料刷新失败：${err.message}`, 'error');
    }
  }

  function clearClassroomFilterIfDeleted(classroomId) {
    if (state.filters.classroom_id === classroomId) {
      state.filters.classroom_id = '';
      const classroomSelect = document.querySelector('[data-filter="classroom_id"]');
      if (classroomSelect) classroomSelect.value = '';
    }
  }

  async function handleClassroomDelete(id, name) {
    let refs;
    try {
      refs = await client.findClassroomReferences(id);
    } catch (err) {
      showToast('无法确认教室是否被引用，未执行删除', 'error');
      return;
    }
    const summary = summarizeClassroomReferences(refs);
    if (summary.hasReferences) {
      showToast(`该教室已被${summary.message}引用，无法删除`, 'error');
      return;
    }
    elements.classroomConfirmMessage.textContent = `确认删除教室「${name || ''}」？`;
    elements.classroomConfirm.hidden = false;

    const onYes = async () => {
      elements.classroomConfirm.hidden = true;
      elements.classroomConfirm.removeEventListener('click', handleClick);
      try {
        await client.deleteClassroom(id);
        showToast('已删除教室');
        clearClassroomFilterIfDeleted(id);
        await reloadClassroomData();
        await loadClassroomList(state.classrooms.keyword);
        if (!state.filters.classroom_id) {
          await refreshSchedules();
        }
      } catch (err) {
        showToast(err.message, 'error');
      }
    };
    const onNo = () => {
      elements.classroomConfirm.hidden = true;
      elements.classroomConfirm.removeEventListener('click', handleClick);
    };
    function handleClick(event) {
      if (event.target.dataset.action === 'classroom-confirm-yes') onYes();
      if (event.target.dataset.action === 'classroom-confirm-no') onNo();
    }
    elements.classroomConfirm.addEventListener('click', handleClick);
  }

  function bindClassroomEvents() {
    document.querySelector('[data-action="open-classrooms"]').addEventListener('click', openClassroomsDrawer);
    elements.classroomsDrawer.querySelectorAll('[data-action="close-classrooms"]').forEach((el) => {
      el.addEventListener('click', closeClassroomsDrawer);
    });
    elements.classroomSearchForm.addEventListener('submit', (event) => {
      event.preventDefault();
      const keyword = elements.classroomSearchInput.value.trim();
      void loadClassroomList(keyword);
    });
    elements.classroomForm.addEventListener('submit', handleClassroomSubmit);
    elements.classroomForm.querySelectorAll('[data-action="cancel-classroom-form"]').forEach((el) => {
      el.addEventListener('click', closeClassroomForm);
    });
    document.querySelector('[data-action="open-classroom-form"]').addEventListener('click', openClassroomForm);
    elements.classroomList.addEventListener('click', (event) => {
      const target = event.target.closest('[data-action="delete-classroom"]');
      if (!target) return;
      void handleClassroomDelete(target.dataset.id, target.dataset.name);
    });
  }

  // ---------- Teacher management ----------

  function setTeacherStateMessage(message) {
    const node = elements.teacherState;
    if (!message) { node.hidden = true; node.textContent = ''; return; }
    node.hidden = false;
    node.textContent = message;
  }

  function renderTeacherList() {
    const list = state.teachers.list;
    if (state.teachers.loading) {
      elements.teacherList.innerHTML = '';
      setTeacherStateMessage('正在加载教师…');
      return;
    }
    if (!list.length) {
      elements.teacherList.innerHTML = '';
      setTeacherStateMessage(state.teachers.keyword ? '没有匹配的教师' : '暂无教师');
      return;
    }
    setTeacherStateMessage('');
    elements.teacherList.innerHTML = list.map((t) => `
      <div class="teacher-row" data-id="${escapeHtml(t.id)}">
        <div class="teacher-cell teacher-name">${escapeHtml(t.name || '')}</div>
        <div class="teacher-cell">${escapeHtml(t.phone || '—')}</div>
        <div class="teacher-cell">${escapeHtml(t.email || '—')}</div>
        <div class="teacher-cell">${escapeHtml(t.specialty || '—')}</div>
        <div class="teacher-cell teacher-actions">
          <button type="button" class="danger" data-action="delete-teacher" data-id="${escapeHtml(t.id)}" data-name="${escapeHtml(t.name || '')}">删除</button>
        </div>
      </div>
    `).join('');
  }

  function openTeachersDrawer() {
    state.teachers.drawerOpen = true;
    elements.teachersDrawer.hidden = false;
    elements.teachersDrawer.setAttribute('aria-hidden', 'false');
    if (!state.teachers.list.length && !state.teachers.loading) {
      void loadTeacherList('');
    }
  }

  function closeTeachersDrawer() {
    state.teachers.drawerOpen = false;
    elements.teachersDrawer.hidden = true;
    elements.teachersDrawer.setAttribute('aria-hidden', 'true');
    elements.teacherConfirm.hidden = true;
  }

  async function loadTeacherList(keyword) {
    state.teachers.loading = true;
    state.teachers.keyword = keyword || '';
    renderTeacherList();
    try {
      const list = await client.listTeachers(state.teachers.keyword);
      // If the user has since cleared the drawer or typed a newer keyword, drop stale result.
      if (!state.teachers.drawerOpen) return;
      if ((keyword || '') !== state.teachers.keyword) return;
      state.teachers.list = list;
      state.teachers.error = '';
    } catch (err) {
      if (!state.teachers.drawerOpen) return;
      state.teachers.list = [];
      state.teachers.error = err.message || '加载失败';
      setTeacherStateMessage(`加载失败：${state.teachers.error}`);
      elements.teacherList.innerHTML = '';
    } finally {
      if (state.teachers.drawerOpen) {
        state.teachers.loading = false;
        renderTeacherList();
      }
    }
  }

  function openTeacherForm() {
    state.teachers.formOpen = true;
    state.teachers.error = '';
    elements.teacherForm.hidden = false;
    elements.teacherFormError.hidden = true;
    elements.teacherFormError.textContent = '';
    elements.teacherForm.querySelectorAll('.error').forEach((el) => { el.textContent = ''; });
    elements.teacherForm.reset();
    elements.teacherForm.elements.name.focus();
  }

  function closeTeacherForm() {
    state.teachers.formOpen = false;
    elements.teacherForm.hidden = true;
    elements.teacherForm.reset();
    elements.teacherForm.querySelectorAll('.error').forEach((el) => { el.textContent = ''; });
    elements.teacherFormError.hidden = true;
    elements.teacherFormError.textContent = '';
  }

  function readTeacherForm() {
    const data = {};
    for (const el of elements.teacherForm.elements) {
      if (!el.name) continue;
      data[el.name] = el.value;
    }
    return data;
  }

  function paintTeacherFormErrors(errors) {
    elements.teacherForm.querySelectorAll('.error').forEach((el) => { el.textContent = ''; });
    elements.teacherFormError.hidden = true;
    elements.teacherFormError.textContent = '';
    for (const [field, message] of Object.entries(errors)) {
      const target = elements.teacherForm.querySelector(`[data-error-for="${field}"]`);
      if (target) target.textContent = message;
    }
    if (Object.keys(errors).length) {
      elements.teacherFormError.hidden = false;
      elements.teacherFormError.textContent = '请修正表单错误后再提交';
    }
  }

  async function handleTeacherSubmit(event) {
    event.preventDefault();
    if (state.teachers.submitting) return;
    const raw = readTeacherForm();
    const cleaned = cleanTeacherFields(raw);
    const errors = validateTeacher(cleaned);
    paintTeacherFormErrors(errors);
    if (Object.keys(errors).length) return;

    state.teachers.submitting = true;
    elements.teacherSubmit.disabled = true;
    try {
      await client.createTeacher({
        name: cleaned.name,
        phone: cleaned.phone,
        email: cleaned.email,
        specialty: cleaned.specialty
      });
      closeTeacherForm();
      showToast('已新增教师');
      await reloadTeacherData();
      await loadTeacherList(state.teachers.keyword);
    } catch (err) {
      paintTeacherFormErrors({ _form: err.message });
      elements.teacherFormError.hidden = false;
      elements.teacherFormError.textContent = err.message;
    } finally {
      state.teachers.submitting = false;
      elements.teacherSubmit.disabled = false;
    }
  }

  async function reloadTeacherData() {
    try {
      const teachers = await client.listTeachers('');
      const teachersById = new Map(teachers.map((t) => [t.id, t]));
      state.mappings = { ...state.mappings, teachers: teachersById };
      renderFilters();
    } catch (err) {
      showToast(`教师资料刷新失败：${err.message}`, 'error');
    }
  }

  function clearTeacherFilterIfDeleted(teacherId) {
    if (state.filters.teacher_id === teacherId) {
      state.filters.teacher_id = '';
      const teacherSelect = document.querySelector('[data-filter="teacher_id"]');
      if (teacherSelect) teacherSelect.value = '';
    }
  }

  async function handleTeacherDelete(id, name) {
    let refs;
    try {
      refs = await client.findTeacherReferences(id);
    } catch (err) {
      showToast('无法确认教师是否被引用，未执行删除', 'error');
      return;
    }
    const summary = summarizeReferences(refs);
    if (summary.hasReferences) {
      showToast(`该教师已被${summary.message}引用，无法删除`, 'error');
      return;
    }
    elements.teacherConfirmMessage.textContent = `确认删除教师「${name || ''}」？`;
    elements.teacherConfirm.hidden = false;

    const onYes = async () => {
      elements.teacherConfirm.hidden = true;
      elements.teacherConfirm.removeEventListener('click', handleClick);
      elements.teacherConfirm.dataset.busy = '1';
      try {
        await client.deleteTeacher(id);
        showToast('已删除教师');
        clearTeacherFilterIfDeleted(id);
        await reloadTeacherData();
        await loadTeacherList(state.teachers.keyword);
        if (!state.filters.teacher_id) {
          await refreshSchedules();
        }
      } catch (err) {
        showToast(err.message, 'error');
      }
    };
    const onNo = () => {
      elements.teacherConfirm.hidden = true;
      elements.teacherConfirm.removeEventListener('click', handleClick);
    };
    function handleClick(event) {
      if (event.target.dataset.action === 'teacher-confirm-yes') onYes();
      if (event.target.dataset.action === 'teacher-confirm-no') onNo();
    }
    elements.teacherConfirm.addEventListener('click', handleClick);
  }

  function bindTeacherEvents() {
    document.querySelector('[data-action="open-teachers"]').addEventListener('click', openTeachersDrawer);
    elements.teachersDrawer.querySelectorAll('[data-action="close-teachers"]').forEach((el) => {
      el.addEventListener('click', closeTeachersDrawer);
    });
    elements.teacherSearchForm.addEventListener('submit', (event) => {
      event.preventDefault();
      const keyword = elements.teacherSearchInput.value.trim();
      void loadTeacherList(keyword);
    });
    elements.teacherForm.addEventListener('submit', handleTeacherSubmit);
    elements.teacherForm.querySelectorAll('[data-action="cancel-teacher-form"]').forEach((el) => {
      el.addEventListener('click', closeTeacherForm);
    });
    document.querySelector('[data-action="open-teacher-form"]').addEventListener('click', openTeacherForm);
    elements.teacherList.addEventListener('click', (event) => {
      const target = event.target.closest('[data-action="delete-teacher"]');
      if (!target) return;
      void handleTeacherDelete(target.dataset.id, target.dataset.name);
    });
  }

  function bindGlobalEvents() {
    document.querySelector('[data-action="prev-week"]').addEventListener('click', () => { state.weekStart = addDays(state.weekStart, -7); refreshSchedules(); });
    document.querySelector('[data-action="next-week"]').addEventListener('click', () => { state.weekStart = addDays(state.weekStart, 7); refreshSchedules(); });
    document.querySelector('[data-action="this-week"]').addEventListener('click', () => { state.weekStart = getWeekStart(new Date()); refreshSchedules(); });
    document.querySelector('[data-action="create"]').addEventListener('click', () => openCreateDrawer(formatDate(new Date()), config.availableTimePoints[0]));
    document.querySelectorAll('[data-action="close-drawer"]').forEach((el) => el.addEventListener('click', closeDrawer));
    elements.form.addEventListener('submit', handleFormSubmit);
    elements.form.elements.start_time.addEventListener('change', () => {
      const start = elements.form.elements.start_time.value;
      if (start && config.availableTimePoints.includes(start)) {
        elements.form.elements.end_time.value = addMinutes(start, config.slotMinutes);
      }
    });
    elements.filters.forEach((el) => {
      if (el.tagName === 'SELECT') {
        el.addEventListener('change', () => {
          state.filters[el.dataset.filter] = el.value;
          refreshSchedules();
        });
      }
    });
    const searchForm = document.querySelector('[data-role="search"]');
    searchForm.addEventListener('submit', (event) => {
      event.preventDefault();
      state.filters.topic = searchForm.querySelector('[data-role="topic-input"]').value.trim();
      refreshSchedules();
    });
  }

  async function bootstrap() {
    try {
      await loadBaseData();
    } catch (err) {
      showToast(`基础资料加载失败：${err.message}`, 'error');
    }
    bindGlobalEvents();
    bindTeacherEvents();
    bindClassEvents();
    bindClassroomEvents();
    await refreshSchedules();
  }

  bootstrap();
})();
