import { api } from './client/api.js';
import { createAdminViews, numberSegmentMeta } from './client/admin-views.js';
import { createCandidateViews } from './client/candidate-views.js';
import { createAdmissionViews } from './client/admission-views.js';
import { admissionCategoryEditor, indicatorAllocationEditor } from './client/admission-plan-editor.js';
import { createPublicViews } from './client/public-views.js';
import { state } from './client/state.js';
import { badge, dateRange, formatDate, h, icons, money, passPolicyText, statusLabels } from './client/ui.js';
import { formatRegionAddress, mountRegionSelects, updateRegionSelects } from './client/region-select.js';
import { specialtyCatalog } from './data/specialty-types.js';
import { getTableControl, setTableControl } from './client/table-state.js';
import { downloadAdmissionNotice, downloadScoreReport } from './client/pdf-export.js';

const app = document.querySelector('#app');
const modalRoot = document.querySelector('#modalRoot');
let toastTimer;
let noticeEditor;
let ckeditorModulePromise;
let tableSearchTimer;
let reportingCameraStream;
let reportingCameraFrame;
let reportingCameraToken = 0;
function toast(title, message = '') {
  const element = document.querySelector('#toast');
  element.querySelector('strong').textContent = title;
  element.querySelector('small').textContent = message;
  element.classList.add('show');
  clearTimeout(toastTimer);
  toastTimer = setTimeout(() => element.classList.remove('show'), 2800);
}

function setModal(content) {
  document.body.classList.remove('review-subpage-open');
  modalRoot.innerHTML = `<div class="modal-layer" data-modal-backdrop><div class="modal-card" role="dialog" aria-modal="true">${content}</div></div>`;
  setTimeout(() => modalRoot.querySelector('input,textarea,button')?.focus(), 30);
}

function setReviewSubpage(content) {
  document.body.classList.add('review-subpage-open');
  modalRoot.innerHTML = `<div class="review-subpage-layer"><main class="review-subpage" role="dialog" aria-modal="true">${content}</main></div>`;
  setTimeout(() => modalRoot.querySelector('button,input,textarea,select')?.focus(), 30);
}

function stopReportingCamera() {
  reportingCameraToken += 1;
  if (reportingCameraFrame) cancelAnimationFrame(reportingCameraFrame);
  reportingCameraFrame = null;
  reportingCameraStream?.getTracks().forEach(track => track.stop());
  reportingCameraStream = null;
}

function closeModal() {
  stopReportingCamera();
  const editor = noticeEditor;
  noticeEditor = null;
  if (editor) editor.destroy().catch(error => console.error('CKEditor cleanup failed', error));
  document.body.classList.remove('review-subpage-open');
  modalRoot.innerHTML = '';
}

function reportingScanConfirmation(preview, rawCode) {
  stopReportingCamera();
  const row = preview.row;
  setModal(`<div class="modal-head reporting-confirm-head"><div><span>NOTICE VERIFICATION</span><h2>确认考生报到</h2><p>二维码已通过防伪核验。请核对考生信息，选择结论后暂存。</p></div><button type="button" data-action="close-modal">×</button></div><form class="reporting-confirm-form" data-form="admission-reporting-scan-confirm"><input type="hidden" name="examId" value="${h(preview.examId)}"><input type="hidden" name="code" value="${h(rawCode)}"><section class="reporting-candidate-card"><div class="candidate-stamp">核验通过</div><dl><div><dt>考生姓名</dt><dd>${h(row.name)}</dd></div><div><dt>报名号</dt><dd class="mono">${h(row.candidateNumber)}</dd></div><div><dt>通知书编号</dt><dd class="mono">${h(row.noticeNumber)}</dd></div><div><dt>录取类别</dt><dd>${h(row.categoryName || '—')}</dd></div></dl></section><fieldset class="reporting-decision-options"><legend>报到确认结果</legend><label class="selected"><input type="radio" name="status" value="reported" checked><span><strong>确认报到</strong><small>默认选项，暂存为 Y · 已报到</small></span></label><label><input type="radio" name="status" value="not_reported"><span><strong>确认未报到</strong><small>暂存为 N · 未报到</small></span></label><label><input type="radio" name="status" value="pending"><span><strong>暂待确认</strong><small>暂存为 P · 待确认</small></span></label></fieldset><label class="reporting-confirm-note"><span>报到备注</span><input name="note" value="${h(row.note || '')}" placeholder="选填，系统会为扫码操作生成默认备注"></label><div class="modal-foot"><button type="button" class="ghost-button" data-action="open-reporting-camera" data-exam-id="${h(preview.examId)}">重新扫描</button><button type="submit" class="solid-button">暂存</button></div></form>`);
}

async function previewReportingScan(code, examId) {
  const preview = await api('/api/admission/reporting/scan-preview', { method: 'POST', body: { code, examId } });
  reportingScanConfirmation(preview, code);
  return preview;
}

async function openReportingCamera(examId) {
  stopReportingCamera();
  const token = reportingCameraToken;
  setModal(`<div class="modal-head reporting-camera-head"><div><span>LIVE CAMERA</span><h2>扫描录取通知书二维码</h2><p>请将二维码放入取景框，识别后会进入报到确认页，不会直接保存。</p></div><button type="button" data-action="close-modal">×</button></div><section class="reporting-camera-stage"><video data-reporting-camera autoplay muted playsinline></video><div class="scan-frame"><i></i><i></i><i></i><i></i><span>正在等待二维码进入画面</span></div></section><p class="reporting-camera-status" data-reporting-camera-status>正在请求相机权限…</p><div class="reporting-camera-fallback"><form data-form="admission-reporting-scan-preview"><input type="hidden" name="examId" value="${h(examId)}"><input name="code" placeholder="无法使用相机时，可粘贴二维码链接或 AN 防伪码" required><button class="ghost-button" type="submit">核验</button></form><label class="qr-capture">选择二维码图片<input type="file" accept="image/*" data-reporting-qr-file data-exam-id="${h(examId)}" hidden></label></div>`);
  const video = modalRoot.querySelector('[data-reporting-camera]');
  const status = modalRoot.querySelector('[data-reporting-camera-status]');
  try {
    if (!navigator.mediaDevices?.getUserMedia) throw new Error('当前浏览器未提供相机访问能力');
    reportingCameraStream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: { ideal: 'environment' } }, audio: false });
    if (token !== reportingCameraToken) { reportingCameraStream.getTracks().forEach(track => track.stop()); return; }
    video.srcObject = reportingCameraStream;
    await video.play();
    if (!('BarcodeDetector' in window)) throw new Error('相机已打开，但当前浏览器不支持自动识别二维码，请使用下方防伪码核验');
    const detector = new BarcodeDetector({ formats: ['qr_code'] });
    status.textContent = '相机已开启，请将二维码对准取景框';
    let detecting = false;
    const scan = async () => {
      if (token !== reportingCameraToken || !reportingCameraStream) return;
      if (!detecting && video.readyState >= 2) {
        detecting = true;
        try {
          const codes = await detector.detect(video);
          const code = codes[0]?.rawValue;
          if (code) {
            status.textContent = '已识别二维码，正在核验…';
            await previewReportingScan(code, examId);
            return;
          }
        } catch (error) {
          if (token !== reportingCameraToken) return;
          status.textContent = error.message || '二维码核验失败，请重新对准取景框';
        } finally { detecting = false; }
      }
      reportingCameraFrame = requestAnimationFrame(scan);
    };
    scan();
  } catch (error) {
    if (token !== reportingCameraToken) return;
    status.textContent = `无法使用相机：${error.message}。可检查权限，或使用下方备用方式。`;
    status.classList.add('error');
  }
}

function loadCKEditor() {
  if (!document.querySelector('link[data-ckeditor-styles]')) {
    const stylesheet = document.createElement('link');
    stylesheet.rel = 'stylesheet';
    stylesheet.href = '/vendor/ckeditor5/ckeditor5.css';
    stylesheet.dataset.ckeditorStyles = '';
    document.head.append(stylesheet);
  }
  ckeditorModulePromise ||= Promise.all([
    import('/vendor/ckeditor5/ckeditor5.js'),
    import('/vendor/ckeditor5/translations/zh-cn.js')
  ]);
  return ckeditorModulePromise;
}

function emptyState(title, description, route, action) {
  return `<section class="panel empty-panel"><span>${icons.ticket}</span><h2>${h(title)}</h2><p>${h(description)}</p>${route ? `<button class="solid-button" data-route="${h(route)}">${h(action)}</button>` : ''}</section>`;
}

function renderError(error) {
  if (error?.status === 401) return requireLogin();
  console.error('Page failed to render', error);
  const message = error?.status ? error.message : '请求未能完成，请稍后重试或返回首页。';
  app.innerHTML = `<section class="fatal-error"><span>!</span><h1>页面暂时无法加载</h1><p>${h(message)}</p><div class="fatal-error-actions"><button class="ghost-button" data-route="home">返回首页</button><button class="solid-button" data-action="retry">重试</button></div></section>`;
}

function requireLogin() {
  state.user = null;
  state.profile = null;
  state.permissions = [];
  state.scopeLabel = '';
  state.pageData = null;
  state.resultExamFilter = '';
  state.resultSubjectFilter = '';
  state.resultExamCatalog = null;
  state.authNotice = '登录状态已失效，请重新登录。';
  navigate('login');
}

const baseViewContext = { state, app, h, formatDate, dateRange, badge, money, passPolicyText, statusLabels, icons, api, renderError, requireLogin, emptyState };
const { brand, renderHome, renderNoticeCenter, renderAuth, renderVerification } = createPublicViews(baseViewContext);
const { adminNavForUser, portalShell, loadingPanel, renderCandidate, accountSecurity } = createCandidateViews({ ...baseViewContext, brand });
const { renderAdmin, workflowStepEditor } = createAdminViews({ ...baseViewContext, brand, portalShell, loadingPanel, adminNavForUser, accountSecurity });
const { renderAdmission } = createAdmissionViews({ ...baseViewContext, brand });

function navigate(route) {
  location.hash = route;
  if (location.hash.slice(1) === route) renderRoute();
}

async function renderRoute() {
  closeModal();
  window.scrollTo({ top: 0, behavior: 'instant' });
  const route = location.hash.slice(1) || 'home';
  const [section, page = 'dashboard'] = route.split('/');
  if (section !== 'login') state.authNotice = '';
  if (section === 'home') {
    if (window.__EIS_VUE_SHELL__) {
      location.reload();
      return;
    }
    renderHome();
  }
  else if (section === 'verify') {
    if (!page || page === 'dashboard') renderVerification();
    else {
      try { renderVerification(page, await api(`/api/public/verifications/${encodeURIComponent(page)}`)); }
      catch (error) { renderVerification(page, null, error.message); }
    }
  }
  else if (section === 'notices' || section === 'announcements') { state.publicAnnouncements = await api('/api/public/announcements'); renderNoticeCenter(state.publicAnnouncements); }
  else if (section === 'notice') { state.publicAnnouncements = await api('/api/public/announcements'); renderNoticeCenter(state.publicAnnouncements, page); }
  else if (section === 'login' || section === 'register') renderAuth(section);
  else if (section === 'candidate') await renderCandidate(page);
  else if (section === 'admin') await renderAdmin(page);
  else if (section === 'admission_school') await renderAdmission(page);
  else navigate('home');
  restoreTableControls();
}

function restoreTableControls() {
  document.querySelectorAll('[data-action="table-search"][data-target]').forEach(input => { input.value = getTableControl(state, input.dataset.target).query || ''; });
  document.querySelectorAll('[data-action="status-filter"][data-target]').forEach(button => button.classList.toggle('active', (getTableControl(state, button.dataset.target).status || 'all') === button.dataset.status));
  document.querySelectorAll('[data-table-filter][data-target]').forEach(select => { select.value = getTableControl(state, select.dataset.target).filters?.[select.dataset.tableFilter] || ''; });
}

function formObject(form) {
  return Object.fromEntries(new FormData(form).entries());
}

function updateRegistrationSelection() {
  const table = document.querySelector('#registrationTable');
  if (!table) return;
  const selectable = [...table.querySelectorAll('[data-registration-select]:not(:disabled)')];
  const visible = selectable.filter(input => !input.closest('tr').hidden);
  const selected = selectable.filter(input => input.checked);
  const selectAll = table.querySelector('[data-registration-select-all]');
  if (selectAll) {
    selectAll.checked = visible.length > 0 && visible.every(input => input.checked);
    selectAll.indeterminate = visible.some(input => input.checked) && !selectAll.checked;
  }
  const count = document.querySelector('[data-registration-selection-count]');
  if (count) count.textContent = selected.length;
  document.querySelectorAll('[data-action="bulk-registration-review"]').forEach(button => { button.disabled = selected.length === 0; });
}

function updateCandidateSelection() {
  const table = document.querySelector('#candidateTable');
  if (!table) return;
  const selectable = [...table.querySelectorAll('[data-candidate-select]:not(:disabled)')];
  const visible = selectable.filter(input => !input.closest('tr').hidden);
  const selected = selectable.filter(input => input.checked);
  const selectAll = table.querySelector('[data-candidate-select-all]');
  if (selectAll) {
    selectAll.checked = visible.length > 0 && visible.every(input => input.checked);
    selectAll.indeterminate = visible.some(input => input.checked) && !selectAll.checked;
  }
  const count = document.querySelector('[data-candidate-selection-count]');
  if (count) count.textContent = selected.length;
  document.querySelectorAll('[data-action="bulk-candidate-review"]').forEach(button => { button.disabled = selected.length === 0; });
}

function updatePaymentSelection() {
  const table = document.querySelector('#paymentTable');
  if (!table) return;
  const selectable = [...table.querySelectorAll('[data-payment-select]:not(:disabled)')];
  const visible = selectable.filter(input => !input.closest('tr').hidden);
  const selected = selectable.filter(input => input.checked);
  const selectAll = table.querySelector('[data-payment-select-all]');
  if (selectAll) {
    selectAll.checked = visible.length > 0 && visible.every(input => input.checked);
    selectAll.indeterminate = visible.some(input => input.checked) && !selectAll.checked;
  }
  const count = document.querySelector('[data-payment-selection-count]');
  if (count) count.textContent = selected.length;
  document.querySelectorAll('[data-action="bulk-payment-update"]').forEach(button => { button.disabled = selected.length === 0; });
}

function updateQualificationSelection(container) {
  if (!container) return;
  const rows = [...container.querySelectorAll('[data-qualification-select]')];
  const visible = rows.filter(input => !input.closest('tr').hidden);
  const selected = rows.filter(input => input.checked);
  const selectAll = container.querySelector('[data-action="qualification-select-all"]');
  if (selectAll) {
    selectAll.checked = visible.length > 0 && visible.every(input => input.checked);
    selectAll.indeterminate = visible.some(input => input.checked) && !selectAll.checked;
  }
  const count = container.querySelector('[data-qualification-selected-count]');
  if (count) count.textContent = `已选 ${selected.length} 人`;
}

function updatePlacementSelection() {
  const table = document.getElementById('placementReviewTable');
  if (!table) return;
  const selectable = [...table.querySelectorAll('[data-placement-select]:not(:disabled)')];
  const visible = selectable.filter(input => !input.closest('tr').hidden);
  const selected = selectable.filter(input => input.checked);
  const selectAll = document.querySelector('[data-placement-select-all][data-target="placementReviewTable"]');
  if (selectAll) {
    selectAll.checked = visible.length > 0 && visible.every(input => input.checked);
    selectAll.indeterminate = visible.some(input => input.checked) && !selectAll.checked;
  }
  const count = document.querySelector('[data-placement-selected-count]');
  if (count) count.textContent = `已选 ${selected.length} 人`;
}

function applyTableFilters(tableId) {
  const table = document.getElementById(tableId);
  if (!table) return;
  const search = [...document.querySelectorAll('[data-action="table-search"]')].find(input => input.dataset.target === tableId);
  const query = search?.value.trim().toLowerCase() || '';
  const status = [...document.querySelectorAll('[data-action="status-filter"]')].find(button => button.dataset.target === tableId && button.classList.contains('active'))?.dataset.status || 'all';
  const filters = [...document.querySelectorAll('[data-table-filter]')].filter(select => select.dataset.target === tableId && select.value);
  table.querySelectorAll('[data-filter-row], tbody tr[data-status]').forEach(row => {
    const matchesSearch = !query || row.textContent.toLowerCase().includes(query);
    const matchesStatus = status === 'all' || String(row.dataset.status || '').split(/\s+/).includes(status);
    const matchesFilters = filters.every(select => String(row.dataset[select.dataset.tableFilter] || '').split('|').includes(select.value));
    row.hidden = !(matchesSearch && matchesStatus && matchesFilters);
  });
  if (tableId === 'registrationTable') updateRegistrationSelection();
  if (tableId === 'candidateTable') updateCandidateSelection();
  if (tableId === 'paymentTable') updatePaymentSelection();
  if (tableId === 'placementReviewTable') updatePlacementSelection();
  if (table.closest('.qualification-ledger')) updateQualificationSelection(table.closest('.qualification-ledger'));
}

