<script setup>
import { computed, onMounted, reactive, ref } from "vue";
import LedgerPager from "@/components/common/LedgerPager.vue";
import StatusBadge from "@/components/common/StatusBadge.vue";
import { useLedger } from "@/composables/useLedger";
import { api } from "@/lib/api";
import { downloadSelectedExcel } from "@/lib/export-selection";
import { dateRange, money, passPolicyText } from "@/lib/format";
import { sessionStore } from "@/stores/session";
import { uiStore } from "@/stores/ui";

const props = defineProps({
  page: { type: String, required: true },
  data: { type: Object, default: () => ({}) },
});
const emit = defineEmits(["reload"]);
const busy = ref(false);
const error = ref("");
const arrangementPreview = ref(null);
const resultImportPreview = ref(null);
const resultData = ref({
  exams: sessionStore.state.publicData.exams || [],
  results: [],
  appeals: [],
});
const selectedExamId = ref("");
const selectedSubjectId = ref("");
const roster = ref([]);
const featureRoster = ref([]);
const scoreDrafts = reactive({});
const featureDrafts = reactive({});
const appealNotes = reactive({});
const resultPage = ref(1);
const resultPagination = ref({ page: 1, totalPages: 1, total: 0 });
const rosterPage = ref(1);
const rosterPagination = ref({ page: 1, totalPages: 1, total: 0 });
const featurePage = ref(1);
const featurePagination = ref({ page: 1, totalPages: 1, total: 0 });
const resultQuery = ref("");
const resultStatus = ref("all");
const resultPageSize = ref(50);
const rosterQuery = ref("");
const rosterStatus = ref("all");
const rosterPageSize = ref(50);
const featureQuery = ref("");
const featureStatus = ref("all");
const featurePageSize = ref(50);
const resultSelected = ref([]);
const admitSelected = ref([]);
const resultSelectionCache = new Map();
const arrangement = reactive({
  examId: "",
  mixingScope: "school",
  numberRuleId: "",
  seed: "",
});
const examForm = reactive({
  name: "",
  code: "",
  description: "",
  registrationStart: "",
  registrationEnd: "",
  examStart: "",
  examEnd: "",
  admitDownloadStart: "",
  admitDownloadEnd: "",
  passPolicy: "rank_percent",
  passValue: 60,
  location: "",
  status: "draft",
  subjects: [],
});

const selectedExam = computed(() =>
  resultData.value.exams?.find((item) => item.id === selectedExamId.value),
);
const selectedSubject = computed(() =>
  selectedExam.value?.subjects?.find(
    (item) => item.id === selectedSubjectId.value,
  ),
);
const admitLedger = useLedger(() => props.data.registrations || [], {
  filters: {
    exam: (item, value) => (item.exam?.id || item.examId) === value,
    school: (item, value) =>
      (item.candidate?.school || item.schoolName) === value,
    status: (item, value) =>
      value === "arranged"
        ? Boolean(item.admitCard?.number)
        : !item.admitCard?.number,
    center: (item, value) =>
      (item.admitCard?.centerId || item.admitCard?.testCenter) === value,
  },
  searchText: (item) =>
    [
      item.candidate?.name,
      item.candidate?.candidateNumber,
      item.registrationNumber,
      item.exam?.name,
      item.admitCard?.number,
      item.admitCard?.testCenter,
      ...(item.admitCard?.assignments || []).map(
        (row) => `${row.subjectName} ${row.roomName || row.room} ${row.seat}`,
      ),
    ].join(" "),
});
const admitSchools = computed(() => [
  ...new Set(
    (props.data.registrations || [])
      .map((item) => item.candidate?.school || item.schoolName)
      .filter(Boolean),
  ),
]);
const admitCenters = computed(() => {
  const map = new Map();
  for (const item of props.data.registrations || []) {
    const id = item.admitCard?.centerId || item.admitCard?.testCenter;
    if (id) map.set(id, item.admitCard?.testCenter || id);
  }
  return [...map].map(([id, name]) => ({ id, name }));
});
function togglePageSelection(selection, rows) {
  const ids = rows.map((item) => item.id);
  const current = Array.isArray(selection) ? selection : selection.value;
  const allSelected = ids.length > 0 && ids.every((id) => current.includes(id));
  const next = allSelected
    ? current.filter((id) => !ids.includes(id))
    : [...new Set([...current, ...ids])];
  if (Array.isArray(selection)) selection.splice(0, selection.length, ...next);
  else selection.value = next;
}
function exportAdmitSelected() {
  if (!admitSelected.value.length) return;
  const anchor = document.createElement("a");
  anchor.href = `/api/admin/admission-exports/info?examId=${encodeURIComponent(admitLedger.filters.exam || arrangement.examId)}&ids=${encodeURIComponent(admitSelected.value.join(","))}`;
  anchor.click();
}
function exportResultsSelected() {
  downloadSelectedExcel("results", resultSelected.value, {
    examId: selectedExamId.value,
  });
}
function rememberResultPage() {
  for (const item of resultData.value.results || []) {
    if (resultSelected.value.includes(item.id))
      resultSelectionCache.set(item.id, item);
  }
}
function selectedResultRows() {
  rememberResultPage();
  return resultSelected.value
    .map((id) => resultSelectionCache.get(id))
    .filter(Boolean);
}
function emptySubject() {
  return {
    name: "",
    fullScore: 100,
    passRule: "fixed_score",
    passValue: 60,
    date: "",
    start: "09:00",
    end: "11:00",
    fee: 0,
  };
}
function addSubject() {
  examForm.subjects.push(emptySubject());
}