function updateResultScoreInput(input) {
  const row = input.closest('tr');
  const error = row?.querySelector('[data-score-error]');
  const value = input.value.trim();
  const score = Number(value);
  const max = Number(input.max);
  const invalid = value !== '' && (!Number.isFinite(score) || score < 0 || score > max || !Number.isInteger(score * 2));
  input.dataset.dirty = String(value !== input.defaultValue);
  input.setAttribute('aria-invalid', String(invalid));
  row?.classList.toggle('score-row-invalid', invalid);
  if (error) error.textContent = invalid ? `须在 0—${max} 之间，且只能填写整数或 .5` : '';
  const form = input.closest('form');
  const dirty = form?.querySelectorAll('[data-result-score][data-dirty="true"]').length || 0;
  const count = form?.querySelector('[data-result-dirty-count]');
  if (count) count.textContent = dirty ? `${dirty} 条成绩尚未暂存` : '尚无未保存修改';
}

function updateFeatureScoreInput(input) {
  const row = input.closest('tr');
  const error = row?.querySelector('[data-feature-score-error]');
  const value = input.value.trim();
  const score = Number(value);
  const invalid = value === '' || !Number.isFinite(score) || score < 0 || score > 1000;
  input.dataset.dirty = String(value !== input.defaultValue);
  input.setAttribute('aria-invalid', String(invalid));
  row?.classList.toggle('score-row-invalid', invalid);
  if (error) error.textContent = invalid ? '须在 0—1000 之间' : '';
  const form = input.closest('form');
  const dirty = form?.querySelectorAll('[data-feature-score][data-dirty="true"]').length || 0;
  const count = form?.querySelector('[data-feature-dirty-count]');
  if (count) count.textContent = dirty ? `${dirty} 条特征分尚未保存` : '尚无未保存修改';
}

async function refreshPublic() {
  state.publicData = await api('/api/public/home');
}

async function refreshSession() {
  const session = await api('/api/auth/me');
  state.user = session.user;
  state.profile = session.profile;
  state.permissions = session.permissions || [];
  state.scopeLabel = session.scopeLabel || '';
}

async function finishLogin(data) {
  state.user = data.user;
  state.authNotice = '';
  await refreshSession();
  closeModal();
  toast(data.usedRecoveryCode ? '已使用恢复码登录' : '登录成功', data.usedRecoveryCode ? '该恢复码已失效，请检查剩余恢复码' : `欢迎，${data.user.displayName}`);
  navigate(data.user.role === 'candidate' && (state.user.mustChangePassword || !state.profile?.profileCompleted) ? 'candidate/onboarding' : `${data.user.role}/dashboard`);
}

function showRecoveryCodes(codes) {
  setModal(`<div class="modal-head"><div><span>RECOVERY CODES</span><h2>保存一次性恢复码</h2><p>手机丢失或验证器不可用时，可使用其中一个恢复码登录。每个只能使用一次。</p></div></div><div class="recovery-code-sheet" data-recovery-codes>${codes.map(code => `<code>${h(code)}</code>`).join('')}</div><div class="security-warning">请立即复制并离线保存。关闭后系统不会再次显示这些恢复码。</div><div class="modal-foot"><button class="ghost-button" type="button" data-action="copy-recovery-codes">复制全部</button><button class="solid-button" type="button" data-action="close-modal-refresh">我已安全保存</button></div>`);
}