async function act(action, success, reload = true) {
  busy.value = true;
  error.value = "";
  try {
    const result = await action();
    if (success) uiStore.notify(success);
    if (reload) emit("reload");
    return result;
  } catch (requestError) {
    error.value = requestError.message;
    return null;
  } finally {
    busy.value = false;
  }
}
function createExam() {
  const body = {
    ...examForm,
    subjects: examForm.subjects.map((item) => ({
      ...item,
      fullScore: Number(item.fullScore),
      passValue: Number(item.passValue),
      fee: Number(item.fee),
    })),
  };
  for (const field of [
    "registrationStart",
    "registrationEnd",
    "examStart",
    "examEnd",
    "admitDownloadStart",
    "admitDownloadEnd",
  ])
    body[field] = new Date(body[field]).toISOString();
  body.passValue = ["subject_scores", "none"].includes(body.passPolicy)
    ? 0
    : Number(body.passValue);
  act(
    () => api("/api/admin/exams", { method: "POST", body }),
    "考试计划已创建",
  );
}
function toggleExam(item) {
  act(
    () =>
      api(`/api/admin/exams/${item.id}`, {
        method: "PATCH",
        body: { status: item.status === "published" ? "draft" : "published" },
      }),
    item.status === "published" ? "考试已撤回为草稿" : "考试已发布",
  );
}
function archiveExam(item) {
  if (window.confirm(`确认归档“${item.name}”并永久锁定成绩吗？`))
    act(
      () => api(`/api/admin/exams/${item.id}/archive`, { method: "POST" }),
      "考试已归档",
    );
}
function configureArrangement(exam) {
  if (!exam) return;
  arrangement.examId = exam.id;
  admitLedger.filters.exam = exam.id;
  arrangement.seed ||= exam.code;
  arrangement.numberRuleId ||= props.data.rules?.[0]?.id || "";
}
async function previewArrangement() {
  arrangementPreview.value = await act(
    () =>
      api(
        `/api/admin/exams/${arrangement.examId}/admission-arrangement/preview`,
        { method: "POST", body: arrangement },
      ),
    "容量与档案预检完成",
    false,
  );
}
function applyArrangement() {
  if (window.confirm("确认生成或替换本场考试全部准考证编排吗？"))
    act(
      () =>
        api(`/api/admin/exams/${arrangement.examId}/admission-arrangement`, {
          method: "POST",
          body: arrangement,
        }),
      "整场准考证编排已生成",
    );
}

async function loadResults() {
  if (!selectedExamId.value) return;
  rememberResultPage();
  busy.value = true;
  error.value = "";
  try {
    const summary = await api(
      `/api/admin/results/summary?examId=${encodeURIComponent(selectedExamId.value)}`,
    );
    resultData.value = { ...summary };
    selectedSubjectId.value ||= selectedExam.value?.subjects?.[0]?.id || "";
    const ledgerParams = new URLSearchParams({
      examId: selectedExamId.value,
      page: String(resultPage.value),
      pageSize: String(resultPageSize.value),
      status: resultStatus.value,
      query: resultQuery.value,
    });
    const rosterParams = new URLSearchParams({
      examId: selectedExamId.value,
      subjectId: selectedSubjectId.value,
      page: String(rosterPage.value),
      pageSize: String(rosterPageSize.value),
      status: rosterStatus.value,
      query: rosterQuery.value,
    });
    const featureParams = new URLSearchParams({
      examId: selectedExamId.value,
      mode: "feature",
      page: String(featurePage.value),
      pageSize: String(featurePageSize.value),
      status: featureStatus.value,
      query: featureQuery.value,
    });
    const [ledger, entry, features] = await Promise.all([
      api(`/api/admin/results?${ledgerParams}`),
      selectedSubjectId.value
        ? api(`/api/admin/results/roster?${rosterParams}`)
        : Promise.resolve({
            items: [],
            pagination: { page: 1, totalPages: 1, total: 0 },
          }),
      api(`/api/admin/results/roster?${featureParams}`),
    ]);
    resultData.value.results = ledger.items || [];
    roster.value = entry.items || [];
    featureRoster.value = features.items || [];
    resultPagination.value = ledger.pagination || {
      page: 1,
      totalPages: 1,
      total: 0,
    };
    rosterPagination.value = entry.pagination || {
      page: 1,
      totalPages: 1,
      total: 0,
    };
    featurePagination.value = features.pagination || {
      page: 1,
      totalPages: 1,
      total: 0,
    };
    for (const item of roster.value)
      scoreDrafts[item.id] = item.result?.score ?? "";
    for (const item of featureRoster.value)
      featureDrafts[item.id] = Number(item.featureScore || 0);
  } catch (requestError) {
    error.value = requestError.message;
  } finally {
    busy.value = false;
  }
}
function selectResultExam(exam) {
  selectedExamId.value = exam.id;
  selectedSubjectId.value = "";
  resultPage.value = 1;
  rosterPage.value = 1;
  featurePage.value = 1;
  resultSelected.value = [];
  resultSelectionCache.clear();
  loadResults();
}
async function changeSubject() {
  rosterPage.value = 1;
  await loadResults();
}
async function changeResultPage(target) {
  resultPage.value = target;
  await loadResults();
}
async function changeRosterPage(target) {
  rosterPage.value = target;
  await loadResults();
}
async function changeFeaturePage(target) {
  featurePage.value = target;
  await loadResults();
}
async function changeResultPageSize(size) {
  resultPageSize.value = size;
  resultPage.value = 1;
  await loadResults();
}
async function changeRosterPageSize(size) {
  rosterPageSize.value = size;
  rosterPage.value = 1;
  await loadResults();
}
async function changeFeaturePageSize(size) {
  featurePageSize.value = size;
  featurePage.value = 1;
  await loadResults();
}
async function applyResultFilters() {
  resultPage.value = 1;
  await loadResults();
}
async function applyRosterFilters() {
  rosterPage.value = 1;
  await loadResults();
}
async function applyFeatureFilters() {
  featurePage.value = 1;
  await loadResults();
}
function saveScores(published) {
  const rows = roster.value
    .filter((item) => scoreDrafts[item.id] !== "")
    .map((item) => ({
      registrationId: item.id,
      score: Number(scoreDrafts[item.id]),
    }));
  if (!rows.length) {
    error.value = "请至少录入一条成绩";
    return;
  }
  if (
    published &&
    !window.confirm(`确认发布当前名单 ${rows.length} 条成绩吗？`)
  )
    return;
  act(
    () =>
      api("/api/admin/results/bulk", {
        method: "POST",
        body: {
          examId: selectedExamId.value,
          subjectId: selectedSubjectId.value,
          published,
          rows,
        },
      }),
    published ? "成绩已发布" : "成绩已暂存",
    false,
  ).then(loadResults);
}
async function bulkResultStatus(published) {
  const selectedRows = selectedResultRows();
  if (
    !selectedRows.length ||
    !window.confirm(
      `确认将选中的 ${selectedRows.length} 条成绩统一改为“${published ? "已发布" : "未发布"}”吗？`,
    )
  )
    return;
  busy.value = true;
  error.value = "";
  try {
    const groups = new Map();
    for (const item of selectedRows)
      groups.set(item.subjectId, [...(groups.get(item.subjectId) || []), item]);
    for (const [subjectId, rows] of groups) {
      await api("/api/admin/results/bulk", {
        method: "POST",
        body: {
          examId: selectedExamId.value,
          subjectId,
          published,
          rows: rows.map((item) => ({
            registrationId: item.registrationId,
            score: item.score,
          })),
        },
      });
    }
    uiStore.notify(`已更新 ${selectedRows.length} 条成绩状态`);
    resultSelected.value = [];
    resultSelectionCache.clear();
    await loadResults();
  } catch (requestError) {
    error.value = requestError.message;
  } finally {
    busy.value = false;
  }
}
function saveFeatures() {
  const rows = featureRoster.value.map((item) => ({
    registrationId: item.id,
    featureScore: Number(featureDrafts[item.id] || 0),
  }));
  act(
    () =>
      api("/api/admin/feature-scores/bulk", {
        method: "POST",
        body: { examId: selectedExamId.value, rows },
      }),
    "特征分已保存",
    false,
  ).then(loadResults);
}
function refreshCache() {
  act(
    () => api("/api/admin/results/cache/refresh", { method: "POST" }),
    "成绩缓存已刷新",
    false,
  );
}
function reviewAppeal(item, status) {
  const finalStep = item.currentStep >= (item.steps?.length || 1);
  let reviewedScore = item.result?.score;
  if (status === "approved" && finalStep) {
    const input = window.prompt(
      `请输入复核后的成绩（满分 ${item.result?.fullScore}）`,
      String(item.result?.score ?? ""),
    );
    if (input == null) return;
    reviewedScore = Number(input);
  }
  act(
    () =>
      api(`/api/admin/score-appeals/${item.result.id}`, {
        method: "PATCH",
        body: { status, reviewedScore, reviewNote: appealNotes[item.id] || "" },
      }),
    status === "approved" ? "成绩复议已处理" : "成绩复议已退回",
    false,
  ).then(loadResults);
}
async function importResultFile(event) {
  const file = event.target.files?.[0];
  if (!file) return;
  resultImportPreview.value = await act(
    () =>
      api("/api/admin/excel/results", {
        method: "POST",
        headers: {
          "Content-Type":
            "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
        },
        body: file,
      }),
    "Excel 已解析，请核对预览后确认写入",
    false,
  );
  event.target.value = "";
}
async function confirmResultImport() {
  const preview = resultImportPreview.value;
  if (!preview || preview.summary?.invalid) {
    error.value = "请先修正 Excel 中的无效行并重新上传";
    return;
  }
  if (!window.confirm(`确认写入 ${preview.summary?.valid || 0} 条成绩吗？`))
    return;
  const result = await act(
    () =>
      api("/api/admin/results/import", {
        method: "POST",
        body: { rows: preview.rows },
      }),
    "成绩已原子批量写入",
    false,
  );
  if (result) {
    resultImportPreview.value = null;
    await loadResults();
  }
}
onMounted(() => {
  if (props.page === "exams" && !examForm.subjects.length) addSubject();
  if (props.page === "admit") {
    const exam = props.data.exams?.find((item) => !item.archivedAt);
    if (exam) configureArrangement(exam);
  }
});
</script>