document.addEventListener('click', async event => {
  const routeTarget = event.target.closest('[data-route]');
  if (routeTarget) {
    event.preventDefault();
    return navigate(routeTarget.dataset.route);
  }
  if (event.target.matches('[data-modal-backdrop]')) return closeModal();
  const target = event.target.closest('[data-action]');
  if (!target) return;
  const action = target.dataset.action;
  try {
    if (action === 'close-modal') return closeModal();
    if (action === 'close-modal-refresh') { closeModal(); return renderRoute(); }
    if (action === 'copy-recovery-codes') {
      const codes = [...document.querySelectorAll('[data-recovery-codes] code')].map(item => item.textContent).join('\n');
      await navigator.clipboard.writeText(codes);
      return toast('恢复码已复制', '请保存到可信的离线位置');
    }
    if (action === 'copy-totp-secret') {
      const secret = document.querySelector('[data-totp-secret]')?.textContent.replace(/\s/g, '') || '';
      await navigator.clipboard.writeText(secret);
      return toast('手动密钥已复制');
    }
    if (action === 'retry') return renderRoute();
    if (action === 'open-sidebar') return document.querySelector('#portalSidebar')?.classList.add('open');
    if (action === 'close-sidebar') return document.querySelector('#portalSidebar')?.classList.remove('open');
    if (action === 'toggle-public-nav') return document.querySelector('.public-header nav')?.classList.toggle('open');
    if (action === 'scroll-to') {
      event.preventDefault();
      if (!document.querySelector(`#${target.dataset.target}`)) { navigate('home'); setTimeout(() => document.querySelector(`#${target.dataset.target}`)?.scrollIntoView({ behavior: 'smooth' }), 80); }
      else document.querySelector(`#${target.dataset.target}`).scrollIntoView({ behavior: 'smooth' });
      return;
    }
    if (action === 'logout') {
      await api('/api/auth/logout', { method: 'POST' });
      state.user = null; state.profile = null; state.pageData = null; state.permissions = []; state.scopeLabel = ''; state.resultExamFilter = ''; state.resultSubjectFilter = ''; state.resultExamCatalog = null;
      await refreshPublic(); navigate('home'); toast('已安全退出', '期待下次见面'); return;
    }
    if (action === 'open-notice') {
      return navigate(`notice/${target.dataset.id}`);
    }
    if (action === 'notice-category') {
      state.noticeCategory = target.dataset.category;
      state.noticePage = 1;
      renderNoticeCenter(state.publicAnnouncements);
      return;
    }
    if (action === 'notice-page') {
      if (target.disabled) return;
      state.noticePage = Number(target.dataset.page || 1);
      renderNoticeCenter(state.publicAnnouncements);
      window.scrollTo({ top: 0, behavior: 'smooth' });
      return;
    }
    if (action === 'download-admit') { window.location.href = `/api/candidate/registrations/${target.dataset.id}/admit-card`; return; }
    if (action === 'download-score-report') {
      const examId = target.dataset.examId;
      const results = (state.pageData?.results || []).filter(item => item.examId === examId);
      const summary = (state.pageData?.summaries || []).find(item => item.examId === examId);
      if (!results.length || !summary?.verificationCode) return toast('成绩单暂不可下载', '请刷新页面后重试');
      await downloadScoreReport({ organization: state.publicData.organization, candidate: state.pageData.candidate || { name: state.user.displayName, candidateNumber: state.user.candidateNumber }, exam: { id: examId, name: results[0].examName, code: results[0].examCode }, results, summary: { ...summary, publishedAt: [...results].sort((a,b) => new Date(b.publishedAt) - new Date(a.publishedAt))[0]?.publishedAt }, verificationCode: summary.verificationCode, verificationQr: summary.verificationQr, verificationUrl: `${location.origin}/#verify/${summary.verificationCode}` });
      return toast('PDF 成绩单已生成', '文件包含防伪查询码');
    }
    if (action === 'download-admission-notice') {
      const item = (state.pageData?.admissions || []).find(entry => entry.examId === target.dataset.examId);
      if (!item?.placement || item.placement.status !== 'final' || !item.noticeVerificationCode) return toast('录取通知书暂不可下载', '只有正式录取后才能生成');
      await downloadAdmissionNotice({ organization: state.publicData.organization, candidate: { name: state.profile?.name || state.user.displayName }, exam: item.exam, placement: item.placement, school: item.placementSchool || { name: item.placement.schoolName || '招生学校' }, template: item.noticeTemplate || {}, verificationCode: item.noticeVerificationCode, verificationQr: item.noticeVerificationQr, noticeNumber: item.noticeNumber, verificationUrl: `${location.origin}/#verify/${item.noticeVerificationCode}` });
      return toast('录取通知书已生成', '请核对学校和录取类别');
    }
    if (action === 'download-admitted-candidates') {
      const examId = target.closest('.admission-export-bar')?.querySelector('[name="exportExamId"]')?.value;
      if (!examId) return toast('请选择考试', '仅录取工作结束的考试可以下载');
      window.location.href = `/api/admission/placements/export?examId=${encodeURIComponent(examId)}`;
      return;
    }
    if (action === 'admission-plan-review') {
      const reviewNote = window.prompt(target.dataset.status === 'approved' ? '填写审核意见（可留空）' : '请填写退回原因', '') ?? null;
      if (reviewNote == null) return;
      await api(`/api/admin/admission-plans/${target.dataset.id}`, { method: 'PATCH', body: { status: target.dataset.status, reviewNote } });
      toast(target.dataset.status === 'approved' ? '招生计划已通过并自动公示' : '招生计划已退回', target.dataset.status === 'approved' ? '公开通知公告已同步生成招生计划公示' : '招生学校可以修改后重新提交'); return renderRoute();
    }
    if (action === 'admission-match') {
      if (!window.confirm('确认按“分数优先、遵循志愿”执行本轮投档？填报顺序将锁定。')) return;
      const data = await api(`/api/admin/admissions/${target.dataset.examId}/match`, { method: 'POST' });
      toast('投档完成', `${data.placementCount} 名考生已发送招生学校`); return renderRoute();
    }
    if (action === 'admission-finalize') {
      if (!window.confirm('确认结束本次录取？系统会向考生发送通知，并按设置自动公示。')) return;
      const data = await api(`/api/admin/admissions/${target.dataset.examId}/finalize`, { method: 'POST' });
      await refreshPublic(); toast('录取工作已结束', `${data.admittedCount} 人正式录取`); return renderRoute();
    }
    if (action === 'admission-supplementary') {
      const preferenceEnd = window.prompt('请输入补录志愿截止时间（例如 2026-07-25T18:00）', '');
      if (!preferenceEnd) return;
      await api(`/api/admin/admissions/${target.dataset.examId}/supplementary`, { method: 'POST', body: { preferenceEnd } });
      toast('补录已开启', '未录取考生可以填报新一轮志愿'); return renderRoute();
    }
    if (action === 'withdrawal-review') {
      const approved = target.dataset.approved === 'true';
      const reviewNote = window.prompt(approved ? '填写批准退档意见' : '填写驳回退档意见', '');
      if (reviewNote == null) return;
      await api(`/api/admin/admission-withdrawals/${target.dataset.id}`, { method: 'PATCH', body: { approved, reviewNote } });
      toast(approved ? '退档已批准' : '退档申请已驳回'); return renderRoute();
    }
    if (action === 'save-indicator-qualification') {
      const row = target.closest('tr');
      const value = row?.querySelector('[data-indicator-eligible]')?.value;
      if (!value) return toast('请选择资格结论', '必须明确选择有资格或无资格');
      const data = await api(`/api/admin/indicator-qualifications/${row.dataset.examId}/${row.dataset.userId}`, { method: 'PUT', body: { eligible: value === 'true' } });
      toast(data.published ? '资格已确认并自动公示' : '资格已确认', data.published ? '本校全部考生已确认完成' : '继续核对其他考生');
      return renderRoute();
    }
    if (action === 'qualification-select-all') {
      const container = target.closest('[data-qualification-bulk]')?.closest('.qualification-ledger');
      container?.querySelectorAll('[data-qualification-select]').forEach(input => {
        if (!input.closest('tr').hidden) input.checked = target.checked;
      });
      updateQualificationSelection(container);
      return;
    }
    if (action === 'download-admission-reporting') {
      window.location.href = `/api/admission/reporting/export?examId=${encodeURIComponent(target.dataset.examId)}`;
      return;
    }
    if (action === 'open-reporting-camera') {
      await openReportingCamera(target.dataset.examId);
      return;
    }
    if (action === 'bulk-reporting-apply') {
      const toolbar = target.closest('[data-reporting-bulk]');
      const form = toolbar?.closest('form[data-form="admission-reporting-draft"]');
      const table = document.getElementById(toolbar?.dataset.tableId || '');
      const selected = [...(table?.querySelectorAll('[data-reporting-select]:checked') || [])];
      if (!selected.length) return toast('请先选择考生', '可勾选单人或全选当前页');
      const status = toolbar.querySelector('[data-reporting-bulk-status]').value;
      const note = toolbar.querySelector('[data-reporting-bulk-note]').value.trim();
      selected.forEach(input => {
        form.querySelector(`[data-reporting-status][data-placement-id="${CSS.escape(input.value)}"]`).value = status;
        if (note) form.querySelector(`[data-reporting-note][data-placement-id="${CSS.escape(input.value)}"]`).value = note;
      });
      toast('批量修改已应用', `已修改 ${selected.length} 名考生，请点击“暂存当前页”保存`);
      return;
    }
    if (action === 'submit-admission-reporting') {
      if (!window.confirm('确认提交本轮全部报到情况？提交后需先完成补录决定，才能交由超级管理员审批。')) return;
      await api('/api/admission/reporting/submit', { method: 'POST', body: { examId: target.dataset.examId } });
      toast('报到情况已提交', '请根据计划完成率确认是否申请补录');
      return renderRoute();
    }
    if (action === 'review-admission-reporting') {
      const approved = target.dataset.approved === 'true';
      const supplement = target.dataset.supplement === 'true';
      const approvalNote = window.prompt(approved ? '请输入审批意见（可选）' : '请输入退回原因', '') ?? '';
      if (!approved && approvalNote.trim().length < 2) return toast('需要退回原因', '请说明招生学校应修改的内容');
      let preferenceEnd = '';
      if (approved && supplement) {
        preferenceEnd = window.prompt('请输入补录志愿截止时间（例如 2026-07-30T18:00）', '') ?? '';
        if (!preferenceEnd) return;
      }
      await api(`/api/admin/admission-reporting/${encodeURIComponent(target.dataset.id)}`, { method: 'PATCH', body: { approved, approvalNote, preferenceEnd } });
      toast(approved ? '审批完成并自动公开' : '已退回招生学校', approved ? (supplement ? '全部学校审批完成后将自动开启补录' : '报到统计已进入公开通知') : '招生学校可修改暂存数据后重新提交');
      return renderRoute();
    }
    if (action === 'admission-ledger-export') {
      const ledger = target.dataset.ledger;
      if (!['preferences', 'placements'].includes(ledger)) return;
      const query = new URLSearchParams();
      if (target.dataset.examId) query.set('examId', target.dataset.examId);
      if (target.dataset.round) query.set('round', target.dataset.round);
      if (target.dataset.table) {
        const control = getTableControl(state, target.dataset.table);
        if (String(control.query || '').trim()) query.set('q', String(control.query).trim());
        if (control.status && control.status !== 'all') query.set('status', control.status);
        Object.entries(control.filters || {}).forEach(([key, value]) => { if (value) query.set(key, value); });
      }
      window.location.href = `/api/admin/admissions/${ledger}/export?${query}`;
      return;
    }
    if (action === 'clear-table-filters') {
      const tableId = target.dataset.target;
      state.tableFilters[tableId] = { query: '', status: 'all', filters: {} };
      document.querySelectorAll(`[data-table-filter][data-target="${tableId}"]`).forEach(select => { select.value = ''; });
      document.querySelectorAll(`[data-action="table-search"][data-target="${tableId}"]`).forEach(input => { input.value = ''; });
      document.querySelectorAll(`[data-action="status-filter"][data-target="${tableId}"]`).forEach((button, index) => button.classList.toggle('active', index === 0));
      return renderRoute();
    }
    if (action === 'bulk-indicator-qualification') {
      const toolbar = target.closest('[data-qualification-bulk]');
      const ledger = toolbar?.closest('.qualification-ledger');
      const userIds = [...ledger.querySelectorAll('[data-qualification-select]:checked')].map(input => input.closest('tr').dataset.userId);
      const value = toolbar.querySelector('[data-bulk-eligible]').value;
      if (!userIds.length) return toast('请先选择考生', '可使用左侧复选框或全选');
      if (!value) return toast('请选择批量资格结论');
      if (!window.confirm(`确认将所选 ${userIds.length} 名考生批量设为“${value === 'true' ? '有' : '无'}指标分配资格”？`)) return;
      const data = await api(`/api/admin/indicator-qualifications/${toolbar.dataset.examId}/bulk`, { method: 'PUT', body: { userIds, eligible: value === 'true' } });
      toast(data.published ? '批量确认完成并自动公示' : '批量确认完成', `已更新 ${data.count} 名考生`);
      return renderRoute();
    }
    if (action === 'bulk-placement-review') {
      const ids = [...document.querySelectorAll('#placementReviewTable [data-placement-select]:checked')].map(input => input.value);
      const decision = target.dataset.decision;
      if (!ids.length) return toast('请先选择待审核考生', '可勾选单人，或选择当前筛选结果');
      let note = '';
      if (decision === 'withdraw') {
        note = window.prompt(`将为所选 ${ids.length} 名考生申请退档，请填写统一的特殊理由（至少 8 个字）`, '') ?? '';
        if (!note) return;
        if (note.trim().length < 8) return toast('退档理由至少需要 8 个字');
      } else if (!window.confirm(`确认批量接收所选 ${ids.length} 名投档考生吗？`)) return;
      const result = await api('/api/admission/placements/bulk', { method: 'POST', body: { ids, decision, note } });
      toast(decision === 'accept' ? '批量接收完成' : '批量退档申请已提交', `已处理 ${result.count} 名考生`);
      return renderRoute();
    }
    if (action === 'toggle-admin-account') {
      const active = target.dataset.active === 'true';
      if (!active && !window.confirm('确认停用该管理员账户？其现有登录会话会立即失效，历史审批记录将保留。')) return;
      await api(`/api/admin/admins/${target.dataset.id}`, { method: 'PATCH', body: { active } });
      toast(active ? '管理员账户已启用' : '管理员账户已停用', active ? '该账号可以重新登录' : '现有会话已结束，历史记录仍保留');
      return renderRoute();
    }
    if (action === 'reset-admin-password') {
      if (!window.confirm('确认重置该管理员密码？其现有登录会话会立即失效。')) return;
      const data = await api(`/api/admin/admins/${target.dataset.id}/reset-password`, { method: 'POST' });
      setModal(`<div class="modal-head"><div><span>ADMIN PASSWORD RESET</span><h2>管理员临时密码已生成</h2><p>请通过线下安全渠道交给管理员，本页面关闭后不再展示。</p></div><button data-action="close-modal">×</button></div><div class="issued-number"><span>登录账号</span><strong>${h(data.username)}</strong><span>临时密码</span><strong>${h(data.temporaryPassword)}</strong><p>旧密码和现有登录会话均已失效。</p></div><div class="modal-foot"><button class="solid-button" data-action="close-modal">我已保存</button></div>`);
      return;
    }
    if (action === 'toggle-admission-account') {
      const active = target.dataset.active === 'true';
      if (!active && !window.confirm('确认停用该招生学校账户？现有登录会话会立即失效，历史审核记录继续保留。')) return;
      await api(`/api/admin/admission-school-accounts/${target.dataset.id}`, { method: 'PATCH', body: { active } });
      toast(active ? '招生学校账户已启用' : '招生学校账户已停用');
      return renderRoute();
    }
    if (action === 'reset-admission-account-password') {
      if (!window.confirm('确认重置该招生学校账户密码？旧密码和现有登录会话会立即失效。')) return;
      const data = await api(`/api/admin/admission-school-accounts/${target.dataset.id}/reset-password`, { method: 'POST' });
      setModal(`<div class="modal-head"><div><span>ADMISSION ACCOUNT RESET</span><h2>招生学校临时密码已生成</h2><p>请通过线下安全渠道交给招生学校，本页面关闭后不再展示。</p></div><button data-action="close-modal">×</button></div><div class="issued-number"><span>登录账号</span><strong>${h(data.username)}</strong><span>临时密码</span><strong>${h(data.temporaryPassword)}</strong><p>账户已同步启用，旧密码和现有会话均已失效。</p></div><div class="modal-foot"><button class="solid-button" data-action="close-modal">我已保存</button></div>`);
      return;
    }
    if (action === 'add-admission-category') {
      const sources = state.pageData?.sourceSchools || [];
      target.closest('form')?.querySelector('[data-admission-categories]')?.insertAdjacentHTML('beforeend', admissionCategoryEditor(h, sources));
      return;
    }
    if (action === 'remove-admission-category') {
      const list = target.closest('[data-admission-categories]');
      if (list?.children.length <= 1) return toast('至少保留一个招生类别');
      target.closest('.admission-category-editor')?.remove();
      return;
    }
    if (action === 'add-indicator-allocation') {
      const sources = state.pageData?.sourceSchools || [];
      target.closest('.indicator-allocation-editor')?.querySelector('[data-indicator-allocations]')?.insertAdjacentHTML('beforeend', indicatorAllocationEditor(h, sources));
      return;
    }
    if (action === 'remove-indicator-allocation') {
      target.closest('.indicator-allocation-row')?.remove();
      return;
    }
    if (['batch-admit-download', 'admit-info-export', 'center-materials-export'].includes(action)) {
      event.preventDefault();
      const examId = target.closest('.admission-export-panel')?.querySelector('[data-admission-export-exam]')?.value;
      if (!examId) return toast('请选择考试', '没有可导出的考试范围');
      const type = { 'batch-admit-download': 'admit-cards', 'admit-info-export': 'info', 'center-materials-export': 'center-materials' }[action];
      window.location.href = `/api/admin/admission-exports/${type}?examId=${encodeURIComponent(examId)}`;
      return;
    }
    if (action === 'new-notice') { await openNoticeForm(); return; }
    if (action === 'edit-notice') { await openNoticeForm(state.pageData.notices.find(item => item.id === target.dataset.id)); return; }
    if (action === 'new-exam') return openExamForm();
    if (action === 'new-admin') return openAdminForm();
    if (action === 'new-school') return openSchoolForm();
    if (action === 'edit-school') return openSchoolForm(state.pageData.schools.find(item => item.id === target.dataset.id));
    if (action === 'toggle-school') {
      const active = target.dataset.active === 'true';
      await api(`/api/admin/schools/${target.dataset.id}`, { method: 'PATCH', body: { active } });
      toast(active ? '学校已启用' : '学校已停用', active ? '考生公开入口已恢复显示' : '公开入口已隐藏，班级、管理员和历史数据均已保留'); return renderRoute();
    }
    if (action === 'new-school-class') return openSchoolClassForm();
    if (action === 'edit-school-class') return openSchoolClassForm(state.pageData.classes.find(item => item.id === target.dataset.id));
    if (action === 'toggle-school-class') {
      await api(`/api/admin/classes/${target.dataset.id}`, { method: 'PATCH', body: { active: target.dataset.active === 'true' } });
      toast(target.dataset.active === 'true' ? '班级已启用' : '班级已停用', '班级管理员和历史数据仍会保留'); return renderRoute();
    }
    if (action === 'new-class-admin') return openClassAdminForm(null, target.dataset.classId);
    if (action === 'edit-class-admin') {
      const schoolClass = state.pageData.classes.find(item => item.id === target.dataset.classId);
      return openClassAdminForm(schoolClass?.admins.find(item => item.id === target.dataset.id), target.dataset.classId);
    }
    if (action === 'new-center') return openCenterForm();
    if (action === 'edit-center') return openCenterForm(state.pageData.centers.find(item => item.id === target.dataset.id));
    if (action === 'add-center-room') {
      document.querySelector('[data-center-rooms]')?.insertAdjacentHTML('beforeend', centerRoomEditor());
      return;
    }
    if (action === 'remove-center-room') {
      const list = target.closest('[data-center-rooms]');
      if (list.children.length <= 1) return toast('至少保留一个考场', '考点档案必须包含结构化考场');
      target.closest('.center-room-editor').remove(); return;
    }
    if (action === 'open-flow') return openFlowDetail(target.dataset.id);
    if (action === 'add-workflow-step') {
      const form = document.querySelector(`[data-form="workflow-design"][data-type="${target.dataset.type}"]`);
      form?.querySelector('[data-workflow-steps]')?.insertAdjacentHTML('beforeend', workflowStepEditor());
      return;
    }
    if (action === 'remove-workflow-step') {
      const list = target.closest('[data-workflow-steps]');
      if (list.children.length <= 1) return toast('至少保留一步', '审批流程不能为空');
      target.closest('.workflow-step-row').remove(); return;
    }
    if (action === 'add-exam-subject') {
      const form = target.closest('form');
      const date = form?.examStart?.value?.slice(0, 10) || '';
      form?.querySelector('[data-exam-subjects]')?.insertAdjacentHTML('beforeend', examSubjectEditor({ date }));
      refreshExamScoringForm(form);
      return;
    }
    if (action === 'remove-exam-subject') {
      const form = target.closest('form');
      const list = target.closest('[data-exam-subjects]');
      if (list.children.length <= 1) return toast('至少保留一个科目', '考试计划必须包含科目');
      target.closest('.exam-subject-editor').remove();
      refreshExamScoringForm(form);
      return;
    }
    if (action === 'edit-exam') return openExamForm(state.pageData.exams.find(exam => exam.id === target.dataset.id));
    if (action === 'review-candidate') return openCandidateReview(target.dataset.id);
    if (action === 'reset-candidate-password') return openCandidatePasswordReset(target.dataset.id);
    if (action === 'candidate-archive') {
      const scope = document.querySelector('[data-archive-scope]')?.value || '';
      const separator = scope.indexOf(':');
      if (separator < 1) return toast('请选择归档范围', '可以选择一个班级或整个年级');
      const archived = target.dataset.archived === 'true';
      const data = await api('/api/admin/candidate-accounts/archive', { method: 'POST', body: { scopeType: scope.slice(0, separator), scopeValue: scope.slice(separator + 1), archived } });
      toast(archived ? '账户已归档' : '账户已恢复', `${data.scopeLabel} · ${data.count} 个账户状态已更新`); return renderRoute();
    }
    if (action === 'review-registration') return openRegistrationReview(target.dataset.id);
    if (action === 'bulk-registration-review') {
      const ids = [...document.querySelectorAll('#registrationTable [data-registration-select]:checked')].map(input => input.value);
      if (!ids.length) return toast('请先选择报名记录', '仅待审核记录可批量处理');
      const status = target.dataset.status;
      const promptText = status === 'rejected' ? '请填写批量退回原因（必填）' : '填写批量审核意见（可留空）';
      const reviewNote = window.prompt(promptText, '');
      if (reviewNote === null) return;
      if (status === 'rejected' && !reviewNote.trim()) return toast('请填写退回原因', '考生需要根据原因修改报名');
      let completed = 0;
      const failures = [];
      for (const id of ids) {
        try {
          await api(`/api/admin/registrations/${id}`, { method: 'PATCH', body: { status, reviewNote } });
          completed += 1;
        } catch (error) {
          failures.push(error.message);
        }
      }
      toast(status === 'rejected' ? `已退回 ${completed} 条报名` : `已处理 ${completed} 条报名`, failures.length ? `${failures.length} 条未完成，请刷新后重试` : '报名状态与流程轨迹已同步更新');
      return renderRoute();
    }
    if (action === 'bulk-candidate-review') {
      const ids = [...document.querySelectorAll('#candidateTable [data-candidate-select]:checked')].map(input => input.value);
      if (!ids.length) return toast('请先选择考生', '仅当前步骤可由你处理的资料可以勾选');
      const status = target.dataset.status;
      const reviewNote = window.prompt(status === 'rejected' ? '请填写批量退回原因（必填）' : '填写批量审核意见（可留空）', '');
      if (reviewNote === null) return;
      if (status === 'rejected' && !reviewNote.trim()) return toast('请填写退回原因', '考生需要根据原因补充或修改资料');
      let completed = 0;
      const failures = [];
      for (const id of ids) {
        try {
          await api(`/api/admin/candidates/${id}`, { method: 'PATCH', body: { status, reviewNote } });
          completed += 1;
        } catch (error) { failures.push(error.message); }
      }
      toast(status === 'rejected' ? `已退回 ${completed} 名考生资料` : `已处理 ${completed} 名考生资料`, failures.length ? `${failures.length} 名未完成，请检查其流程责任人` : '资料状态与流程轨迹已同步更新');
      return renderRoute();
    }
    if (action === 'confirm-payment') {
      if (!window.confirm(`确认已收到 ${target.dataset.name} 的“${target.dataset.exam}”考试费用吗？`)) return;
      await api(`/api/admin/payments/${target.dataset.id}`, { method: 'PATCH' });
      toast('缴费已确认', '考生端状态、确认人和确认时间已同步记录');
      return renderRoute();
    }
    if (action === 'update-payment') {
      const paid = target.dataset.status === 'paid';
      const message = paid
        ? `确认将 ${target.dataset.name} 的“${target.dataset.exam}”标记为已缴费吗？`
        : `确认撤销 ${target.dataset.name} 的“${target.dataset.exam}”缴费记录，并改为待缴费吗？`;
      if (!window.confirm(message)) return;
      await api(`/api/admin/payments/${target.dataset.id}`, { method: 'PATCH', body: { status: target.dataset.status } });
      toast(paid ? '已标记为已缴费' : '已改为待缴费', paid ? '确认人和确认时间已同步记录' : '原确认人和确认时间已清除');
      return renderRoute();
    }
    if (action === 'bulk-payment-update') {
      const selected = [...document.querySelectorAll('#paymentTable [data-payment-select]:checked')];
      if (!selected.length) return toast('请先选择缴费记录');
      const status = target.dataset.status;
      const changed = selected.filter(input => input.dataset.status !== status);
      const skipped = selected.length - changed.length;
      if (!changed.length) return toast('所选记录无需更改', status === 'paid' ? '所选考生都已缴费' : '所选考生都处于待缴费状态');
      if (!window.confirm(`确认将 ${changed.length} 条缴费记录批量改为“${status === 'paid' ? '已缴费' : '待缴费'}”吗？`)) return;
      let completed = 0;
      const failures = [];
      for (const input of changed) {
        try {
          await api(`/api/admin/payments/${input.value}`, { method: 'PATCH', body: { status } });
          completed += 1;
        } catch (error) { failures.push(error.message); }
      }
      toast(`已更新 ${completed} 条缴费记录`, [skipped ? `${skipped} 条状态相同已跳过` : '', failures.length ? `${failures.length} 条未完成` : ''].filter(Boolean).join('；'));
      return renderRoute();
    }
    if (action === 'excel-download') {
      const query = new URLSearchParams();
      if (target.dataset.template === '1') query.set('template', '1');
      if (target.dataset.batchId) query.set('batchId', target.dataset.batchId);
      if (target.dataset.examId) query.set('examId', target.dataset.examId);
      else if (target.dataset.resource === 'results' && state.resultExamFilter) query.set('examId', state.resultExamFilter);
      window.location.href = `/api/admin/excel/${target.dataset.resource}?${query}`;
      return;
    }
    if (action === 'excel-import') {
      document.querySelector(`[data-excel-file="${target.dataset.resource}"]`)?.click();
      return;
    }
    if (action === 'result-exam-filter') {
      state.resultExamFilter = target.dataset.id;
      state.resultSubjectFilter = '';
      ['resultEntryTable', 'featureScoreTable', 'resultTable'].forEach(key => {
        if (state.tablePages[key]) state.tablePages[key].page = 1;
      });
      return renderRoute();
    }
    if (action === 'table-page') {
      const key = target.dataset.tableKey;
      const current = state.tablePages[key] || { page: 1, pageSize: 50 };
      state.tablePages[key] = { ...current, page: Math.max(1, Number(target.dataset.page) || 1) };
      return renderRoute();
    }
    if (action === 'cancel-result-import') {
      state.resultImportPreview = null;
      return renderRoute();
    }
    if (action === 'refresh-results-cache') {
      const result = await api('/api/admin/results/cache/refresh', { method: 'POST' });
      toast(result.refreshed ? '成绩缓存已刷新' : '成绩缓存未刷新', result.message);
      return renderRoute();
    }
    if (action === 'generate-admit') {
      const registration = state.pageData.registrations.find(item => item.id === target.dataset.id);
      if (registration.admitCard) return openAdmitPreview(registration);
      return toast('尚未编排', '请使用页面上方的整场编排控制台');
    }
    if (action === 'preview-arrangement') {
      const form = target.closest('form');
      const body = formObject(form);
      const output = form.parentElement.querySelector('[data-arrangement-preview]');
      target.disabled = true;
      target.textContent = '正在预检…';
      try {
        const data = await api(`/api/admin/exams/${body.examId}/admission-arrangement/preview`, { method: 'POST', body });
        const summary = data.summary;
        output.innerHTML = `<div class="arrangement-preview ${data.warnings.length ? 'warning' : 'ok'}"><strong>${data.warnings.length ? '预检完成，有提示' : '预检通过，可以生成'}</strong><span>${summary.candidateCount} 人 · ${summary.centerCount} 个考点 · ${summary.subjectAssignmentCount} 个科次座位 · ${summary.subjectCombinationCount} 种科目组合</span>${data.warnings.length ? `<ul>${data.warnings.map(item => `<li>${h(item)}</li>`).join('')}</ul>` : '<small>多科目同考点、容量、科目时间和号码唯一性检查均已通过。</small>'}</div>`;
      } finally {
        target.disabled = false;
        target.textContent = '仅预检，不写入';
      }
      return;
    }
    if (action === 'toggle-exam') {
      await api(`/api/admin/exams/${target.dataset.id}`, { method: 'PATCH', body: { status: target.dataset.status } });
      toast(target.dataset.status === 'published' ? '考试已发布' : '考试已撤回', '公开页面状态已同步'); return refreshPublic().then(renderRoute);
    }
    if (action === 'archive-exam') {
      const confirmed = window.confirm(`确认归档“${target.dataset.name}”吗？\n\n归档不可撤销；本场成绩、Excel 导入和复议改分将永久锁定。`);
      if (!confirmed) return;
      const result = await api(`/api/admin/exams/${target.dataset.id}/archive`, { method: 'POST' });
      state.resultImportPreview = null;
      toast('考试已归档', result.message || '全部成绩已永久锁定');
      return refreshPublic().then(renderRoute);
    }
    if (action === 'toggle-notice') {
      await api(`/api/admin/notices/${target.dataset.id}`, { method: 'PATCH', body: { status: target.dataset.status } });
      toast(target.dataset.status === 'published' ? '通知已发布' : '通知已隐藏', '公开展示状态已更新'); return refreshPublic().then(renderRoute);
    }
    if (action === 'toggle-publication') {
      const visible = target.dataset.visible === 'true';
      await api(`/api/admin/publications/${target.dataset.sourceType}/${target.dataset.id}`, { method: 'PATCH', body: { visible } });
      toast(visible ? '系统公示已显示' : '系统公示已隐藏', '只调整公开目录展示，不修改公示内容'); return refreshPublic().then(renderRoute);
    }
    if (action === 'status-filter') {
      setTableControl(state, target.dataset.target, { status: target.dataset.status });
      return renderRoute();
    }
  } catch (error) { toast('操作未完成', error.message); }
});