<template>
  <div class="admin-exam-workspace">
    <div v-if="error" class="form-error">{{ error }}</div>
    <section
      v-if="page === 'results' && resultImportPreview"
      class="record-panel import-preview-vue"
    >
      <header>
        <div>
          <h2>成绩 Excel 写入预览</h2>
          <p>预览不会修改数据库；只有全部行校验通过后才能确认写入。</p>
        </div>
        <button
          class="app-button"
          type="button"
          @click="resultImportPreview = null"
        >
          关闭预览
        </button>
      </header>
      <section class="record-metrics">
        <article>
          <span>总行数</span
          ><strong>{{ resultImportPreview.summary?.total || 0 }}</strong>
        </article>
        <article>
          <span>有效</span
          ><strong>{{ resultImportPreview.summary?.valid || 0 }}</strong>
        </article>
        <article>
          <span>无效</span
          ><strong>{{ resultImportPreview.summary?.invalid || 0 }}</strong>
        </article>
        <article>
          <span>将发布</span
          ><strong>{{ resultImportPreview.summary?.publish || 0 }}</strong>
        </article>
      </section>
      <div class="table-scroll">
        <table>
          <thead>
            <tr>
              <th>Excel 行</th>
              <th>考生</th>
              <th>考试 / 科目</th>
              <th>成绩</th>
              <th>模式</th>
              <th>校验</th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="row in resultImportPreview.rows" :key="row.sourceRow">
              <td>{{ row.sourceRow }}</td>
              <td>
                {{ row.candidateName }}<small>{{ row.candidateNumber }}</small>
              </td>
              <td>
                {{ row.examName }}<small>{{ row.subjectName }}</small>
              </td>
              <td>{{ row.score }} / {{ row.fullScore }}</td>
              <td>
                {{ row.mode === "create" ? "新增" : "更新" }} ·
                {{ row.published ? "发布" : "暂存" }}
              </td>
              <td>
                <span v-if="row.errors?.length" class="form-error">{{
                  row.errors.join("；")
                }}</span
                ><span v-else>通过</span>
              </td>
            </tr>
          </tbody>
        </table>
      </div>
      <footer>
        <button
          class="app-button app-button--primary"
          type="button"
          :disabled="Boolean(resultImportPreview.summary?.invalid) || busy"
          @click="confirmResultImport"
        >
          确认原子批量写入
        </button>
      </footer>
    </section>
    <template v-if="page === 'exams'"
      ><form
        class="business-form exam-builder-vue"
        @submit.prevent="createExam"
      >
        <header>
          <div>
            <p>新建考试</p>
            <h2>创建考试与科目</h2>
            <span>先定义科目计分，再选择整场合格判定方式。</span>
          </div>
        </header>
        <div class="form-grid">
          <label
            ><span>考试名称</span
            ><input v-model="examForm.name" required /></label
          ><label><span>考试代码</span><input v-model="examForm.code" /></label>
        </div>
        <label
          ><span>考试说明</span
          ><textarea v-model="examForm.description" rows="2"></textarea>
        </label>
        <div class="form-grid">
          <label
            ><span>报名开始</span
            ><input
              v-model="examForm.registrationStart"
              type="datetime-local"
              required /></label
          ><label
            ><span>报名结束</span
            ><input
              v-model="examForm.registrationEnd"
              type="datetime-local"
              required /></label
          ><label
            ><span>考试开始</span
            ><input
              v-model="examForm.examStart"
              type="datetime-local"
              required /></label
          ><label
            ><span>考试结束</span
            ><input
              v-model="examForm.examEnd"
              type="datetime-local"
              required /></label
          ><label
            ><span>准考证下载开始</span
            ><input
              v-model="examForm.admitDownloadStart"
              type="datetime-local"
              required /></label
          ><label
            ><span>准考证下载结束</span
            ><input
              v-model="examForm.admitDownloadEnd"
              type="datetime-local"
              required
          /></label>
        </div>
        <section class="exam-subject-builder">
          <header>
            <strong>考试科目</strong
            ><button type="button" @click="addSubject">＋ 添加科目</button>
          </header>
          <article v-for="(subject, index) in examForm.subjects" :key="index">
            <div class="form-grid">
              <label
                ><span>科目名称</span
                ><input v-model="subject.name" required /></label
              ><label
                ><span>满分</span
                ><input
                  v-model="subject.fullScore"
                  type="number"
                  min="0.5"
                  step="0.5"
                  required /></label
              ><label
                ><span>及格线方式</span
                ><select v-model="subject.passRule">
                  <option value="fixed_score">固定分</option>
                  <option value="rank_percent">排名比例</option>
                  <option value="none">不设单科线</option>
                </select></label
              ><label
                ><span>规则数值</span
                ><input
                  v-model="subject.passValue"
                  type="number"
                  min="0" /></label
              ><label
                ><span>日期</span
                ><input v-model="subject.date" type="date" required /></label
              ><label
                ><span>开始</span
                ><input v-model="subject.start" type="time" required /></label
              ><label
                ><span>结束</span
                ><input v-model="subject.end" type="time" required /></label
              ><label
                ><span>费用</span
                ><input v-model="subject.fee" type="number" min="0" step="0.01"
              /></label>
            </div>
            <button type="button" @click="examForm.subjects.splice(index, 1)">
              移除科目
            </button>
          </article>
        </section>
        <div class="form-grid">
          <label
            ><span>整场合格判定</span
            ><select v-model="examForm.passPolicy">
              <option value="score_ratio">总分得分率</option>
              <option value="fixed_score">固定总分线</option>
              <option value="rank_percent">总成绩排名比例</option>
              <option value="subject_scores">全部单科达线</option>
              <option value="none">不判定</option>
            </select></label
          ><label
            ><span>策略数值</span
            ><input v-model="examForm.passValue" type="number" min="0" /></label
          ><label
            ><span>考点说明</span><input v-model="examForm.location" /></label
          ><label
            ><span>创建状态</span
            ><select v-model="examForm.status">
              <option value="draft">草稿</option>
              <option value="published">立即发布</option>
            </select></label
          >
        </div>
        <button class="app-button app-button--primary" :disabled="busy">
          创建考试计划
        </button>
      </form>
      <section class="admin-exam-grid-vue">
        <article
          v-for="exam in data.exams"
          :key="exam.id"
          class="exam-apply-card"
        >
          <header>
            <span>{{ exam.code }}</span
            ><StatusBadge :value="exam.archivedAt ? 'archived' : exam.status" />
          </header>
          <h2>{{ exam.name }}</h2>
          <p>{{ exam.description }}</p>
          <dl>
            <div>
              <dt>报名</dt>
              <dd>
                {{ dateRange(exam.registrationStart, exam.registrationEnd) }}
              </dd>
            </div>
            <div>
              <dt>考试</dt>
              <dd>{{ dateRange(exam.examStart, exam.examEnd) }}</dd>
            </div>
            <div>
              <dt>合格规则</dt>
              <dd>{{ passPolicyText(exam) }}</dd>
            </div>
          </dl>
          <div class="chip-list">
            <span v-for="subject in exam.subjects" :key="subject.id"
              >{{ subject.name
              }}<small
                >满分 {{ subject.fullScore }} · {{ money(subject.fee) }}</small
              ></span
            >
          </div>
          <footer>
            <span>{{ exam.registrationCount || 0 }} 人报名</span>
            <div v-if="!exam.archivedAt">
              <button class="table-action" @click="toggleExam(exam)">
                {{
                  exam.status === "published" ? "撤回草稿" : "发布考试"
                }}</button
              ><button class="table-action" @click="archiveExam(exam)">
                归档锁定
              </button>
            </div>
          </footer>
        </article>
      </section></template
    >

    <template v-else-if="page === 'admit'"
      ><section
        v-if="data.canArrange"
        class="business-form arrangement-console-vue"
      >
        <p>准考证编排</p>
        <h2>整场准考证编排</h2>
        <div class="form-grid">
          <label
            ><span>考试</span
            ><select
              v-model="arrangement.examId"
              @change="
                configureArrangement(
                  data.exams.find((item) => item.id === arrangement.examId),
                )
              "
            >
              <option
                v-for="exam in data.exams"
                :key="exam.id"
                :value="exam.id"
              >
                {{ exam.name }}（通过 {{ exam.approvedCount }} / 已编排
                {{ exam.arrangedCount }}）
              </option>
            </select></label
          ><label
            ><span>混编范围</span
            ><select v-model="arrangement.mixingScope">
              <option
                v-for="item in data.mixingScopes"
                :key="item.code"
                :value="item.code"
              >
                {{ item.name }}
              </option>
            </select></label
          ><label
            ><span>号码规则</span
            ><select v-model="arrangement.numberRuleId">
              <option
                v-for="rule in data.rules"
                :key="rule.id"
                :value="rule.id"
              >
                {{ rule.name }}
              </option>
            </select></label
          ><label
            ><span>稳定编排种子</span><input v-model="arrangement.seed"
          /></label>
        </div>
        <div>
          <button class="app-button" @click="previewArrangement">仅预检</button
          ><button
            class="app-button app-button--primary"
            @click="applyArrangement"
          >
            生成整场编排
          </button>
        </div>
        <pre v-if="arrangementPreview">{{
          JSON.stringify(arrangementPreview, null, 2)
        }}</pre>
      </section>
      <div class="excel-action-bar">
        <a
          v-for="type in data.canExportCenterMaterials
            ? ['admit-cards', 'info', 'center-materials']
            : ['admit-cards', 'info']"
          :key="type"
          :href="`/api/admin/admission-exports/${type}?examId=${encodeURIComponent(arrangement.examId)}`"
          >{{
            type === "admit-cards"
              ? "批量下载准考证"
              : type === "info"
                ? "导出准考证信息"
                : "导出考点材料"
          }}</a
        >
      </div>
      <section class="record-panel">
        <header>
          <div>
            <h2>准考证编排台账</h2>
            <p>
              筛选结果 {{ admitLedger.total }} / 共
              {{ data.registrations?.length || 0 }} 人
            </p>
          </div>
        </header>
        <div class="ledger-toolbar ledger-toolbar--wide">
          <label
            ><span>关键词</span
            ><input
              v-model="admitLedger.query"
              placeholder="考生、报名号、准考证号、考点、考场"
          /></label>
          <label
            ><span>考试</span
            ><select v-model="admitLedger.filters.exam">
              <option value="">全部考试</option>
              <option
                v-for="exam in data.exams"
                :key="exam.id"
                :value="exam.id"
              >
                {{ exam.name }}
              </option>
            </select></label
          >
          <label
            ><span>生源学校</span
            ><select v-model="admitLedger.filters.school">
              <option value="">全部学校</option>
              <option v-for="school in admitSchools" :key="school">
                {{ school }}
              </option>
            </select></label
          >
          <label
            ><span>编排状态</span
            ><select v-model="admitLedger.filters.status">
              <option value="">全部状态</option>
              <option value="arranged">已编排</option>
              <option value="pending">待编排</option>
            </select></label
          >
          <label
            ><span>固定考点</span
            ><select v-model="admitLedger.filters.center">
              <option value="">全部考点</option>
              <option
                v-for="item in admitCenters"
                :key="item.id"
                :value="item.id"
              >
                {{ item.name }}
              </option>
            </select></label
          >
          <button class="table-action" @click="admitLedger.clear">
            清除筛选
          </button>
        </div>
        <div class="ledger-bulk">
          <label
            ><input
              type="checkbox"
              @change="togglePageSelection(admitSelected, admitLedger.rows)"
            />
            选择当前页</label
          >
          <strong>已选 {{ admitSelected.length }} 人</strong>
          <span>选择会跨页保留</span>
          <button
            :disabled="!admitSelected.length"
            @click="exportAdmitSelected"
          >
            导出选中项 XLSX
          </button>
        </div>
        <div class="table-scroll">
          <table>
            <thead>
              <tr>
                <th>选择</th>
                <th>考生</th>
                <th>考试</th>
                <th>准考证号</th>
                <th>固定考点</th>
                <th>分科考场与座位</th>
              </tr>
            </thead>
            <tbody>
              <tr v-for="item in admitLedger.rows" :key="item.id">
                <td>
                  <input
                    v-model="admitSelected"
                    type="checkbox"
                    :value="item.id"
                  />
                </td>
                <td>
                  {{ item.candidate?.name
                  }}<small>{{ item.candidate?.school }}</small>
                </td>
                <td>{{ item.exam?.name }}</td>
                <td>{{ item.admitCard?.number || "待编排" }}</td>
                <td>
                  {{ item.admitCard?.testCenter || "—"
                  }}<small>{{ item.admitCard?.centerAddress }}</small>
                </td>
                <td>
                  <span
                    v-for="assignment in item.admitCard?.assignments"
                    :key="assignment.subjectId"
                    class="table-stack"
                    >{{ assignment.subjectName }} ·
                    {{ assignment.roomName || assignment.room }} · 座位
                    {{ assignment.seat }}</span
                  >
                </td>
              </tr>
            </tbody>
          </table>
        </div>
        <LedgerPager
          v-model:page="admitLedger.page"
          v-model:page-size="admitLedger.pageSize"
          :total="admitLedger.total"
        /></section
    ></template>

    <template v-else-if="page === 'results'"
      ><section class="result-exam-picker">
        <button
          v-for="exam in resultData.exams"
          :key="exam.id"
          :class="{ active: selectedExamId === exam.id }"
          @click="selectResultExam(exam)"
        >
          <span>{{ exam.code }}</span
          ><strong>{{ exam.name }}</strong
          ><small>{{
            exam.archivedAt
              ? "已归档锁定"
              : `${exam.registrationCount || 0} 人报名`
          }}</small>
        </button>
      </section>
      <template v-if="selectedExam"
        ><div class="excel-action-bar">
          <a
            :href="`/api/admin/excel/results?template=1&examId=${selectedExamId}`"
            >下载成绩名单模板</a
          ><label v-if="sessionStore.state.user?.adminLevel === 'super'"
            >导入成绩 Excel<input
              type="file"
              accept=".xlsx"
              hidden
              @change="importResultFile" /></label
          ><a :href="`/api/admin/excel/results?examId=${selectedExamId}`"
            >导出本场成绩</a
          ><button class="table-action" @click="refreshCache">
            刷新成绩缓存
          </button>
        </div>
        <section class="record-metrics">
          <article>
            <span>报名考生</span
            ><strong>{{ selectedExam.registrationCount || 0 }}</strong>
          </article>
          <article>
            <span>已录科次</span
            ><strong
              >{{ selectedExam.scored || 0 }} /
              {{ selectedExam.enrolledSubjects || 0 }}</strong
            >
          </article>
          <article>
            <span>已发布</span
            ><strong>{{ selectedExam.published || 0 }}</strong>
          </article>
          <article>
            <span>成绩出齐</span
            ><strong>{{ selectedExam.complete || 0 }}</strong>
          </article>
        </section>
        <section v-if="resultData.appeals?.length" class="record-panel">
          <header>
            <div>
              <h2>成绩复议审批</h2>
              <p>
                {{
                  resultData.appeals.filter((item) => item.status === "pending")
                    .length
                }}
                项待处理
              </p>
            </div>
          </header>
          <div class="ledger-toolbar">
            <label
              ><span>名单搜索</span
              ><input
                v-model="rosterQuery"
                placeholder="考生、报名号、学校、准考证号"
                @keyup.enter="applyRosterFilters" /></label
            ><label
              ><span>录入状态</span
              ><select v-model="rosterStatus">
                <option value="all">全部状态</option>
                <option value="pending">未录入</option>
                <option value="draft">未发布</option>
                <option value="published">已发布</option>
              </select></label
            ><button class="table-action" @click="applyRosterFilters">
              应用筛选
            </button>
          </div>
          <div class="table-scroll">
            <table>
              <thead>
                <tr>
                  <th>考生 / 科目</th>
                  <th>当前成绩</th>
                  <th>申请理由</th>
                  <th>流程</th>
                  <th>处理</th>
                </tr>
              </thead>
              <tbody>
                <tr v-for="item in resultData.appeals" :key="item.id">
                  <td>
                    {{ item.result?.candidateName
                    }}<small
                      >{{ item.result?.candidateNumber }} ·
                      {{ item.result?.subjectName }}</small
                    >
                  </td>
                  <td>
                    {{ item.result?.score }} / {{ item.result?.fullScore }}
                  </td>
                  <td>{{ item.reason }}</td>
                  <td>
                    <StatusBadge :value="item.status" /><small>{{
                      item.currentStepDetail?.name
                    }}</small>
                  </td>
                  <td>
                    <div v-if="item.status === 'pending'" class="row-decision">
                      <input
                        v-model="appealNotes[item.id]"
                        placeholder="复议意见"
                      /><button @click="reviewAppeal(item, 'rejected')">
                        退回</button
                      ><button @click="reviewAppeal(item, 'approved')">
                        通过
                      </button>
                    </div>
                  </td>
                </tr>
              </tbody>
            </table>
          </div>
        </section>
        <section
          v-if="
            sessionStore.state.user?.adminLevel === 'super' &&
            !selectedExam.archivedAt
          "
          class="record-panel result-entry-vue"
        >
          <header>
            <div>
              <h2>按科目批量录入</h2>
              <p>成绩须在 0 到科目满分之间。</p>
            </div>
            <select v-model="selectedSubjectId" @change="changeSubject">
              <option
                v-for="subject in selectedExam.subjects"
                :key="subject.id"
                :value="subject.id"
              >
                {{ subject.name }} · 满分 {{ subject.fullScore }}
              </option>
            </select>
          </header>
          <div class="table-scroll">
            <table>
              <thead>
                <tr>
                  <th>考生</th>
                  <th>报名号</th>
                  <th>准考证号</th>
                  <th>成绩</th>
                  <th>状态</th>
                </tr>
              </thead>
              <tbody>
                <tr v-for="item in roster" :key="item.id">
                  <td>
                    {{ item.candidateName
                    }}<small
                      >{{ item.schoolName }} · {{ item.className }}</small
                    >
                  </td>
                  <td>{{ item.candidateNumber }}</td>
                  <td>{{ item.admitCard?.number || "待编排" }}</td>
                  <td>
                    <input
                      v-model="scoreDrafts[item.id]"
                      type="number"
                      min="0"
                      :max="selectedSubject?.fullScore"
                      step="0.5"
                    />
                  </td>
                  <td>
                    <StatusBadge
                      :value="
                        item.result?.published
                          ? 'published'
                          : item.result
                            ? 'draft'
                            : 'pending'
                      "
                    />
                  </td>
                </tr>
              </tbody>
            </table>
          </div>
          <LedgerPager
            :page="rosterPagination.page"
            :page-size="rosterPageSize"
            :total="rosterPagination.total"
            :total-pages="rosterPagination.totalPages"
            @update:page="changeRosterPage"
            @update:page-size="changeRosterPageSize"
          />
          <footer>
            <button class="app-button" @click="saveScores(false)">
              暂存当前页</button
            ><button
              class="app-button app-button--primary"
              @click="saveScores(true)"
            >
              发布当前页
            </button>
          </footer>
        </section>
        <section
          v-if="
            sessionStore.state.user?.adminLevel === 'super' &&
            !selectedExam.archivedAt
          "
          class="record-panel result-entry-vue"
        >
          <header>
            <div>
              <h2>特征分登记</h2>
              <p>普通类别不计，特长生类别投档时加入文化课总分。</p>
            </div>
            <button
              class="app-button app-button--primary"
              @click="saveFeatures"
            >
              保存当前页特征分
            </button>
          </header>
          <div class="ledger-toolbar">
            <label
              ><span>名单搜索</span
              ><input
                v-model="featureQuery"
                placeholder="考生、报名号、学校、特长项目"
                @keyup.enter="applyFeatureFilters" /></label
            ><label
              ><span>登记状态</span
              ><select v-model="featureStatus">
                <option value="all">全部状态</option>
                <option value="modified">已登记</option>
                <option value="pending">未登记</option>
                <option value="specialty">特长生</option>
              </select></label
            ><button class="table-action" @click="applyFeatureFilters">
              应用筛选
            </button>
          </div>
          <div class="table-scroll">
            <table>
              <thead>
                <tr>
                  <th>考生</th>
                  <th>报名号</th>
                  <th>特长资格</th>
                  <th>特征分</th>
                </tr>
              </thead>
              <tbody>
                <tr v-for="item in featureRoster" :key="item.id">
                  <td>{{ item.candidateName }}</td>
                  <td>{{ item.candidateNumber }}</td>
                  <td>{{ item.specialtyLabel || "普通生" }}</td>
                  <td>
                    <input
                      v-model="featureDrafts[item.id]"
                      type="number"
                      min="0"
                      max="1000"
                    />
                  </td>
                </tr>
              </tbody>
            </table>
          </div>
          <LedgerPager
            :page="featurePagination.page"
            :page-size="featurePageSize"
            :total="featurePagination.total"
            :total-pages="featurePagination.totalPages"
            @update:page="changeFeaturePage"
            @update:page-size="changeFeaturePageSize"
          />
        </section>
        <section class="record-panel">
          <header>
            <div>
              <h2>本场成绩台账</h2>
              <p>{{ resultPagination.total || 0 }} 条</p>
            </div>
          </header>
          <div class="ledger-toolbar ledger-toolbar--wide">
            <label
              ><span>关键词</span
              ><input
                v-model="resultQuery"
                placeholder="考生、报名号、学校、科目"
                @keyup.enter="applyResultFilters" /></label
            ><label
              ><span>成绩状态</span
              ><select v-model="resultStatus">
                <option value="all">全部状态</option>
                <option value="published">已发布</option>
                <option value="draft">未发布</option>
                <option value="qualified">已达线</option>
                <option value="unqualified">未达线</option>
              </select></label
            ><button class="table-action" @click="applyResultFilters">
              应用筛选
            </button>
          </div>
          <div class="ledger-bulk">
            <label
              ><input
                type="checkbox"
                @change="
                  togglePageSelection(resultSelected, resultData.results)
                "
              />
              选择当前页</label
            ><strong>已选 {{ resultSelected.length }} 条</strong
            ><button
              v-if="
                sessionStore.state.user?.adminLevel === 'super' &&
                !selectedExam.archivedAt
              "
              :disabled="!resultSelected.length || busy"
              @click="bulkResultStatus(false)"
            >
              批量改为未发布</button
            ><button
              v-if="
                sessionStore.state.user?.adminLevel === 'super' &&
                !selectedExam.archivedAt
              "
              :disabled="!resultSelected.length || busy"
              @click="bulkResultStatus(true)"
            >
              批量发布</button
            ><button
              :disabled="!resultSelected.length"
              @click="exportResultsSelected"
            >
              导出选中项 XLSX
            </button>
          </div>
          <div class="table-scroll">
            <table>
              <thead>
                <tr>
                  <th>选择</th>
                  <th>考生</th>
                  <th>科目</th>
                  <th>成绩</th>
                  <th>排名 / 等级</th>
                  <th>达线</th>
                  <th>发布</th>
                </tr>
              </thead>
              <tbody>
                <tr v-for="item in resultData.results" :key="item.id">
                  <td>
                    <input
                      v-model="resultSelected"
                      type="checkbox"
                      :value="item.id"
                    />
                  </td>
                  <td>
                    {{ item.candidateName
                    }}<small>{{ item.candidateNumber }}</small>
                  </td>
                  <td>{{ item.subjectName }}</td>
                  <td>
                    <strong>{{ item.score }} / {{ item.fullScore }}</strong>
                  </td>
                  <td>
                    第 {{ item.rank }} / {{ item.cohortSize }} ·
                    {{ item.grade }}
                  </td>
                  <td>
                    {{
                      item.qualified == null
                        ? "不判定"
                        : item.qualified
                          ? "达线"
                          : "未达线"
                    }}
                  </td>
                  <td>
                    <StatusBadge
                      :value="item.published ? 'published' : 'draft'"
                    />
                  </td>
                </tr>
              </tbody>
            </table>
          </div>
          <LedgerPager
            :page="resultPagination.page"
            :page-size="resultPageSize"
            :total="resultPagination.total"
            :total-pages="resultPagination.totalPages"
            @update:page="changeResultPage"
            @update:page-size="changeResultPageSize"
          /></section
      ></template>
      <div v-else class="page-state page-state--empty">
        <strong>请选择考试</strong>
        <p>选中考试后加载成绩录入、分析与复议数据。</p>
      </div></template
    >
  </div>
</template>