document.addEventListener('input', event => {
  if (event.target.matches('[data-action="table-search"]')) {
    setTableControl(state, event.target.dataset.target, { query: event.target.value });
    applyTableFilters(event.target.dataset.target);
    clearTimeout(tableSearchTimer);
    tableSearchTimer = setTimeout(() => renderRoute(), 260);
  }
  if (event.target.closest('[data-notice-template]')) {
    const studio = event.target.closest('[data-notice-template]');
    const preview = studio.querySelector('.notice-template-preview');
    if (event.target.name === 'primaryColor') preview.style.setProperty('--template-primary', event.target.value);
    if (event.target.name === 'accentColor') preview.style.setProperty('--template-accent', event.target.value);
    const output = studio.querySelector(`[data-template-preview="${event.target.name}"]`);
    if (output) output.textContent = event.target.value.replaceAll('{{考生姓名}}','张同学').replaceAll('{{考试名称}}','示例考试').replaceAll('{{录取学校}}', state.pageData?.school?.name || '本校').replaceAll('{{录取类别}}','普通生');
  }
  if (event.target.matches('[data-result-score]')) updateResultScoreInput(event.target);
  if (event.target.matches('[data-feature-score]')) updateFeatureScoreInput(event.target);
  if (event.target.matches('.subject-options input')) {
    const form = event.target.closest('form');
    const checked = [...form.querySelectorAll('.subject-options input:checked')];
    form.querySelector('[data-subject-count]').textContent = checked.length;
    const exam = state.pageData.exams.find(item => item.id === form.examId.value);
    const fee = checked.reduce((sum, input) => sum + Number(exam.subjects.find(subject => subject.id === input.value)?.fee || 0), 0);
    const fullScore = checked.reduce((sum, input) => sum + Number(exam.subjects.find(subject => subject.id === input.value)?.fullScore || 0), 0);
    form.querySelector('[data-subject-fee]').textContent = `满分 ${fullScore} · ${money(fee)}`;
  }
  if (event.target.closest('[data-exam-subjects]') || event.target.matches('[name="passValue"]')) refreshExamScoringForm(event.target.closest('form'));
});

document.addEventListener('change', event => {
  if (event.target.matches('[data-action="table-page-size"]')) {
    const key = event.target.dataset.tableKey;
    state.tablePages[key] = { page: 1, pageSize: Number(event.target.value) || 50 };
    renderRoute();
    return;
  }
  if (event.target.matches('[data-placement-select]')) updatePlacementSelection();
  if (event.target.matches('[data-placement-select-all]')) {
    const table = document.getElementById(event.target.dataset.target);
    table?.querySelectorAll('[data-placement-select]:not(:disabled)').forEach(input => {
      if (!input.closest('tr').hidden) input.checked = event.target.checked;
    });
    updatePlacementSelection();
  }
  if (event.target.matches('[data-reporting-select]')) {
    const toolbar = event.target.closest('form')?.querySelector('[data-reporting-bulk]');
    const table = event.target.closest('table');
    const selected = table ? table.querySelectorAll('[data-reporting-select]:checked').length : 0;
    const total = table ? table.querySelectorAll('[data-reporting-select]').length : 0;
    const count = toolbar?.querySelector('[data-reporting-selected-count]');
    const selectAll = toolbar?.querySelector('[data-reporting-select-all]');
    if (count) count.textContent = `已选 ${selected} 人`;
    if (selectAll) { selectAll.checked = total > 0 && selected === total; selectAll.indeterminate = selected > 0 && selected < total; }
  }
  if (event.target.matches('[data-reporting-select-all]')) {
    const table = document.getElementById(event.target.dataset.tableId);
    table?.querySelectorAll('[data-reporting-select]').forEach(input => { input.checked = event.target.checked; });
    const count = event.target.closest('[data-reporting-bulk]')?.querySelector('[data-reporting-selected-count]');
    if (count) count.textContent = `已选 ${event.target.checked ? table?.querySelectorAll('[data-reporting-select]').length || 0 : 0} 人`;
  }
  if (event.target.matches('.reporting-decision-options input[type="radio"]')) {
    event.target.closest('fieldset').querySelectorAll('label').forEach(label => label.classList.toggle('selected', label.contains(event.target)));
  }
  if (event.target.matches('[data-action="result-bulk-exam"]')) {
    state.resultExamFilter = event.target.value;
    state.resultSubjectFilter = '';
    ['resultEntryTable', 'featureScoreTable', 'resultTable'].forEach(key => {
      if (state.tablePages[key]) state.tablePages[key].page = 1;
    });
    renderRoute();
  }
  if (event.target.matches('[data-action="result-bulk-subject"]')) {
    state.resultSubjectFilter = event.target.value;
    if (state.tablePages.resultEntryTable) state.tablePages.resultEntryTable.page = 1;
    renderRoute();
  }
  if (event.target.matches('[data-qualification-select]')) updateQualificationSelection(event.target.closest('.qualification-ledger'));
  if (event.target.matches('[data-table-filter]')) {
    setTableControl(state, event.target.dataset.target, { filters: { [event.target.dataset.tableFilter]: event.target.value } });
    renderRoute();
  }
  if (event.target.matches('[data-registration-select]')) updateRegistrationSelection();
  if (event.target.matches('[data-candidate-select]')) updateCandidateSelection();
  if (event.target.matches('[data-payment-select]')) updatePaymentSelection();
  if (event.target.matches('[data-registration-select-all]')) {
    const table = document.getElementById(event.target.dataset.target);
    table?.querySelectorAll('[data-registration-select]:not(:disabled)').forEach(input => {
      if (!input.closest('tr').hidden) input.checked = event.target.checked;
    });
    updateRegistrationSelection();
  }
  if (event.target.matches('[data-candidate-select-all]')) {
    const table = document.getElementById(event.target.dataset.target);
    table?.querySelectorAll('[data-candidate-select]:not(:disabled)').forEach(input => {
      if (!input.closest('tr').hidden) input.checked = event.target.checked;
    });
    updateCandidateSelection();
  }
  if (event.target.matches('[data-payment-select-all]')) {
    const table = document.getElementById(event.target.dataset.target);
    table?.querySelectorAll('[data-payment-select]:not(:disabled)').forEach(input => {
      if (!input.closest('tr').hidden) input.checked = event.target.checked;
    });
    updatePaymentSelection();
  }
  if (event.target.matches('[data-excel-file]')) {
    const input = event.target;
    const file = input.files?.[0];
    if (!file) return;
    const resource = input.dataset.excelFile;
    input.value = '';
    (async () => {
      try {
        toast(resource === 'results' ? '正在生成导入预览' : '正在导入 Excel', resource === 'results' ? `${file.name} · 当前不会写入数据库` : `${file.name} · 逐行校验并写入，错误会标出具体行`);
        const result = await api(`/api/admin/excel/${resource}`, {
          method: 'POST', headers: { 'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }, body: await file.arrayBuffer()
        });
        if (resource === 'results') {
          state.resultImportPreview = { ...result, fileName: file.name };
          toast(result.summary.invalid ? '预览完成，发现错误' : '预览校验通过', `${result.summary.valid}/${result.summary.total} 行可提交，数据库尚未修改`);
          renderRoute();
        } else if (resource === 'account_quotas') {
          document.querySelectorAll('[data-class-id]').forEach(field => { field.value = '0'; });
          result.quotas.forEach(item => { const field = document.querySelector(`[data-class-id="${item.classId}"]`); if (field) field.value = item.count; });
          toast('班级配额已填入', `已读取 ${result.quotas.length} 个班级，请核对后提交审批`);
        } else {
          toast('Excel 导入完成', `已处理 ${result.count} 条数据`); renderRoute();
        }
      } catch (error) { toast('Excel 导入失败', error.message); }
    })();
    return;
  }
  if (event.target.matches('[data-action="school-select"]')) {
    const form = event.target.closest('form');
    const classSelect = form?.querySelector('select[name="classId"]');
    if (classSelect) {
      const classes = state.pageData?.classes || state.publicData.classes || [];
      classSelect.innerHTML = `<option value="">请选择班级</option>${classes.filter(item => item.schoolId === event.target.value).map(item => `<option value="${h(item.id)}">${h(item.name)}</option>`).join('')}`;
    }
  }
  if (event.target.matches('[data-action="specialty-category"]')) {
    const typeSelect = event.target.closest('.admission-category-editor')?.querySelector('[data-specialty-type]') || event.target.closest('form')?.querySelector('[data-specialty-type]');
    const category = specialtyCatalog.find(item => item.code === event.target.value);
    if (typeSelect) {
      typeSelect.disabled = !category;
      typeSelect.innerHTML = `<option value="">${category ? '请选择小类' : '请先选择大类'}</option>${(category?.types || []).map(item => `<option value="${h(item.code)}">${h(item.name)}</option>`).join('')}`;
    }
  }
  if (event.target.matches('[data-action="plan-category-kind"]')) {
    const editor = event.target.closest('.admission-category-editor');
    const specialtyFields = editor?.querySelector('[data-plan-specialty]');
    const enabled = event.target.value === 'specialty';
    specialtyFields?.classList.toggle('hidden', !enabled);
    specialtyFields?.querySelectorAll('select').forEach(select => { select.disabled = !enabled || (select.hasAttribute('data-specialty-type') && !specialtyFields.querySelector('[name="categorySpecialtyCategory"]')?.value); });
  }
  if (event.target.matches('[name="categoryName"]')) {
    const title = event.target.closest('.admission-category-editor')?.querySelector('header strong');
    if (title) title.textContent = event.target.value.trim() || '新类别';
  }
  if (event.target.matches('[data-action="preference-school"]')) {
    const row = event.target.closest('.preference-choice-row');
    const categorySelect = row?.querySelector('[name="choiceCategory"]');
    const admission = state.pageData?.admissions?.find(item => item.examId === event.target.dataset.examId);
    const plan = admission?.plans?.find(item => item.schoolId === event.target.value);
    const preferenceType = row?.dataset.preferenceType || 'general';
    if (categorySelect) {
      categorySelect.disabled = !plan;
      categorySelect.innerHTML = `<option value="">${plan ? '请选择招生类别' : '请先按代码选择学校'}</option>${(plan?.categories || []).filter(item => item.preferenceTypes?.includes(preferenceType) && Number(preferenceType === 'indicator' ? item.indicatorRemaining : item.generalRemaining) > 0).map(item => `<option value="${h(item.code)}">${h(item.name)} · 对应余 ${h(preferenceType === 'indicator' ? item.indicatorRemaining : item.generalRemaining)}</option>`).join('')}`;
    }
  }
  if (event.target.matches('[data-region-level]')) updateRegionSelects(event.target);
  if (event.target.matches('[data-action="admin-level"]')) {
    const form = event.target.closest('form');
    form?.querySelector('[data-admin-school]')?.classList.toggle('hidden', event.target.value === 'super');
    form?.querySelector('[data-admin-class]')?.classList.toggle('hidden', event.target.value !== 'class');
  }
  if (event.target.matches('[data-action="result-exam"]')) {
    const form = event.target.closest('form');
    const exam = state.pageData?.exams?.find(item => item.id === event.target.value);
    const subjectSelect = form?.querySelector('[name="subjectId"]');
    const candidateSelect = form?.querySelector('[name="registrationId"]');
    if (subjectSelect) subjectSelect.innerHTML = `<option value="">请选择科目</option>${(exam?.subjects || []).map(subject => `<option value="${h(subject.id)}" data-full-score="${h(subject.fullScore)}" data-pass-score="${h(subject.passScore ?? '')}" data-pass-text="${h(subject.passRule === 'none' ? '不设单科线' : subject.passRule === 'rank_percent' ? `本科排名前 ${subject.passValue}%` : `固定 ${subject.passScore} 分`)}">${h(subject.name)} · 满分 ${h(subject.fullScore)}</option>`).join('')}`;
    if (candidateSelect) { candidateSelect.innerHTML = '<option value="">请先选择科目</option>'; candidateSelect.disabled = true; }
    subjectSelect?.dispatchEvent(new Event('change', { bubbles: true }));
  }
  if (event.target.matches('[data-action="result-subject"]')) {
    const form = event.target.closest('form');
    const option = event.target.selectedOptions[0];
    const examId = form?.querySelector('[name="examId"]')?.value;
    const subjectId = event.target.value;
    const fullScore = Number(option?.dataset.fullScore || 0);
    const passText = option?.dataset.passText || '';
    const scoreInput = form?.querySelector('[name="score"]');
    if (scoreInput) scoreInput.max = fullScore || '';
    const candidateSelect = form?.querySelector('[name="registrationId"]');
    const registrations = (state.pageData?.registrations || []).filter(item => item.examId === examId && item.subjectIds.includes(subjectId));
    if (candidateSelect) {
      candidateSelect.disabled = !subjectId;
      candidateSelect.innerHTML = `<option value="">${subjectId ? '请选择考生' : '请先选择科目'}</option>${registrations.map(item => `<option value="${h(item.id)}">${h(item.candidateName)} · ${h(item.candidateNumber)} · ${h(item.className)}</option>`).join('')}`;
    }
    const label = form?.querySelector('[data-score-label]');
    const hint = form?.querySelector('[data-score-hint]');
    if (label) label.textContent = fullScore ? `成绩（0—${fullScore}）` : '成绩';
    if (hint) hint.textContent = fullScore ? `本科满分 ${fullScore} 分；独立及格规则：${passText}。` : '选择科目后显示其独立及格规则。';
  }
  if (event.target.matches('[data-action="result-candidate"]')) {
    const form = event.target.closest('form');
    const result = state.pageData?.results?.find(item => item.registrationId === event.target.value && item.subjectId === form?.subjectId?.value);
    if (form?.score) form.score.value = result?.score ?? '';
    if (form?.published) form.published.checked = result ? result.published : true;
    const button = form?.querySelector('button[type="submit"]');
    if (button) button.textContent = result ? '更新这条成绩' : '保存成绩';
  }
  if (event.target.matches('[name="subjectPassRule"]')) refreshSubjectPassRuleRow(event.target.closest('.exam-subject-editor'));
  if (event.target.matches('[name="passPolicy"]')) {
    refreshExamScoringForm(event.target.closest('form'));
  }
});

document.addEventListener('submit', async event => {
  const form = event.target.closest('form[data-form]');
  if (!form) return;
  event.preventDefault();
  const submit = event.submitter || form.querySelector('button[type="submit"]');
  const original = submit?.innerHTML;
  if (submit) { submit.disabled = true; submit.textContent = '正在处理…'; }
  try {
    const kind = form.dataset.form;
    if (kind === 'login') {
      const data = await api('/api/auth/login', { method: 'POST', body: formObject(form) });
      if (data.requiresTotp) {
        setModal(`<div class="modal-head"><div><span>SECOND STEP</span><h2>输入动态验证码</h2><p>打开验证器应用，输入当前显示的 6 位验证码；也可以使用一个恢复码。</p></div><button data-action="close-modal">×</button></div><form class="modal-form totp-login-form" data-form="totp-login"><input type="hidden" name="challenge" value="${h(data.challenge)}"><label><span>动态验证码或恢复码</span><input name="code" inputmode="numeric" autocomplete="one-time-code" required autofocus placeholder="000000"></label><div class="modal-foot"><button type="button" class="ghost-button" data-action="close-modal">返回</button><button type="submit" class="solid-button">验证并登录</button></div></form>`);
      } else await finishLogin(data);
    } else if (kind === 'totp-login') {
      const data = await api('/api/auth/login/totp', { method: 'POST', body: formObject(form) });
      await finishLogin(data);
    } else if (kind === 'register') {
      const data = await api('/api/auth/register', { method: 'POST', body: formObject(form) });
      setModal(`<div class="modal-head"><div><span>CANDIDATE NUMBER</span><h2>请保存你的报名号</h2><p>该号码就是长期使用的考生账户。</p></div><button data-action="close-modal">×</button></div><div class="issued-number"><span>固定报名号</span><strong>${h(data.registrationNumber)}</strong><p>以后报名不同考试仍使用这个号码。关闭窗口前请抄写或截图保存。</p></div><div class="modal-foot"><button class="solid-button" data-route="login">前往登录</button></div>`);
    } else if (kind === 'document-verification') {
      const code = form.code.value.trim().toUpperCase();
      if (!code) throw new Error('请输入防伪查询码');
      navigate(`verify/${encodeURIComponent(code)}`);
    } else if (kind === 'candidate-password') {
      const body = formObject(form);
      if (body.newPassword !== body.confirmPassword) throw new Error('两次输入的新密码不一致');
      await api('/api/auth/change-password', { method: 'POST', body });
      await refreshSession(); toast('密码修改成功', '下一步请补全个人信息'); navigate('candidate/onboarding');
    } else if (kind === 'account-password') {
      const body = formObject(form);
      if (body.newPassword !== body.confirmPassword) throw new Error('两次输入的新密码不一致');
      await api('/api/auth/change-password', { method: 'POST', body });
      form.reset(); toast('密码修改成功', '下次登录请使用新密码');
    } else if (kind === 'totp-setup') {
      const data = await api('/api/auth/totp/setup', { method: 'POST', body: formObject(form) });
      setModal(`<div class="modal-head"><div><span>AUTHENTICATOR SETUP</span><h2>扫描二维码</h2><p>在验证器应用中添加账户，然后输入应用显示的 6 位验证码完成绑定。</p></div><button data-action="close-modal">×</button></div><div class="totp-setup-grid"><div class="totp-qr"><img src="${h(data.qrCode)}" alt="TOTP 绑定二维码" width="240" height="240"></div><div class="totp-manual"><span>无法扫码？手动输入密钥</span><code data-totp-secret>${h(data.secret.match(/.{1,4}/g)?.join(' ') || data.secret)}</code><button type="button" class="text-button" data-action="copy-totp-secret">复制密钥</button><small>类型：基于时间 · 6 位 · 每 30 秒更新</small></div></div><form class="modal-form totp-confirm-form" data-form="totp-enable"><label><span>验证器中的 6 位验证码</span><input name="code" inputmode="numeric" autocomplete="one-time-code" pattern="[0-9]{6}" maxlength="6" required placeholder="000000"></label><div class="modal-foot"><button type="button" class="ghost-button" data-action="close-modal">取消</button><button type="submit" class="solid-button">验证并启用</button></div></form>`);
    } else if (kind === 'totp-enable') {
      const data = await api('/api/auth/totp/enable', { method: 'POST', body: formObject(form) });
      state.user = data.user;
      showRecoveryCodes(data.recoveryCodes);
    } else if (kind === 'totp-recovery-codes') {
      const data = await api('/api/auth/totp/recovery-codes', { method: 'POST', body: formObject(form) });
      showRecoveryCodes(data.recoveryCodes);
    } else if (kind === 'totp-disable') {
      await api('/api/auth/totp/disable', { method: 'POST', body: formObject(form) });
      await refreshSession();
      toast('二次验证已关闭', '账户现在仅使用密码登录');
      renderRoute();
    } else if (kind === 'candidate-password-reset') {
      const body = formObject(form);
      const data = await api(`/api/admin/candidates/${body.id}/reset-password`, { method: 'POST' });
      setModal(`<div class="modal-head"><div><span>PASSWORD RESET</span><h2>临时密码已生成</h2><p>请通过线下安全渠道交给考生，仅此处展示一次。</p></div><button data-action="close-modal">×</button></div><div class="issued-number"><span>报名号</span><strong>${h(data.candidateNumber)}</strong><span>临时密码</span><strong>${h(data.temporaryPassword)}</strong><p>原密码和现有登录会话均已失效，考生下次登录必须修改此密码。</p></div><div class="modal-foot"><button class="solid-button" data-action="close-modal">我已保存</button></div>`);
    } else if (kind === 'candidate-profile') {
      const data = await api('/api/candidate/profile', { method: 'PUT', body: formObject(form) });
      state.profile = data.profile; await refreshSession(); toast('资料已提交', '管理员审核后会更新状态'); navigate('candidate/dashboard');
    } else if (kind === 'volunteer-preference') {
      const choices = [...form.querySelectorAll('.preference-choice-row')].map(row => ({ schoolId: row.querySelector('[name="choiceSchool"]').value, categoryCode: row.querySelector('[name="choiceCategory"]').value, preferenceType: row.dataset.preferenceType || 'general' })).filter(item => item.schoolId && item.categoryCode);
      const data = await api(`/api/candidate/admissions/${form.examId.value}/preferences`, { method: 'PUT', body: { choices } });
      toast(data.locked ? '志愿已保存并锁定' : '志愿已保存', data.locked ? '已达到本轮提交次数上限' : `还可提交 ${data.remainingSubmissions} 次`); renderRoute();
    } else if (kind === 'exam-registration') {
      const body = { examId: form.examId.value, subjectIds: [...form.querySelectorAll('input[name="subjectIds"]:checked')].map(input => input.value) };
      if (!body.subjectIds.length) throw new Error('请至少选择一个报考科目');
      await api('/api/candidate/registrations', { method: 'POST', body });
      toast('报名已提交', `已选择 ${body.subjectIds.length} 个科目`); renderRoute();
    } else if (kind === 'score-appeal') {
      const body = formObject(form);
      await api(`/api/candidate/results/${body.resultId}/appeals`, { method: 'POST', body: { reason: body.reason } });
      toast('成绩复议已提交', '系统已按班级、学校和考试中心流程自动分配'); renderRoute();
    } else if (kind === 'candidate-review') {
      const body = formObject(form);
      await api(`/api/admin/candidates/${body.id}`, { method: 'PATCH', body });
      closeModal(); toast(body.status === 'approved' ? '资料审核通过' : '资料已退回', '考生端状态已同步'); renderRoute();
    } else if (kind === 'registration-review') {
      const body = formObject(form);
      await api(`/api/admin/registrations/${body.id}`, { method: 'PATCH', body });
      closeModal(); toast(body.status === 'approved' ? '报名审核通过' : '报名已退回', '报名状态已更新'); renderRoute();
    } else if (kind === 'admin-form') {
      const body = formObject(form);
      await api('/api/admin/admins', { method: 'POST', body });
      closeModal(); toast('管理员已创建', '权限范围已按层级绑定'); renderRoute();
    } else if (kind === 'admission-setting') {
      const body = formObject(form); body.enabled = form.enabled.checked; body.autoPublish = form.autoPublish.checked; body.maxChoices = Number(body.maxChoices || 5); body.maxSubmissions = Number(body.maxSubmissions || 3);
      await api(`/api/admin/admissions/${body.examId}/setting`, { method: 'PUT', body });
      toast('志愿设置已保存', '考生端阶段与进度已同步'); renderRoute();
    } else if (kind === 'admission-account') {
      await api('/api/admin/admission-school-accounts', { method: 'POST', body: formObject(form) });
      form.reset(); toast('招生学校账号已创建'); renderRoute();
    } else if (kind === 'admission-notice-template') {
      await api('/api/admission/notice-template', { method: 'PUT', body: formObject(form) });
      toast('录取通知书模板已保存', '正式录取考生将使用该模板生成 PDF'); renderRoute();
    } else if (kind === 'admission-reporting-draft') {
      const rows = [...form.querySelectorAll('[data-reporting-status]')].map(select => ({
        placementId: select.dataset.placementId,
        status: select.value,
        note: form.querySelector(`[data-reporting-note][data-placement-id="${CSS.escape(select.dataset.placementId)}"]`)?.value || ''
      }));
      if (!rows.length) return toast('当前页没有可暂存记录');
      await api('/api/admission/reporting/draft', { method: 'PUT', body: { examId: form.dataset.examId, rows } });
      toast('当前页已暂存', `${rows.length} 名考生的状态已保存，尚未正式提交`); renderRoute();
    } else if (kind === 'admission-reporting-scan-preview') {
      const body = formObject(form);
      await previewReportingScan(body.code, body.examId);
    } else if (kind === 'admission-reporting-scan-confirm') {
      const body = formObject(form);
      const result = await api('/api/admission/reporting/scan', { method: 'POST', body });
      closeModal();
      const label = body.status === 'reported' ? '已报到' : body.status === 'not_reported' ? '未报到' : '待确认';
      toast('扫码结果已暂存', `${result.row.name} · ${label}，尚未正式提交`); renderRoute();
    } else if (kind === 'admission-reporting-decision') {
      const body = formObject(form);
      body.supplement = body.supplement === 'true';
      await api('/api/admission/reporting/decision', { method: 'POST', body });
      toast('学校决定已提交', '等待超级管理员审批；审批后报到统计将自动公开'); renderRoute();
    } else if (kind === 'admission-plan' || kind === 'school-admission-plan') {
      const body = formObject(form);
      body.categories = [...form.querySelectorAll('.admission-category-editor')].map((editor, index) => {
        const specialty = editor.querySelector('[name="categoryKind"]').value === 'specialty';
        const indicatorAllocations = [...editor.querySelectorAll('.indicator-allocation-row')].map(row => ({ sourceSchoolId: row.querySelector('[name="indicatorSchool"]').value, quota: Number(row.querySelector('[name="indicatorQuota"]').value || 0) })).filter(item => item.sourceSchoolId && item.quota > 0);
        return { code: `category_${index + 1}`, name: editor.querySelector('[name="categoryName"]').value.trim(), quota: Number(editor.querySelector('[name="categoryQuota"]').value || 0), isSpecialty: specialty, specialtyCategory: specialty ? editor.querySelector('[name="categorySpecialtyCategory"]').value : '', specialtyType: specialty ? editor.querySelector('[name="categorySpecialtyType"]').value : '', indicatorAllocations };
      }).filter(item => item.name && item.quota > 0);
      if (!body.categories.length) throw new Error('请至少添加一个有效招生类别');
      if (body.categories.some(item => item.isSpecialty && (!item.specialtyCategory || !item.specialtyType))) throw new Error('特长生类别必须同时选择对应的大类和小类');
      await api(kind === 'admission-plan' ? '/api/admin/admission-plans' : '/api/admission/plans', { method: 'POST', body });
      toast(kind === 'admission-plan' ? '招生计划已代上传并通过' : '招生计划已提交审核'); renderRoute();
    } else if (kind === 'placement-review') {
      const body = formObject(form);
      await api(`/api/admission/placements/${body.id}`, { method: 'PATCH', body });
      toast(body.decision === 'accept' ? '已接收投档考生' : '退档申请已提交超级管理员'); renderRoute();
    } else if (kind === 'school-form') {
      const body = formObject(form); body.active = form.active.checked; body.isSourceSchool = form.isSourceSchool.checked; body.isAdmissionSchool = form.isAdmissionSchool.checked;
      await api(body.id ? `/api/admin/schools/${body.id}` : '/api/admin/schools', { method: body.id ? 'PATCH' : 'POST', body });
      closeModal(); await refreshPublic(); toast(body.id ? '学校档案已更新' : '学校已创建', `${body.name} · ${body.code.toUpperCase()}`); renderRoute();
    } else if (kind === 'school-class') {
      const body = formObject(form); body.active = form.active.checked;
      await api(body.id ? `/api/admin/classes/${body.id}` : '/api/admin/classes', { method: body.id ? 'PATCH' : 'POST', body });
      closeModal(); toast(body.id ? '班级已更新' : '班级已创建', `${body.grade} · ${body.name}`); renderRoute();
    } else if (kind === 'class-admin') {
      const body = formObject(form); body.active = form.active.checked;
      if (body.id) await api(`/api/admin/admins/${body.id}`, { method: 'PATCH', body });
      else await api('/api/admin/admins', { method: 'POST', body: { ...body, adminLevel: 'class', schoolId: state.user.schoolId } });
      closeModal(); toast(body.id ? '班级管理员已更新' : '班级管理员已创建', '权限范围已绑定到指定班级'); renderRoute();
    } else if (kind === 'candidate-account-batch') {
      const quotas = [...form.querySelectorAll('[data-class-id]')].map(input => ({ classId: input.dataset.classId, count: Number(input.value || 0) })).filter(item => item.count > 0);
      const total = quotas.reduce((sum, item) => sum + item.count, 0);
      if (!total) throw new Error('请至少为一个班级填写申领数量');
      await api('/api/admin/candidate-account-batches', { method: 'POST', body: { quotas } });
      toast('批量申领已提交', `${total} 个账户将在最终批准后统一生成`); renderRoute();
    } else if (kind === 'self-registration-setting') {
      const enabled = form.enabled.value === 'true';
      await api('/api/admin/settings/self-registration', { method: 'PUT', body: { enabled } });
      await refreshPublic(); toast(enabled ? '自主注册已开放' : '自主注册已关闭', enabled ? '公开入口现在可以申请报名号' : '仅保留学校下发账户流程'); renderRoute();
    } else if (kind === 'center-form') {
      const body = formObject(form);
      const editing = Boolean(body.id);
      body.rooms = [...form.querySelectorAll('.center-room-editor')].map(row => ({
        id: row.querySelector('[name="roomId"]').value || null,
        code: row.querySelector('[name="roomCode"]').value,
        name: row.querySelector('[name="roomName"]').value,
        building: row.querySelector('[name="roomBuilding"]').value,
        floor: row.querySelector('[name="roomFloor"]').value,
        capacity: Number(row.querySelector('[name="roomCapacity"]').value),
        seatPlan: row.querySelector('[name="roomSeatPlan"]').value,
        roomType: row.querySelector('[name="roomType"]').value,
        status: row.querySelector('[name="roomStatus"]').value,
        notes: row.querySelector('[name="roomNotes"]').value
      }));
      await api(editing ? `/api/admin/centers/${body.id}` : '/api/admin/centers', { method: editing ? 'PATCH' : 'POST', body });
      closeModal(); toast(editing ? '考点变更已提交' : '新考点已提交', '审批通过后才会更新正式档案'); renderRoute();
    } else if (kind === 'number-rule') {
      const raw = formObject(form);
      const types = Object.keys(numberSegmentMeta);
      const segments = types.filter(type => type === 'sequence' || form.querySelector(`[name="include_${type}"]`)?.checked).map(type => ({
        type, position: Number(raw[`position_${type}`] || 99), value: raw[`value_${type}`] || '', width: Number(raw[`width_${type}`] || 0)
      })).sort((a, b) => a.position - b.position);
      await api('/api/admin/number-rules', { method: 'POST', body: { id: raw.id, name: raw.name, separator: raw.separator, segments } });
      toast('报名号规则已启用', '后续创建的考生账户将按此规则生成固定号码'); renderRoute();
    } else if (kind === 'admission-arrangement') {
      const body = formObject(form);
      const data = await api(`/api/admin/exams/${body.examId}/admission-arrangement`, { method: 'POST', body });
      toast('整场编排已生成', `${data.summary.candidateCount} 名考生 · ${data.summary.subjectAssignmentCount} 个科次座位`); renderRoute();
    } else if (kind === 'workflow-design') {
      const names = [...form.querySelectorAll('[name="stepName"]')];
      const levels = [...form.querySelectorAll('[name="stepLevel"]')];
      const steps = names.map((input, index) => ({ name: input.value, adminLevel: levels[index].value }));
      await api(`/api/admin/workflows/${form.dataset.type}`, { method: 'PUT', body: { name: form.name.value, steps } });
      toast('审批流程已保存', `${steps.length} 个步骤已启用`); renderRoute();
    } else if (kind === 'flow-process') {
      const body = formObject(form);
      const path = body.businessType === 'profile_change'
        ? `/api/admin/candidates/${body.businessId}`
        : body.businessType === 'registration_review'
          ? `/api/admin/registrations/${body.businessId}`
          : body.businessType === 'center_change'
            ? `/api/admin/center-change-requests/${body.businessId}`
            : body.businessType === 'candidate_account_batch'
              ? `/api/admin/candidate-account-batches/${body.businessId}`
              : `/api/admin/score-appeals/${body.businessId}`;
      await api(path, { method: 'PATCH', body: { status: body.status, reviewNote: body.reviewNote, ...(body.reviewedScore == null || body.reviewedScore === '' ? {} : { reviewedScore: Number(body.reviewedScore) }) } });
      closeModal(); toast(body.status === 'approved' ? '流程已处理' : '流程已退回', '操作已写入流程轨迹'); renderRoute();
    } else if (kind === 'flow-transfer') {
      const body = formObject(form);
      await api(`/api/admin/workflow-instances/${body.id}/transfer`, { method: 'PATCH', body });
      closeModal(); toast('流程已转交', '新责任人已收到待办'); renderRoute();
    } else if (kind === 'flow-supervise') {
      const body = formObject(form);
      await api(`/api/admin/workflow-instances/${body.id}/supervise`, { method: 'PATCH', body });
      closeModal(); toast('流程已监督调整', '节点与责任人已更新并记录'); renderRoute();
    } else if (kind === 'notice-form') {
      const body = formObject(form);
      if (noticeEditor) body.content = noticeEditor.getData();
      body.pinned = form.pinned.checked;
      const endpoint = body.id ? `/api/admin/notices/${body.id}` : '/api/admin/notices';
      await api(endpoint, { method: body.id ? 'PATCH' : 'POST', body });
      closeModal(); await refreshPublic(); toast(body.status === 'published' ? '通知已发布' : '草稿已保存', body.id ? '草稿内容已更新' : '公开首页状态已同步'); renderRoute();
    } else if (kind === 'exam-form') {
      const body = formObject(form);
      body.subjects = [...form.querySelectorAll('.exam-subject-editor')].map(row => ({
        name: row.querySelector('[name="subjectName"]').value.trim(),
        fullScore: Number(row.querySelector('[name="subjectFullScore"]').value),
        passRule: row.querySelector('[name="subjectPassRule"]').value,
        passValue: Number(row.querySelector('[name="subjectPassValue"]').value || 0),
        date: row.querySelector('[name="subjectDate"]').value,
        start: row.querySelector('[name="subjectStart"]').value,
        end: row.querySelector('[name="subjectEnd"]').value,
        fee: Number(row.querySelector('[name="subjectFee"]').value || 0)
      }));
      body.passValue = ['subject_scores', 'none'].includes(body.passPolicy) ? 0 : Number(body.passValue);
      ['registrationStart','registrationEnd','examStart','examEnd','admitDownloadStart','admitDownloadEnd'].forEach(field => body[field] = new Date(body[field]).toISOString());
      const editing = Boolean(body.id);
      await api(editing ? `/api/admin/exams/${body.id}` : '/api/admin/exams', { method: editing ? 'PATCH' : 'POST', body });
      closeModal(); await refreshPublic(); toast(editing ? '考试草稿已更新' : '考试计划已创建', `${body.subjects.length} 个科目已配置`); renderRoute();
    } else if (kind === 'result-entry') {
      const body = formObject(form); body.published = form.published.checked;
      await api('/api/admin/results', { method: 'POST', body });
      toast(body.published ? '成绩已发布' : '成绩已保存', '考生端可见状态已更新'); renderRoute();
    } else if (kind === 'result-bulk-entry') {
      const mode = event.submitter?.dataset.resultMode || 'draft';
      const inputs = [...form.querySelectorAll('[data-result-score]')];
      const invalid = inputs.find(input => input.value.trim() !== '' && (!Number.isFinite(Number(input.value)) || Number(input.value) < 0 || Number(input.value) > Number(input.max) || !Number.isInteger(Number(input.value) * 2)));
      if (invalid) { invalid.focus(); throw new Error(`成绩须在 0—${invalid.max} 之间，且只能填写整数或 .5`); }
      const targets = mode === 'publish'
        ? inputs.filter(input => input.value.trim() !== '')
        : inputs.filter(input => input.dataset.dirty === 'true' && input.value.trim() !== '');
      if (mode === 'publish') {
        const missing = inputs.filter(input => input.value.trim() === '');
        if (missing.length) { missing[0].focus(); throw new Error(`当前页还有 ${missing.length} 名考生未录入成绩`); }
        if (!window.confirm(`确认发布当前页 ${targets.length} 名考生的成绩吗？发布后考生可立即查询。`)) return;
      }
      if (!targets.length) throw new Error(mode === 'publish' ? '当前科目没有可发布的成绩' : '请先修改至少一条成绩');
      const body = {
        examId: form.examId.value,
        subjectId: form.subjectId.value,
        published: mode === 'publish',
        rows: targets.map(input => ({ registrationId: input.dataset.registrationId, score: Number(input.value) }))
      };
      const result = await api('/api/admin/results/bulk', { method: 'POST', body });
      toast(mode === 'publish' ? '当前页成绩已发布' : '成绩已暂存', `${result.count} 条成绩已在同一事务中保存并重算本场排名`);
      renderRoute();
    } else if (kind === 'feature-score-entry') {
      const body = formObject(form); body.featureScore = Number(body.featureScore || 0);
      await api(`/api/admin/registrations/${body.registrationId}/feature-score`, { method: 'PATCH', body });
      toast('特征分已登记', '该分数独立于考试科目，默认值为 0'); renderRoute();
    } else if (kind === 'feature-score-bulk') {
      const inputs = [...form.querySelectorAll('[data-feature-score]')];
      const invalid = inputs.find(input => input.value.trim() === '' || !Number.isFinite(Number(input.value)) || Number(input.value) < 0 || Number(input.value) > 1000);
      if (invalid) { invalid.focus(); throw new Error('特征分必须在 0—1000 之间；未参加时填写 0'); }
      const targets = inputs.filter(input => input.dataset.dirty === 'true');
      if (!targets.length) throw new Error('请先修改至少一名考生的特征分');
      const result = await api('/api/admin/feature-scores/bulk', { method: 'POST', body: { examId: form.examId.value, rows: targets.map(input => ({ registrationId: input.dataset.registrationId, featureScore: Number(input.value) })) } });
      toast('特征分已保存', `${result.count} 名考生已更新；其余考生继续保持 0 分`);
      renderRoute();
    } else if (kind === 'result-import-commit') {
      const rows = state.resultImportPreview?.rows || [];
      if (!rows.length) throw new Error('没有可提交的成绩预览');
      const result = await api('/api/admin/results/import', { method: 'POST', body: { rows } });
      state.resultImportPreview = null;
      toast('批量成绩已写入', `${result.count} 条成绩已在同一事务中提交`); renderRoute();
    }
  } catch (error) { toast('操作未完成', error.message); }
  finally { if (submit && submit.isConnected) { submit.disabled = false; submit.innerHTML = original; } }
});

function openAdminForm() {
  const { schools = [], classes = [] } = state.pageData;
  setModal(`<div class="modal-head"><div><span>ADMIN SCOPE</span><h2>添加分级管理员</h2><p>同一级可以创建多个账号；校级和班级管理员必须绑定数据范围。</p></div><button data-action="close-modal">×</button></div><form class="modal-form" data-form="admin-form"><div class="field-row"><label><span>姓名 *</span><input name="displayName" required></label><label><span>管理员层级 *</span><select name="adminLevel" data-action="admin-level"><option value="school">校级管理员</option><option value="class">班级管理员</option><option value="super">超级管理员</option></select></label></div><div class="field-row"><label><span>登录账号 *</span><input name="username" required></label><label><span>初始密码 *</span><input name="password" type="password" minlength="8" required></label></div><label data-admin-school><span>绑定学校</span><select name="schoolId" data-action="school-select"><option value="">请选择学校</option>${schools.map(item => `<option value="${h(item.id)}">${h(item.name)}</option>`).join('')}</select></label><label class="hidden" data-admin-class><span>绑定班级</span><select name="classId"><option value="">请先选择学校</option>${classes.map(item => `<option value="${h(item.id)}">${h(item.name)}</option>`).join('')}</select></label><div class="modal-foot"><button type="button" class="ghost-button" data-action="close-modal">取消</button><button type="submit" class="solid-button">创建管理员</button></div></form>`);
}

function openSchoolForm(school = null) {
  setModal(`<div class="modal-head"><div><span>SCHOOL DIRECTORY</span><h2>${school ? '编辑学校档案' : '创建学校'}</h2><p>学校代码统一标识学校；同一学校可以同时承担生源校和招生校职责。</p></div><button data-action="close-modal">×</button></div><form class="modal-form" data-form="school-form">${school ? `<input type="hidden" name="id" value="${h(school.id)}">` : ''}<div class="field-row"><label><span>学校名称 *</span><input name="name" required maxlength="100" value="${h(school?.name || '')}" placeholder="例如 海州市第四中学"></label><label><span>学校代码 *</span><input name="code" required maxlength="40" pattern="[A-Za-z0-9_-]+" value="${h(school?.code || '')}" placeholder="例如 HZ04"></label></div><label><span>学校地址</span><input name="address" maxlength="200" value="${h(school?.address || '')}" placeholder="省、市、区县及详细地址"></label><section class="school-role-selector"><strong>学校类型（至少选择一项）</strong><div><label><input type="checkbox" name="isSourceSchool" ${school?.isSourceSchool === false ? '' : 'checked'}><span><b>生源校</b><small>可建立班级、管理考生，并接收指标名额</small></span></label><label><input type="checkbox" name="isAdmissionSchool" ${school?.isAdmissionSchool === false ? '' : 'checked'}><span><b>招生校</b><small>可配置招生账号、上传计划并审核投档</small></span></label></div></section><label class="agreement"><input type="checkbox" name="active" ${school?.active === false ? '' : 'checked'}><span>启用该学校；公开报名入口只显示已启用的生源校</span></label><div class="modal-foot"><button type="button" class="ghost-button" data-action="close-modal">取消</button><button type="submit" class="solid-button">${school ? '保存学校' : '创建学校'}</button></div></form>`);
}

function openSchoolClassForm(schoolClass = null) {
  setModal(`<div class="modal-head"><div><span>SCHOOL CLASS</span><h2>${schoolClass ? '编辑本校班级' : '新增本校班级'}</h2><p>班级是考生资料、报名和成绩权限的最小范围。</p></div><button data-action="close-modal">×</button></div><form class="modal-form" data-form="school-class">${schoolClass ? `<input type="hidden" name="id" value="${h(schoolClass.id)}">` : ''}<div class="field-row"><label><span>年级 *</span><input name="grade" required value="${h(schoolClass?.grade || '')}" placeholder="例如 高三"></label><label><span>班级名称 *</span><input name="name" required value="${h(schoolClass?.name || '')}" placeholder="例如 高三（3）班"></label></div><label class="agreement"><input type="checkbox" name="active" ${schoolClass?.active === false ? '' : 'checked'}><span>启用该班级，可继续分配考生和班级管理员</span></label><div class="modal-foot"><button type="button" class="ghost-button" data-action="close-modal">取消</button><button type="submit" class="solid-button">${schoolClass ? '保存班级' : '创建班级'}</button></div></form>`);
}

function openClassAdminForm(admin = null, classId = '') {
  const classes = state.pageData.classes || [];
  setModal(`<div class="modal-head"><div><span>CLASS SCOPE</span><h2>${admin ? '维护班级管理员' : '添加班级管理员'}</h2><p>该账号可查看并审批所绑定班级的考生、报名与成绩复议流程。</p></div><button data-action="close-modal">×</button></div><form class="modal-form" data-form="class-admin">${admin ? `<input type="hidden" name="id" value="${h(admin.id)}">` : ''}<div class="field-row"><label><span>管理员姓名 *</span><input name="displayName" required value="${h(admin?.displayName || '')}"></label><label><span>绑定班级 *</span><select name="classId" required>${classes.map(item => `<option value="${h(item.id)}" ${item.id === (admin?.classId || classId) ? 'selected' : ''}>${h(item.grade)} · ${h(item.name)}</option>`).join('')}</select></label></div>${admin ? `<label><span>登录账号</span><input value="${h(admin.username)}" disabled></label>` : `<label><span>登录账号 *</span><input name="username" required placeholder="建议使用学校代码与班级缩写"></label>`}<label><span>${admin ? '重置密码（留空则不修改）' : '初始密码 *'}</span><input name="password" type="password" ${admin ? '' : 'required minlength="8"'} placeholder="至少 8 位"></label><label class="agreement"><input type="checkbox" name="active" ${admin?.active === false ? '' : 'checked'}><span>启用该管理员账号</span></label><div class="modal-foot"><button type="button" class="ghost-button" data-action="close-modal">取消</button><button type="submit" class="solid-button">${admin ? '保存管理员' : '创建管理员'}</button></div></form>`);
}

function centerRoomEditor(room = {}) {
  return `<section class="center-room-editor"><input type="hidden" name="roomId" value="${h(room.id || '')}"><header><span>结构化考场</span><button type="button" data-action="remove-center-room">移除</button></header><div class="room-editor-grid"><label><span>考场代码 *</span><input name="roomCode" required value="${h(room.code || '')}" placeholder="如 001"></label><label><span>考场名称 *</span><input name="roomName" required value="${h(room.name || '')}" placeholder="如 第 001 考场"></label><label><span>楼栋 *</span><input name="roomBuilding" required value="${h(room.building || '')}" placeholder="如 教学楼 A"></label><label><span>楼层</span><input name="roomFloor" value="${h(room.floor || '')}" placeholder="如 2 层"></label><label><span>容量 *</span><input name="roomCapacity" type="number" min="1" required value="${h(room.capacity || 30)}"></label><label class="room-notes"><span>座位编排说明</span><input name="roomSeatPlan" value="${h(room.seatPlan || '')}" placeholder="如：按现场桌贴从前至后编排"></label><label><span>考场类型</span><select name="roomType"><option value="standard" ${room.roomType === 'standard' ? 'selected' : ''}>标准考场</option><option value="computer" ${room.roomType === 'computer' ? 'selected' : ''}>机考考场</option><option value="accessible" ${room.roomType === 'accessible' ? 'selected' : ''}>无障碍考场</option><option value="spare" ${room.roomType === 'spare' ? 'selected' : ''}>备用考场</option></select></label><label><span>状态</span><select name="roomStatus"><option value="active" ${room.status !== 'inactive' ? 'selected' : ''}>启用</option><option value="inactive" ${room.status === 'inactive' ? 'selected' : ''}>停用</option></select></label><label class="room-notes"><span>考场备注</span><input name="roomNotes" value="${h(room.notes || '')}" placeholder="设备、无障碍设施或备用安排"></label></div></section>`;
}

function openCenterForm(center = null) {
  const schools = state.pageData.schools || [];
  const rooms = center?.rooms?.length ? center.rooms : [{}];
  setModal(`<div class="modal-head"><div><span>CONTROLLED DOSSIER</span><h2>${center ? '提交考点档案变更' : '提交新考点档案'}</h2><p>表单将进入“考点考场变更审批”，通过前不会改动正式数据。</p></div><button data-action="close-modal">×</button></div><form class="modal-form center-dossier-form" data-form="center-form">${center ? `<input type="hidden" name="id" value="${h(center.id)}">` : ''}<section class="center-form-section"><h3>考点基本档案</h3>${state.user.adminLevel === 'super' ? `<label><span>所属学校 *</span><select name="schoolId" required>${schools.map(item => `<option value="${h(item.id)}" ${center?.schoolId === item.id ? 'selected' : ''}>${h(item.name)}</option>`).join('')}</select></label>` : ''}<div class="field-row"><label><span>考点代码 *</span><input name="code" required value="${h(center?.code || '')}" placeholder="如 HZ01-C01"></label><label><span>考点名称 *</span><input name="name" required value="${h(center?.name || '')}"></label></div><label><span>详细地址 *</span><input name="address" required value="${h(center?.address || '')}"></label><div class="field-row"><label><span>考点负责人</span><input name="managerName" value="${h(center?.managerName || '')}"></label><label><span>负责人手机</span><input name="managerPhone" value="${h(center?.managerPhone || '')}"></label></div><div class="field-row"><label><span>值班电话</span><input name="contact" value="${h(center?.contact || '')}"></label><label><span>应急电话</span><input name="emergencyPhone" value="${h(center?.emergencyPhone || '')}"></label></div><div class="field-row"><label><span>开放时间</span><input name="gateOpenTime" type="time" value="${h(center?.gateOpenTime || '')}"></label><label><span>档案状态</span><select name="status"><option value="active" ${center?.status !== 'inactive' ? 'selected' : ''}>启用</option><option value="inactive" ${center?.status === 'inactive' ? 'selected' : ''}>停用</option></select></label></div><label><span>交通与入场提示</span><textarea name="transport" rows="3">${h(center?.transport || '')}</textarea></label><label><span>考务备注</span><textarea name="notes" rows="2">${h(center?.notes || '')}</textarea></label></section><section class="center-form-section rooms-section"><header><div><h3>考场明细</h3><p>每个考场单独维护位置、容量、座位编排说明与类型。</p></div><button type="button" class="row-action" data-action="add-center-room">添加考场</button></header><div data-center-rooms>${rooms.map(room => centerRoomEditor(room)).join('')}</div></section><div class="approval-callout"><strong>提交即进入审批</strong><span>审批通过后，正式考点档案和全部考场明细会作为一个版本整体生效。</span></div><div class="modal-foot"><button type="button" class="ghost-button" data-action="close-modal">取消</button><button type="submit" class="solid-button">提交审批</button></div></form>`);
  mountRegionSelects(modalRoot, center);
}

function openFlowDetail(id) {
  const instance = state.pageData.instances.find(item => item.id === id);
  const currentLevel = instance.currentStepDetail?.adminLevel;
  const available = state.pageData.availableAdmins.filter(item => {
    if (item.adminLevel !== currentLevel) return false;
    if (currentLevel === 'super') return true;
    if (currentLevel === 'school') return item.schoolId === instance.assignee?.schoolId;
    return item.schoolId === instance.assignee?.schoolId && item.classId === instance.assignee?.classId;
  });
  const canProcess = instance.status === 'pending' && instance.assignee?.id === state.user.id;
  const history = instance.actions.map(action => `<div><i></i><span><strong>${h(action.actorName)} · ${h({submit:'提交',approve:'通过',reject:'退回',transfer:'转交',return:'退回节点',supervise:'监督调整'}[action.action] || action.action)}</strong><small>${h(action.note || '')}${action.toAssigneeName ? ` → ${h(action.toAssigneeName)}` : ''}</small></span><time>${formatDate(action.createdAt, true)}</time></div>`).join('');
  const isCenter = instance.businessType === 'center_change';
  const isBatch = instance.businessType === 'candidate_account_batch';
  const isAppeal = instance.businessType === 'score_appeal';
  const finalBatchStep = isBatch && instance.currentStep >= instance.steps.length;
  const finalAppealStep = isAppeal && instance.currentStep >= instance.steps.length;
  const subject = isCenter ? instance.centerName : isBatch ? `${instance.schoolName} · ${instance.batchTotalCount} 个报名号` : isAppeal ? `${instance.candidateName} · ${instance.appealResult?.subjectName || '成绩复议'}` : instance.candidateName;
  const subjectDetail = isCenter ? `${instance.requestType === 'create' ? '新增考点' : '修改档案'} · ${instance.schoolName}` : isBatch ? '按班级批量申领 · 批准后生成账号' : isAppeal ? `${instance.appealResult?.examName || ''} · 原成绩 ${instance.appealResult?.score ?? '—'}` : `${instance.examName ? `${instance.examName} · ` : ''}${instance.schoolName}`;
  const change = instance.centerChange ? { ...instance.centerChange, address: formatRegionAddress(instance.centerChange) } : null;
  const changeSnapshot = change ? `<section class="flow-center-snapshot"><header><div><span>申请快照</span><h3>${h(change.name)} · ${h(change.code)}</h3></div><b>${change.rooms.length} 个考场</b></header><dl><div><dt>地址</dt><dd>${h(change.address)}</dd></div><div><dt>负责人</dt><dd>${h(change.managerName || '未填写')} · ${h(change.managerPhone || '未填写')}</dd></div><div><dt>开放时间</dt><dd>${h(change.gateOpenTime || '未填写')}</dd></div><div><dt>档案状态</dt><dd>${change.centerStatus === 'active' ? '启用' : '停用'}</dd></div></dl><div>${change.rooms.map(room => `<span><strong>${h(room.name)}</strong><small>${h(room.building)} · ${h(room.capacity)} 席 · ${h(room.seatPlan || '按现场座次表编排')}</small></span>`).join('')}</div></section>` : '';
  const batch = instance.accountBatch;
  const batchSnapshot = batch ? `<section class="flow-batch-snapshot"><header><div><span>班级配额</span><h3>${batch.totalCount} 个待建账户</h3></div><b>${batch.quotas.length} 个班级</b></header><div>${batch.quotas.map(item => `<span><strong>${h(item.className)}</strong><small>${item.count} 人</small></span>`).join('')}</div><p>最终批准时才生成固定报名号和随机初始密码。</p></section>` : '';
  const appeal = instance.appealResult;
  const appealSnapshot = appeal ? `<section class="flow-appeal-snapshot"><header><div><span>复议成绩快照</span><h3>${h(appeal.examName)} · ${h(appeal.subjectName)}</h3></div><b>${h(appeal.score)} / ${h(appeal.fullScore)}</b></header><dl><div><dt>当前等级</dt><dd>${h(appeal.grade)}</dd></div><div><dt>本科排名</dt><dd>第 ${h(appeal.rank)} / ${h(appeal.cohortSize)} 名（前 ${h(appeal.rankPercent)}%）</dd></div><div><dt>单科规则</dt><dd>${h(appeal.passText)}</dd></div><div><dt>当前结论</dt><dd>${appeal.qualified == null ? '不判定' : appeal.qualified ? '达线' : '未达线'}</dd></div></dl></section>` : '';
  const reviewedScoreField = finalAppealStep ? `<label><span>复核后分数（批准时必填）</span><input name="reviewedScore" type="number" min="0" max="${h(appeal?.fullScore || 0)}" step="0.5" value="${h(appeal?.score ?? '')}"></label>` : '';
  setModal(`<div class="modal-head"><div><span>WORKFLOW TRACE</span><h2>${h(instance.workflowName)}</h2><p>${h(subject)} · ${h(subjectDetail)}</p></div><button data-action="close-modal">×</button></div>${changeSnapshot}${batchSnapshot}<div class="flow-detail-track">${instance.steps.map(step => `<div class="${step.position < instance.currentStep || instance.status === 'approved' ? 'done' : step.position === instance.currentStep && instance.status === 'pending' ? 'current' : ''}"><i>${step.position}</i><span><strong>${h(step.name)}</strong><small>${h(statusLabels[step.adminLevel])}</small></span></div>`).join('')}</div><section class="flow-history"><h3>流程轨迹</h3>${history || '<p>暂无操作</p>'}</section>${canProcess ? `<form class="modal-form compact-flow-form" data-form="flow-process"><input type="hidden" name="businessType" value="${h(instance.businessType)}"><input type="hidden" name="businessId" value="${h(instance.businessId)}"><div class="field-row"><label><span>处理结论</span><select name="status"><option value="approved">通过当前步骤</option><option value="rejected">退回申请</option></select></label><label><span>处理意见</span><input name="reviewNote" placeholder="填写核验说明"></label></div><button class="solid-button" type="submit">${finalBatchStep ? '最终批准并生成账号' : '确认处理'}</button></form><form class="modal-form compact-flow-form" data-form="flow-transfer"><input type="hidden" name="id" value="${h(instance.id)}"><div class="field-row"><label><span>转交给同级管理员</span><select name="assigneeId" required>${available.filter(item => item.id !== instance.assignee?.id).map(item => `<option value="${h(item.id)}">${h(item.displayName)}</option>`).join('')}</select></label><label><span>转交说明</span><input name="note"></label></div><button class="ghost-button" type="submit" ${available.filter(item => item.id !== instance.assignee?.id).length ? '' : 'disabled'}>转交流程</button></form>` : '<div class="read-only-callout">当前流程未分配给你，只能查看轨迹。</div>'}${state.pageData.canSupervise ? `<form class="modal-form supervisor-form" data-form="flow-supervise"><input type="hidden" name="id" value="${h(instance.id)}"><h3>超级管理员监督调整</h3><div class="field-row"><label><span>调整到步骤</span><select name="currentStep">${instance.steps.map(step => `<option value="${step.position}" ${step.position === instance.currentStep ? 'selected' : ''}>${step.position}. ${h(step.name)}</option>`).join('')}</select></label><label><span>指定责任人（可留空自动选择）</span><select name="assigneeId"><option value="">自动选择</option>${state.pageData.availableAdmins.map(item => `<option value="${h(item.id)}">${h(item.displayName)} · ${h(statusLabels[item.adminLevel])}</option>`).join('')}</select></label></div><label><span>监督说明</span><input name="note" placeholder="说明修改或退回原因"></label><button class="ghost-button" type="submit">监督调整</button></form>` : ''}`);
  if (appealSnapshot) modalRoot.querySelector('.modal-head')?.insertAdjacentHTML('afterend', appealSnapshot);
  if (finalAppealStep) {
    const processForm = modalRoot.querySelector('[data-form="flow-process"]');
    const fieldRow = processForm?.querySelector('.field-row');
    if (fieldRow) fieldRow.insertAdjacentHTML('beforeend', reviewedScoreField);
    const processButton = processForm?.querySelector('button[type="submit"]');
    if (processButton) processButton.textContent = '批准并更新成绩';
  }
}

function openCandidateReview(id) {
  const source = state.pageData.candidates.find(candidate => candidate.id === id);
  const item = { ...source, address: formatRegionAddress(source) };
  const canReview = item.status === 'pending' && (item.workflow?.assignee?.id === state.user.id || state.user.adminLevel === 'super');
  const registrations = item.registrations || [];
  const examCards = registrations.map(registration => `<article class="review-exam-card"><header><div><span class="exam-code">${h(registration.exam?.code || '')}</span><h3>${h(registration.exam?.name || '考试信息异常')}</h3></div>${badge(registration.exam?.archivedAt ? 'archived' : registration.status)}</header><dl><div><dt>考试时间</dt><dd>${dateRange(registration.exam?.examStart, registration.exam?.examEnd)}</dd></div><div><dt>考试地点</dt><dd>${h(registration.exam?.location || '待公布')}</dd></div><div><dt>报名科目</dt><dd><div class="review-subject-list">${registration.subjects?.map(subject => `<span><strong>${h(subject.name)}</strong><small>${h(subject.date || '')} ${h(subject.start || '')} · 满分 ${h(subject.fullScore)} · ${money(subject.fee || 0)}</small></span>`).join('') || '<em>未选择科目</em>'}</div></dd></div><div><dt>报名 / 缴费</dt><dd>${badge(registration.status)} ${badge(registration.paymentStatus)} · 应缴 ${money(registration.amountDue || 0)}</dd></div></dl></article>`).join('');
  const decision = canReview ? `<form class="review-decision-form" data-form="candidate-review"><input type="hidden" name="id" value="${h(item.id)}"><div><span>当前审核步骤</span><strong>${h(item.workflow?.currentStepDetail?.name || '流程已结束')}</strong><small>责任人：${h(item.workflow?.assignee?.displayName || '—')}</small></div><label><span>审核结论</span><select name="status"><option value="approved">通过当前步骤</option><option value="rejected">退回考生修改</option></select></label><label><span>审核意见</span><textarea name="reviewNote" rows="5" placeholder="填写核验说明；退回时请明确指出需要补充或修改的资料">${h(item.reviewNote || '')}</textarea></label><button type="submit" class="solid-button">确认处理</button></form>` : `<aside class="review-readonly"><strong>当前为只读查看</strong><p>${item.status === 'pending' ? `本步骤由 ${h(item.workflow?.assignee?.displayName || '其他管理员')} 处理。` : `资料状态：${h(statusLabels[item.status] || item.status)}`}</p><button class="ghost-button" data-action="close-modal">返回列表</button></aside>`;
  setReviewSubpage(`<header class="review-subpage-header"><button class="review-back" data-action="close-modal">← 返回考生资料审核</button><div><span>CANDIDATE DOSSIER</span><h1>${h(item.name)} · 资料审核</h1><p><b class="mono">${h(item.candidateNumber)}</b><i></i>${h(item.school || '学校未填写')} · ${h(item.grade || '班级未填写')}<i></i>更新于 ${formatDate(item.updatedAt, true)}</p></div>${badge(item.status)}</header><div class="review-subpage-layout"><div class="review-subpage-main"><section class="review-section"><header><span>01</span><div><h2>身份与学籍信息</h2><p>核对实名、学籍范围以及联系方式。</p></div></header><dl class="review-detail-grid"><div><dt>证件号码</dt><dd class="mono">${h(item.idNumberMasked)}</dd></div><div><dt>性别 / 出生日期</dt><dd>${h(item.gender || '未填写')} · ${h(item.birthDate || '未填写')}</dd></div><div><dt>籍贯 / 民族</dt><dd>${h(item.nativePlace || '未填写')} · ${h(item.ethnicity || '未填写')}</dd></div><div><dt>就读学校 / 班级</dt><dd>${h(item.school || '未填写')} · ${h(item.grade || '未填写')}</dd></div><div><dt>联系电话</dt><dd>${h(item.phone || '未填写')}</dd></div><div><dt>电子邮箱</dt><dd>${h(item.email || '未填写')}</dd></div><div class="wide"><dt>家庭住址</dt><dd>${h(item.address || '未填写')}</dd></div><div><dt>监护人</dt><dd>${h(item.guardianName || '未填写')} · ${h(item.guardianPhone || '电话未填写')}</dd></div><div><dt>紧急联系人</dt><dd>${h(item.emergencyContact || '未填写')} · ${h(item.emergencyPhone || '电话未填写')}</dd></div></dl></section><section class="review-section"><header><span>02</span><div><h2>关联考试与报名科目</h2><p>审核资料时同时查看该考生历次报名上下文。</p></div><strong>${registrations.length} 场</strong></header><div class="review-exam-list">${examCards || '<div class="review-empty-context"><strong>暂无考试报名</strong><p>该考生当前尚未提交考试报名；资料审核通过后才能选择考试与科目。</p></div>'}</div></section></div>${decision}</div>`);
}

function openCandidatePasswordReset(id) {
  const item = state.pageData?.candidates?.find(candidate => candidate.id === id);
  if (!item) return toast('考生不存在', '请刷新页面后重试');
  setModal(`<div class="modal-head"><div><span>RESET PASSWORD</span><h2>重置 ${h(item.name)} 的密码</h2><p class="mono">报名号 ${h(item.candidateNumber)}</p></div><button data-action="close-modal">×</button></div><form class="modal-form" data-form="candidate-password-reset"><input type="hidden" name="id" value="${h(item.id)}"><div class="reset-warning"><strong>重置后立即生效</strong><p>系统将生成临时密码，使原密码及该考生所有现有登录会话失效，并要求下次登录先改密。</p></div><div class="modal-foot"><button type="button" class="ghost-button" data-action="close-modal">取消</button><button type="submit" class="solid-button">确认重置</button></div></form>`);
}

function openRegistrationReview(id) {
  const reg = state.pageData.registrations.find(item => item.id === id);
  const canReview = reg.status === 'pending' && (reg.workflow?.assignee?.id === state.user.id || state.user.adminLevel === 'super');
  const subjects = reg.subjects || [];
  const subjectCards = subjects.map((subject, index) => `<article><i>${index + 1}</i><div><strong>${h(subject.name)}</strong><span>${h(subject.date || '日期待定')} ${h(subject.start || '')}${subject.end ? `—${h(subject.end)}` : ''}</span></div><dl><div><dt>满分</dt><dd>${h(subject.fullScore)} 分</dd></div><div><dt>报名费</dt><dd>${money(subject.fee || 0)}</dd></div></dl></article>`).join('');
  const decision = canReview ? `<form class="review-decision-form" data-form="registration-review"><input type="hidden" name="id" value="${h(reg.id)}"><div><span>当前审核步骤</span><strong>${h(reg.workflow?.currentStepDetail?.name || '流程已结束')}</strong><small>责任人：${h(reg.workflow?.assignee?.displayName || '—')}</small></div><label><span>审核结论</span><select name="status"><option value="approved">通过当前步骤</option><option value="rejected">退回本次报名</option></select></label><label><span>审核意见</span><textarea name="reviewNote" rows="5" placeholder="填写审核依据；退回时请说明考试或科目问题">${h(reg.reviewNote || '')}</textarea></label><button type="submit" class="solid-button">确认处理</button></form>` : `<aside class="review-readonly"><strong>当前为只读查看</strong><p>${reg.status === 'pending' ? `本步骤由 ${h(reg.workflow?.assignee?.displayName || '其他管理员')} 处理。` : `报名状态：${h(statusLabels[reg.status] || reg.status)}`}</p><button class="ghost-button" data-action="close-modal">返回列表</button></aside>`;
  setReviewSubpage(`<header class="review-subpage-header"><button class="review-back" data-action="close-modal">← 返回报名审核</button><div><span>REGISTRATION DOSSIER</span><h1>${h(reg.candidate?.name)} · 报名审核</h1><p><b class="mono">${h(reg.registrationNumber || reg.candidate?.candidateNumber || '号码待同步')}</b><i></i>${h(reg.schoolName || reg.candidate?.school || '')} · ${h([reg.gradeName, reg.className].filter(Boolean).join(' · '))}</p></div>${badge(reg.status)}</header><div class="review-subpage-layout"><div class="review-subpage-main"><section class="review-section exam-context-hero"><header><span>考试</span><div><p class="exam-code">${h(reg.exam.code)}</p><h2>${h(reg.exam.name)}</h2></div></header><dl class="review-detail-grid"><div><dt>报名时间</dt><dd>${dateRange(reg.exam.registrationStart, reg.exam.registrationEnd)}</dd></div><div><dt>考试时间</dt><dd>${dateRange(reg.exam.examStart, reg.exam.examEnd)}</dd></div><div><dt>考试地点</dt><dd>${h(reg.exam.location || '待公布')}</dd></div><div><dt>本次报名</dt><dd><strong>${subjects.length} 个科目 · 应缴 ${money(reg.amountDue || 0)}</strong></dd></div><div><dt>缴费状态</dt><dd>${badge(reg.paymentStatus)}</dd></div><div><dt>账户报名号</dt><dd class="mono">${h(reg.registrationNumber || reg.candidate?.candidateNumber || '待同步')}</dd></div></dl></section><section class="review-section subject-review-section"><header><span>科目</span><div><h2>本次所报科目</h2><p>逐科核对日期、时间、满分和报名费用。</p></div><strong>${subjects.length} 科</strong></header><div class="review-subject-cards">${subjectCards || '<div class="review-empty-context"><strong>未选择任何科目</strong><p>该报名记录数据异常，不应通过审核。</p></div>'}</div><footer><span>费用合计</span><strong>${money(reg.amountDue || 0)}</strong></footer></section><section class="review-section"><header><span>考生</span><div><h2>考生资料摘要</h2><p>报名审核同时核验身份与学校范围。</p></div></header><dl class="review-detail-grid"><div><dt>姓名 / 性别</dt><dd>${h(reg.candidate?.name || '未填写')} · ${h(reg.candidate?.gender || '未填写')}</dd></div><div><dt>证件号码</dt><dd class="mono">${h(reg.candidate?.idNumber || '未填写')}</dd></div><div><dt>学校 / 班级</dt><dd>${h(reg.schoolName || '')} · ${h([reg.gradeName, reg.className].filter(Boolean).join(' · '))}</dd></div><div><dt>联系电话</dt><dd>${h(reg.candidate?.phone || '未填写')}</dd></div></dl></section></div>${decision}</div>`);
}

async function openNoticeForm(notice = null) {
  if (notice === undefined) return toast('草稿不存在', '请刷新页面后重试');
  const editing = Boolean(notice);
  const categories = ['报名通知', '考试须知', '考点公告', '成绩通知', '系统公告'];
  if (notice?.category && !categories.includes(notice.category)) categories.push(notice.category);
  setModal(`<div class="modal-head"><div><span>${editing ? 'EDIT DRAFT' : 'NEW NOTICE'}</span><h2>${editing ? '编辑通知草稿' : '发布通知公告'}</h2><p>${editing ? '修改后可继续保存草稿，也可直接发布。' : '发布后将立即显示在公开首页和考生中心。'}</p></div><button data-action="close-modal">×</button></div><form class="modal-form notice-editor-form" data-form="notice-form">${editing ? `<input type="hidden" name="id" value="${h(notice.id)}">` : ''}<div class="field-row"><label><span>通知分类</span><select name="category">${categories.map(category => `<option ${category === notice?.category ? 'selected' : ''}>${h(category)}</option>`).join('')}</select></label><label><span>发布方式</span><select name="status"><option value="published" ${notice?.status === 'published' ? 'selected' : ''}>立即发布</option><option value="draft" ${!notice || notice.status === 'draft' ? 'selected' : ''}>保存草稿</option></select></label></div><label><span>通知标题 *</span><input name="title" required placeholder="清楚说明通知事项" value="${h(notice?.title || '')}"></label><label><span>首页摘要</span><input name="summary" placeholder="用于首页列表展示；留空将从正文自动提取" value="${h(notice?.summary || '')}"></label><div class="notice-editor-field"><span>通知正文 *</span><textarea name="content" rows="10" data-notice-editor placeholder="请输入完整通知内容">${h(notice?.content || '')}</textarea><small>图片请点工具栏“通过 URL 插入图片”并填写专用服务器地址；普通文件仍使用超链接。</small></div><label class="agreement"><input type="checkbox" name="pinned" ${notice?.pinned ? 'checked' : ''}><span>在公开首页置顶展示</span></label><div class="modal-foot"><button type="button" class="ghost-button" data-action="close-modal">取消</button><button type="submit" class="solid-button">${editing ? '保存修改' : '保存通知'}</button></div></form>`);
  const source = modalRoot.querySelector('[data-notice-editor]');
  try {
    const [ckeditor, translation] = await loadCKEditor();
    const {
      ClassicEditor, AutoImage, AutoLink, BlockQuote, Bold, Essentials, Heading,
      Image, ImageCaption, ImageInsertViaUrl, ImageStyle, ImageTextAlternative, ImageToolbar,
      Italic, Link, List, Paragraph, Table, TableCaption, TableToolbar, Underline
    } = ckeditor;
    if (!source?.isConnected) return;
    noticeEditor = await ClassicEditor.create(source, {
      licenseKey: 'GPL',
      language: 'zh-cn',
      translations: [translation.default],
      plugins: [
        Essentials, Paragraph, Heading, Bold, Italic, Underline, Link, AutoLink, List, BlockQuote,
        Image, ImageCaption, ImageInsertViaUrl, ImageStyle, ImageTextAlternative, ImageToolbar, AutoImage,
        Table, TableToolbar, TableCaption
      ],
      toolbar: {
        items: ['heading', '|', 'bold', 'italic', 'underline', '|', 'bulletedList', 'numberedList', 'blockQuote', '|', 'link', 'insertImageViaUrl', 'insertTable', '|', 'undo', 'redo'],
        shouldNotGroupWhenFull: true
      },
      heading: {
        options: [
          { model: 'paragraph', title: '正文', class: 'ck-heading_paragraph' },
          { model: 'heading2', view: 'h2', title: '二级标题', class: 'ck-heading_heading2' },
          { model: 'heading3', view: 'h3', title: '三级标题', class: 'ck-heading_heading3' }
        ]
      },
      link: { defaultProtocol: 'https://', addTargetToExternalLinks: true },
      image: {
        toolbar: ['imageStyle:inline', 'imageStyle:block', 'imageStyle:side', '|', 'toggleImageCaption', 'imageTextAlternative']
      },
      table: {
        contentToolbar: ['tableColumn', 'tableRow', 'mergeTableCells', '|', 'toggleTableCaption']
      },
      placeholder: '请输入完整通知内容；图片使用 URL 插入，文件使用超链接'
    });
  } catch (error) {
    console.error('CKEditor failed to load', error);
    toast('富文本编辑器加载失败', '已保留普通文本输入，可检查网络后重试');
  }
}

function dateTimeLocal(value) {
  if (!value) return '';
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) return '';
  const pad = number => String(number).padStart(2, '0');
  return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`;
}

function examSubjectEditor(subject = {}) {
  const passRule = subject.passRule === 'score_ratio' ? 'rank_percent' : (subject.passRule || 'fixed_score');
  const passValue = subject.passValue ?? subject.passScore ?? 60;
  return `<article class="exam-subject-editor"><header><span>科目明细</span><button type="button" data-action="remove-exam-subject">移除</button></header><div class="exam-subject-grid"><label class="subject-name-field"><span>科目名称 *</span><input name="subjectName" required value="${h(subject.name || '')}" placeholder="例如：语文"></label><label><span>满分 *</span><input name="subjectFullScore" type="number" min="0.5" max="1000" step="0.5" required value="${h(subject.fullScore ?? 100)}"></label><label><span>单科及格线方式 *</span><select name="subjectPassRule"><option value="fixed_score" ${passRule === 'fixed_score' ? 'selected' : ''}>固定分</option><option value="rank_percent" ${passRule === 'rank_percent' ? 'selected' : ''}>按排名比例</option><option value="none" ${passRule === 'none' ? 'selected' : ''}>不设单科线</option></select></label><label data-subject-pass-value><span data-subject-pass-label>规则数值 *</span><span class="unit-input"><input name="subjectPassValue" type="number" min="0" max="1000" step="0.1" required value="${h(passValue)}"><em data-subject-pass-unit>分</em></span></label><label><span>考试日期 *</span><input name="subjectDate" type="date" required value="${h(subject.date || '')}"></label><label><span>开始 *</span><input name="subjectStart" type="time" required value="${h(subject.start || '09:00')}"></label><label><span>结束 *</span><input name="subjectEnd" type="time" required value="${h(subject.end || '11:00')}"></label><label><span>费用</span><input name="subjectFee" type="number" min="0" step="0.01" value="${h(subject.fee ?? 0)}"></label></div><p class="subject-pass-preview" data-subject-pass-preview></p></article>`;
}

function refreshSubjectPassRuleRow(row) {
  if (!row) return;
  const ruleSelect = row.querySelector('[name="subjectPassRule"]');
  const legacyOption = ruleSelect?.querySelector('option[value="score_ratio"]');
  if (legacyOption) { legacyOption.value = 'rank_percent'; legacyOption.textContent = '按排名比例'; }
  const rule = ruleSelect?.value || 'fixed_score';
  const fullScore = Number(row.querySelector('[name="subjectFullScore"]')?.value || 0);
  const valueField = row.querySelector('[data-subject-pass-value]');
  const input = row.querySelector('[name="subjectPassValue"]');
  const label = row.querySelector('[data-subject-pass-label]');
  const unit = row.querySelector('[data-subject-pass-unit]');
  const preview = row.querySelector('[data-subject-pass-preview]');
  valueField?.classList.toggle('hidden', rule === 'none');
  if (input) {
    input.disabled = rule === 'none';
    input.max = rule === 'rank_percent' ? '100' : String(fullScore || 1000);
    input.min = rule === 'rank_percent' ? '0.1' : '0';
  }
  if (label) label.textContent = rule === 'rank_percent' ? '排名比例 *' : '固定及格分 *';
  if (unit) unit.textContent = rule === 'rank_percent' ? '%' : '分';
  const value = Number(input?.value || 0);
  if (preview) preview.textContent = rule === 'none' ? '本科只展示成绩，不单独判定达线。' : rule === 'rank_percent' ? `本科排名前 ${value}% 达线；分数边界随本次已发布成绩队列变化。` : `本科达到 ${value} 分视为单科达线。`;
}

function refreshExamScoringForm(form) {
  if (!form?.matches('[data-form="exam-form"]')) return;
  const rows = [...form.querySelectorAll('.exam-subject-editor')];
  rows.forEach(refreshSubjectPassRuleRow);
  const total = rows.reduce((sum, row) => sum + Number(row.querySelector('[name="subjectFullScore"]')?.value || 0), 0);
  const totalElement = form.querySelector('[data-exam-total]');
  const countElement = form.querySelector('[data-exam-subject-count]');
  if (totalElement) totalElement.textContent = total;
  if (countElement) countElement.textContent = rows.length;
  const policySelect = form.querySelector('[name="passPolicy"]');
  policySelect?.querySelector('option[value="score_ratio"]')?.remove();
  const policy = policySelect?.value || 'rank_percent';
  const valueField = form.querySelector('[data-pass-value-field]');
  const valueInput = form.querySelector('[name="passValue"]');
  const unit = form.querySelector('[data-pass-unit]');
  const hint = form.querySelector('[data-pass-hint]');
  const hiddenValue = ['subject_scores', 'none'].includes(policy);
  if (valueField) valueField.classList.toggle('hidden', hiddenValue);
  if (valueInput) {
    valueInput.disabled = hiddenValue;
    valueInput.max = policy === 'fixed_score' ? String(total || 1000) : '100';
    valueInput.min = policy === 'fixed_score' ? '0' : '0.1';
  }
  if (unit) unit.textContent = policy === 'fixed_score' ? '分' : '%';
  const hints = {
    fixed_score: '按报考科目的成绩总和判断；适合所有考生科目组合一致的考试。',
    rank_percent: '在相同报考科目组合且成绩完整的考生中排名，同分并列。',
    subject_scores: '每个科目都必须达到上方配置的单科合格分。',
    none: '只展示成绩、总分和排名，不显示合格或未合格。'
  };
  if (hint) hint.textContent = hints[policy];
}

function openExamForm(exam = null) {
  const editing = Boolean(exam);
  if (editing && exam.status !== 'draft') return toast('无法编辑', '请先将已发布考试撤回为草稿');
  const subjects = exam?.subjects?.length ? exam.subjects : [{ date: String(exam?.examStart || '').slice(0, 10) }];
  const passPolicy = exam?.passPolicy === 'score_ratio' ? 'rank_percent' : (exam?.passPolicy || 'rank_percent');
  setModal(`<div class="modal-head"><div><span>${editing ? 'EDIT EXAM DRAFT' : 'NEW EXAM'}</span><h2>${editing ? '编辑考试草稿' : '创建考试与科目'}</h2><p>先定义科目计分，再选择整场考试的合格判定方式。</p></div><button type="button" data-action="close-modal" aria-label="关闭弹窗">×</button></div><form class="modal-form exam-config-form" data-form="exam-form">${editing ? `<input type="hidden" name="id" value="${h(exam.id)}">` : ''}<section class="exam-form-section"><header><span>01</span><div><h3>基本安排</h3><p>考试名称、开放窗口与考点信息</p></div></header><div class="field-row"><label><span>考试名称 *</span><input name="name" required value="${h(exam?.name)}" placeholder="例如：2027 年春季学业考试"></label><label><span>考试代码</span><input name="code" value="${h(exam?.code)}" placeholder="自动生成或手动填写"></label></div><label><span>考试说明</span><textarea name="description" rows="2" placeholder="报名对象、考试范围等">${h(exam?.description)}</textarea></label><div class="field-row"><label><span>报名开始 *</span><input name="registrationStart" type="datetime-local" value="${dateTimeLocal(exam?.registrationStart)}" required></label><label><span>报名结束 *</span><input name="registrationEnd" type="datetime-local" value="${dateTimeLocal(exam?.registrationEnd)}" required></label></div><div class="field-row"><label><span>考试开始 *</span><input name="examStart" type="datetime-local" value="${dateTimeLocal(exam?.examStart)}" required></label><label><span>考试结束 *</span><input name="examEnd" type="datetime-local" value="${dateTimeLocal(exam?.examEnd)}" required></label></div><div class="field-row"><label><span>准考证下载开始 *</span><input name="admitDownloadStart" type="datetime-local" value="${dateTimeLocal(exam?.admitDownloadStart)}" required></label><label><span>准考证下载结束 *</span><input name="admitDownloadEnd" type="datetime-local" value="${dateTimeLocal(exam?.admitDownloadEnd)}" required></label></div></section><section class="exam-form-section subject-config-section"><header><span>02</span><div><h3>科目与总分</h3><p>每科独立设置满分、合格分、时间和费用</p></div><aside><small><b data-exam-subject-count>${subjects.length}</b> 科</small><strong><b data-exam-total>0</b> 分</strong></aside></header><div data-exam-subjects>${subjects.map(examSubjectEditor).join('')}</div><button class="add-subject-button" type="button" data-action="add-exam-subject">${icons.plus} 添加科目</button></section><section class="exam-form-section pass-policy-section"><header><span>03</span><div><h3>合格线策略</h3><p>系统会在所有报考科目成绩发布后自动判定</p></div></header><div class="pass-policy-grid"><label><span>判定方式 *</span><select name="passPolicy"><option value="score_ratio" ${passPolicy === 'score_ratio' ? 'selected' : ''}>按总分得分率</option><option value="fixed_score" ${passPolicy === 'fixed_score' ? 'selected' : ''}>固定总分线</option><option value="rank_percent" ${passPolicy === 'rank_percent' ? 'selected' : ''}>总成绩排名前百分比</option><option value="subject_scores" ${passPolicy === 'subject_scores' ? 'selected' : ''}>所有单科均达线</option><option value="none" ${passPolicy === 'none' ? 'selected' : ''}>不判定合格</option></select></label><label data-pass-value-field><span>策略数值 *</span><span class="unit-input"><input name="passValue" type="number" min="0" max="100" step="0.1" value="${h(exam?.passValue ?? 60)}" required><em data-pass-unit>%</em></span></label></div><p class="pass-policy-hint" data-pass-hint></p></section><section class="exam-form-section"><header><span>04</span><div><h3>发布设置</h3><p>草稿可继续维护，发布后对考生开放</p></div></header><div class="field-row"><label><span>考点说明</span><input name="location" value="${h(exam?.location)}" placeholder="例如：全市指定考点"></label><label><span>${editing ? '保存状态' : '创建状态'}</span><select name="status"><option value="draft">保存为草稿</option><option value="published">立即发布</option></select></label></div></section><div class="modal-foot"><button type="button" class="ghost-button" data-action="close-modal">取消</button><button type="submit" class="solid-button">${editing ? '保存考试' : '创建考试'}</button></div></form>`);
  refreshExamScoringForm(modalRoot.querySelector('[data-form="exam-form"]'));
}

function openAdmitPreview(reg) {
  const assignments = new Map((reg.admitCard.assignments || []).map(item => [item.subjectId, item]));
  const rows = reg.subjects.map(subject => {
    const assignment = assignments.get(subject.id) || {};
    return `<tr><td>${h(subject.name)}</td><td>${h(subject.date)} ${h(subject.start)}</td><td class="mono">${h(assignment.examRoomCode || '待定')}</td><td><strong>${h(assignment.roomName || assignment.room || '待定')}</strong><small>场地代码 ${h(assignment.roomCode || '—')}</small></td><td>${h(assignment.building || '楼栋待定')} · ${h(assignment.floor || '楼层待定')}</td><td>${h(assignment.seat || '—')}</td></tr>`;
  }).join('');
  setModal(`<div class="modal-head"><div><span>ADMISSION CARD</span><h2>准考证编排明细</h2><p>${h(reg.candidate?.name)} · ${h(reg.exam.name)}</p></div><button data-action="close-modal">×</button></div><div class="admit-preview"><strong>${h(reg.admitCard.number)}</strong><dl><div><dt>固定考点</dt><dd><strong>${h(reg.admitCard.testCenter)}</strong><small>${h(reg.admitCard.centerCode || '')} · ${h(reg.admitCard.centerAddress || '详细地址待公布')}</small></dd></div><div><dt>生成时间</dt><dd>${formatDate(reg.admitCard.generatedAt,true)}</dd></div></dl><table><thead><tr><th>科目</th><th>时间</th><th>考试考场序号</th><th>考场通用名称 / 场地代码</th><th>楼栋 / 楼层</th><th>座位</th></tr></thead><tbody>${rows}</tbody></table><p>“考试考场序号”是本次考试编排编号，不等同于考场通用名称。考生可在 ${dateRange(reg.exam.admitDownloadStart, reg.exam.admitDownloadEnd)} 下载。</p></div><div class="modal-foot"><button class="solid-button" data-action="close-modal">完成</button></div>`);
}

window.addEventListener('hashchange', renderRoute);
window.addEventListener('keydown', event => {
  if (event.key === 'Escape') closeModal();
  if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 's') {
    const form = document.querySelector('form[data-form="result-bulk-entry"]');
    if (form) {
      event.preventDefault();
      form.requestSubmit(form.querySelector('[data-result-mode="draft"]'));
    }
  }
});

document.addEventListener('change', event => {
  if (event.target.matches('[data-admission-reporting-file]')) {
    const input = event.target;
    const file = input.files?.[0];
    if (!file) return;
    const examId = input.dataset.examId;
    input.value = '';
    (async () => {
      try {
        toast('正在导入报到状态', `${file.name} · 导入结果只会暂存`);
        const result = await api(`/api/admission/reporting/import?examId=${encodeURIComponent(examId)}`, { method: 'POST', headers: { 'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }, body: await file.arrayBuffer() });
        state.reportingImportSummaries[examId] = result;
        toast(result.changedCount ? 'Excel 已导入并暂存' : 'Excel 已读取，状态没有变化', result.changedCount ? `读取 ${result.count} 行，实际更新 ${result.changedCount} 人，${result.unchangedCount} 人未变化` : `读取 ${result.count} 行，内容与当前暂存状态一致`);
        await renderRoute();
      } catch (error) { toast('Excel 导入失败', error.message); }
    })();
  }
  if (event.target.matches('[data-reporting-qr-file]')) {
    const input = event.target;
    const file = input.files?.[0];
    if (!file) return;
    input.value = '';
    (async () => {
      try {
        if (!('BarcodeDetector' in window)) throw new Error('当前浏览器不支持图片二维码识别，请粘贴二维码中的核验链接');
        const detector = new BarcodeDetector({ formats: ['qr_code'] });
        const codes = await detector.detect(file);
        const code = codes[0]?.rawValue || '';
        if (!code) throw new Error('图片中没有识别到二维码');
        await previewReportingScan(code, input.dataset.examId || '');
      } catch (error) { toast('二维码识别失败', error.message); }
    })();
  }
});

try {
  await Promise.all([refreshPublic(), refreshSession()]);
  await renderRoute();
} catch (error) {
  renderError(error);
}
