<script setup lang="ts">
import { computed, onMounted, reactive, ref } from 'vue'
import { Plus, Promotion, Refresh, UserFilled, Setting, Search, MagicStick } from '@element-plus/icons-vue'
import http, { apiErrorMessage } from '../api/http'
import { useAuthStore } from '../stores/auth'

const auth = useAuthStore()
const isManager = computed(() =>
  auth.user?.roles.some((r) => ['SuperAdmin', 'AcademicAdmin'].includes(r)) ?? false)
const isTeacher = computed(() => auth.user?.roles.includes('Teacher') && !isManager.value)
const plans = ref<any[]>([])
const selected = ref<any | null>(null)
const personal = ref<any[]>([])
const teachingSessions = ref<any[]>([])
const scoreDialogVisible = ref(false)
const scoreSession = ref<any | null>(null)
const scoreMap = reactive<Record<string, number | null>>({})
const scoreSaving = ref(false)
const terms = ref<any[]>([])
const tasks = ref<any[]>([])
const rooms = ref<any[]>([])
const teachers = ref<any[]>([])
const buildings = ref<any[]>([])
const timeSlots = ref<any[]>([])
const loading = ref(false)
const arrangeLoading = ref(false)
const planDialog = ref(false)
const sessionDialog = ref(false)
const editingSession = ref<any | null>(null)
const rosterDrawer = ref(false)
const roster = ref<any | null>(null)
const enrollmentDialog = ref(false)
const enrollmentSession = ref<any | null>(null)
const eligibleStudents = ref<any[]>([])
const eligibleLoading = ref(false)
const selectedStudentIds = ref<string[]>([])
const planForm = reactive<Record<string, any>>({})
const sessionForm = reactive<Record<string, any>>({})
const statusLabels: Record<string, string> = {
  Draft: '草稿', Published: '已发布', Archived: '已归档',
}

function timeText(startsAt: string) {
  return new Date(startsAt).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', hour12: false })
}
function dateOnlyText(value: string) {
  if (!value) return ''
  return new Intl.DateTimeFormat('zh-CN', { month: '2-digit', day: '2-digit', weekday: 'short' }).format(new Date(value))
}
function periodLabel(session: any) {
  const end = session.startPeriod + session.periodCount - 1
  const startSlot = timeSlots.value.find((s: any) => s.periodNumber === session.startPeriod)
  const endSlot = timeSlots.value.find((s: any) => s.periodNumber === end)
  const timeRange = startSlot && endSlot ? `${startSlot.startsAt}—${endSlot.endsAt}` : ''
  return `第 ${session.startPeriod}-${end} 节${timeRange ? ' · ' + timeRange : ''}`
}

async function load() {
  loading.value = true
  try {
    if (!isManager.value) {
      personal.value = (await http.get('/makeup-exams/my-schedule')).data
      if (isTeacher.value) {
        teachingSessions.value = (await http.get('/makeup-exams/my-teaching-sessions')).data
      }
      return
    }
    plans.value = (await http.get('/makeup-exams/plans')).data
    const plan = plans.value.find((x) => x.id === selected.value?.id) ?? plans.value[0]
    if (plan) await selectPlan(plan.id)
  } catch (error) { ElMessage.error(apiErrorMessage(error)) }
  finally { loading.value = false }
}
async function selectPlan(id: string) {
  selected.value = (await http.get(`/makeup-exams/plans/${id}`)).data
}
function openPlan() {
  Object.assign(planForm, {
    academicTermId: terms.value.find((x) => x.isCurrent)?.id,
    name: '', notes: '',
  })
  planDialog.value = true
}
async function savePlan() {
  try {
    await http.post('/makeup-exams/plans', planForm)
    planDialog.value = false
    await load()
  } catch (error) { ElMessage.error(apiErrorMessage(error)) }
}
function openSession(existing?: any) {
  editingSession.value = existing ?? null
  const firstSlot = timeSlots.value[0]
  Object.assign(sessionForm, {
    teachingTaskId: existing?.teachingTaskId ?? undefined,
    classroomId: existing?.classroomId ?? undefined,
    examDate: existing?.examDate ?? '',
    startPeriod: existing?.startPeriod ?? (firstSlot?.periodNumber ?? 1),
    periodCount: existing?.periodCount ?? 2,
    requiredBuildingId: existing?.requiredBuildingId ?? undefined,
    requiredInvigilatorCount: existing?.requiredInvigilatorCount ?? 2,
    invigilatorIds: existing?.invigilatorIds ?? [],
    notes: existing?.notes ?? '',
  })
  sessionDialog.value = true
}
async function saveSession() {
  try {
    const payload = { ...sessionForm }
    if (editingSession.value) {
      await http.put(`/makeup-exams/plans/${selected.value.id}/sessions/${editingSession.value.id}`, payload)
    } else {
      await http.post(`/makeup-exams/plans/${selected.value.id}/sessions`, payload)
    }
    sessionDialog.value = false
    editingSession.value = null
    await selectPlan(selected.value.id)
  } catch (error) { ElMessage.error(apiErrorMessage(error)) }
}
async function removeSession(row: any) {
  try {
    await ElMessageBox.confirm(`移除"${row.courseName}"补考场次？`, '移除补考场次', { type: 'warning' })
    await http.delete(`/makeup-exams/plans/${selected.value.id}/sessions/${row.id}`)
    await selectPlan(selected.value.id)
  } catch (error: any) {
    if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error))
  }
}
async function autoArrange() {
  try {
    await ElMessageBox.confirm(
      '系统将为未分配考场的场次自动匹配教室，为未满监考的场次自动分配教师。',
      '自动编排', { type: 'info', confirmButtonText: '开始编排' })
    arrangeLoading.value = true
    const res = await http.post(`/makeup-exams/plans/${selected.value.id}/auto-arrange`)
    ElMessage.success(res.data.message)
    await selectPlan(selected.value.id)
  } catch (error: any) {
    if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error))
  } finally { arrangeLoading.value = false }
}
async function publishPlan() {
  try {
    await ElMessageBox.confirm('发布后考试时间、考场与监考安排将锁定。', '发布补考计划', {
      type: 'warning', confirmButtonText: '确认发布',
    })
    await http.post(`/makeup-exams/plans/${selected.value.id}/publish`)
    await load()
  } catch (error: any) {
    if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error))
  }
}
async function showRoster(row: any) {
  try {
    roster.value = (await http.get(`/makeup-exams/sessions/${row.id}/roster`)).data
    rosterDrawer.value = true
  } catch (error) { ElMessage.error(apiErrorMessage(error)) }
}
function periodOptions() {
  const max = timeSlots.value.length > 0 ? timeSlots.value[timeSlots.value.length - 1].periodNumber : 12
  return Array.from({ length: max }, (_, i) => ({ value: i + 1, label: `第 ${i + 1} 节` }))
}
function periodCountOptions() {
  return [1, 2, 3, 4].map(n => ({ value: n, label: `${n} 小节` }))
}
function classroomLabel(room: any) {
  return `${room.name} · ${room.capacity}座 · ${room.buildingName}`
}
function filteredRooms() {
  if (!sessionForm.requiredBuildingId) return rooms.value
  return rooms.value.filter((r: any) => r.buildingId === sessionForm.requiredBuildingId)
}

function openEnrollment(session: any) {
  enrollmentSession.value = session
  eligibleStudents.value = []
  selectedStudentIds.value = []
  enrollmentDialog.value = true
}
async function loadEligibleStudents() {
  if (!enrollmentSession.value) return
  eligibleLoading.value = true
  try {
    eligibleStudents.value = (await http.get('/makeup-exams/eligible-students', {
      params: { teachingTaskId: enrollmentSession.value.teachingTaskId },
    })).data
  } catch (error) { ElMessage.error(apiErrorMessage(error)) }
  finally { eligibleLoading.value = false }
}
function onEnrollmentSelection(rows: any[]) {
  selectedStudentIds.value = rows.map((r: any) => r.studentId)
}
async function enrollSelectedStudents() {
  if (!enrollmentSession.value || selectedStudentIds.value.length === 0) return
  try {
    await http.post(`/makeup-exams/sessions/${enrollmentSession.value.id}/enroll`, {
      studentIds: selectedStudentIds.value,
    })
    ElMessage.success(`已登记 ${selectedStudentIds.value.length} 名学生`)
    enrollmentDialog.value = false
    await selectPlan(selected.value.id)
  } catch (error) { ElMessage.error(apiErrorMessage(error)) }
}
async function archivePlan() {
  try {
    await ElMessageBox.confirm('归档后将不再对学生和教师显示。', '归档补考计划', {
      type: 'info', confirmButtonText: '确认归档',
    })
    await http.post(`/makeup-exams/plans/${selected.value.id}/archive`)
    await load()
  } catch (error: any) {
    if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error))
  }
}

// ── Score entry (teacher) ──
function openScoreDialog(session: any) {
  scoreSession.value = session
  Object.keys(scoreMap).forEach(k => delete scoreMap[k])
  if (session.enrollments) {
    for (const e of session.enrollments) {
      scoreMap[e.studentId] = e.makeupScore ?? null
    }
  }
  scoreDialogVisible.value = true
}
function onScoreInput(studentId: string, val: any) {
  scoreMap[studentId] = val ?? null
}
async function submitScores() {
  if (!scoreSession.value) return
  const entries = Object.entries(scoreMap)
    .filter(([, v]) => v !== null && v !== undefined)
    .map(([studentId, score]) => ({ studentId, score: Number(score) }))
  if (entries.length === 0) { ElMessage.warning('没有可保存的成绩'); return }
  scoreSaving.value = true
  try {
    await http.put(`/makeup-exams/sessions/${scoreSession.value.id}/scores`, entries)
    ElMessage.success(`已保存 ${entries.length} 条补考成绩（合格按60分记）`)
    scoreDialogVisible.value = false
    await load()
  } catch (error) { ElMessage.error(apiErrorMessage(error)) }
  finally { scoreSaving.value = false }
}

// ── Auto-create job ──
const autoJobId = ref<string | null>(null)
const autoJobStatus = ref<string | null>(null)
const autoJobProgress = ref(0)
const autoJobMessage = ref('')
let pollTimer: ReturnType<typeof setInterval> | null = null

async function startAutoCreate() {
  try {
    await ElMessageBox.confirm(
      '系统将自动扫描所有已发布教学任务，查询需补考的学生，创建补考场次并登记学生。不同补考科目可混编在同一考场。',
      '一键生成补考安排', { type: 'info', confirmButtonText: '开始生成' })
    const res = await http.post(`/makeup-exams/plans/${selected.value.id}/auto-create`)
    autoJobId.value = res.data.jobId
    autoJobStatus.value = 'Queued'
    autoJobProgress.value = 0
    autoJobMessage.value = '任务已提交，正在排队...'
    startPolling()
  } catch (error: any) {
    if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error))
  }
}

function startPolling() {
  if (pollTimer) clearInterval(pollTimer)
  pollTimer = setInterval(pollAutoJob, 1500)
}

async function pollAutoJob() {
  if (!autoJobId.value) return
  try {
    const res = await http.get(`/makeup-exams/auto-jobs/${autoJobId.value}`)
    const job = res.data
    autoJobStatus.value = job.status
    if (job.totalCourses > 0) {
      autoJobProgress.value = Math.round((job.processedCourses / job.totalCourses) * 100)
    }
    autoJobMessage.value = job.status === 'Succeeded'
      ? `已完成：${job.createdSessions} 个补考场次，${job.enrolledStudents} 名学生`
      : job.status === 'Failed'
        ? `失败：${job.errorMessage || '未知错误'}`
        : `处理中：${job.processedCourses}/${job.totalCourses} 门课程`
    if (job.status === 'Succeeded' || job.status === 'Failed') {
      stopPolling()
      if (job.status === 'Succeeded') {
        ElMessage.success(autoJobMessage.value)
        await selectPlan(selected.value.id)
      } else {
        ElMessage.error(autoJobMessage.value)
      }
    }
  } catch (_) { stopPolling() }
}

function stopPolling() {
  if (pollTimer) { clearInterval(pollTimer); pollTimer = null }
}

onMounted(async () => {
  try {
    if (isManager.value) {
      const currentTermId = (await http.get('/base-data/terms')).data.find((t: any) => t.isCurrent)?.id
      const [termRes, taskRes, roomRes, teacherRes, buildingRes, slotRes] = await Promise.all([
        http.get('/base-data/terms'),
        http.get('/teaching-tasks', { params: { page: 1, pageSize: 200 } }),
        http.get('/base-data/classrooms'),
        http.get('/personnel/teachers', { params: { page: 1, pageSize: 200, teacherStatus: 'Active' } }),
        http.get('/base-data/buildings'),
        currentTermId ? http.get('/makeup-exams/time-slots-for-term', { params: { academicTermId: currentTermId } }) : Promise.resolve({ data: [] }),
      ])
      terms.value = termRes.data
      tasks.value = taskRes.data.items
      rooms.value = roomRes.data
      teachers.value = teacherRes.data.items
      buildings.value = buildingRes.data
      timeSlots.value = slotRes.data
    }
    await load()
  } catch (error) { ElMessage.error(apiErrorMessage(error)) }
})
</script>

<template>
  <div class="page-stack exam-page">
    <section class="page-intro">
      <div>
        <span class="section-kicker">MAKE-UP EXAMINATION</span>
        <h2>{{ isManager ? '补考安排' : isTeacher ? '补考监考' : '我的补考' }}</h2>
        <p>{{ isManager ? '为不及格、缺考或缓考通过的学生安排补考，登记考生并录入成绩。' : '查看已发布的补考日程。' }}</p>
      </div>
      <el-button v-if="isManager" type="primary" :icon="Plus" @click="openPlan">新建补考计划</el-button>
      <el-button v-else :icon="Refresh" @click="load">刷新日程</el-button>
    </section>

    <template v-if="isManager">
      <section class="exam-plan-strip">
        <button v-for="plan in plans" :key="plan.id" :class="{ active: selected?.id === plan.id }" @click="selectPlan(plan.id)">
          <span>{{ plan.termName }}</span><b>{{ plan.name }}</b>
          <small>{{ plan.sessionCount }} 个场次</small><i>{{ statusLabels[plan.status] }}</i>
        </button>
      </section>
      <section v-if="selected" class="exam-board" v-loading="loading">
        <header>
          <div>
            <span>MAKE-UP EXAM TIMELINE</span>
            <h3>{{ selected.name }}</h3>
            <p>{{ selected.termName }} · {{ selected.sessions.length }} 个补考场次</p>
          </div>
          <div class="exam-actions">
            <el-button v-if="selected.status === 'Draft'" :icon="MagicStick" type="success" @click="startAutoCreate">一键生成</el-button>
            <el-button v-if="selected.status === 'Draft'" :icon="Setting" @click="autoArrange" :loading="arrangeLoading">自动编排</el-button>
            <el-button v-if="selected.status === 'Draft'" :icon="Plus" @click="openSession()">安排补考场次</el-button>
            <el-button v-if="selected.status === 'Draft'" type="primary" :icon="Promotion" @click="publishPlan">发布计划</el-button>
            <el-button v-if="selected.status === 'Published'" type="info" @click="archivePlan">归档</el-button>
          </div>
        </header>
        <div v-if="autoJobId && autoJobStatus !== 'Succeeded' && autoJobStatus !== 'Failed'" style="margin-bottom: 16px">
          <el-alert :title="autoJobMessage" type="info" :closable="false">
            <template #default>
              <el-progress :percentage="autoJobProgress" :stroke-width="8" />
            </template>
          </el-alert>
        </div>
        <div class="exam-timeline">
          <article v-for="session in selected.sessions" :key="session.id" :class="{ unassigned: !session.classroomId }">
            <time>
              <b>{{ dateOnlyText(session.examDate) }}</b>
              <span>{{ periodLabel(session) }}</span>
            </time>
            <div>
              <span>{{ session.courseCode }} · {{ session.taskNumber }}</span>
              <h4>{{ session.courseName }}</h4>
              <p>
                <template v-if="session.classroomId">{{ session.buildingName }} · {{ session.classroomName }} · {{ session.classroomCapacity }}座</template>
                <template v-else><el-tag size="small" type="warning">待分配考场</el-tag></template>
                · {{ session.studentCount }} 人
                <template v-if="session.requiredBuildingName"> · 限{{ session.requiredBuildingName }}</template>
                <template v-if="session.requiredInvigilatorCount > 1"> · {{ session.requiredInvigilatorCount }}名监考</template>
              </p>
            </div>
            <div class="exam-staff">
              <span>监考</span>
              <b>{{ session.invigilatorNames.length ? session.invigilatorNames.join('、') : '待分配' }}</b>
              <small>{{ timeText(session.startsAt) }}—{{ timeText(session.endsAt) }}</small>
            </div>
            <div class="exam-row-actions">
              <el-button link type="primary" @click="showRoster(session)">考生名单</el-button>
              <el-button v-if="selected.status === 'Draft'" link type="success" @click="openEnrollment(session)">登记考生</el-button>
              <el-button v-if="selected.status === 'Draft'" link type="primary" @click="openSession(session)">编辑</el-button>
              <el-button v-if="selected.status === 'Draft'" link type="danger" @click="removeSession(session)">移除</el-button>
            </div>
          </article>
          <el-empty v-if="!selected.sessions.length" description="尚未安排补考场次，点击「安排补考场次」开始。" />
        </div>
      </section>
    </template>

    <!-- Teacher: teaching sessions for score entry -->
    <section v-if="isTeacher && teachingSessions.length" class="exam-board" v-loading="loading" style="margin-top: 0">
      <header>
        <div>
          <span>MAKE-UP EXAM GRADING</span>
          <h3>任课补考成绩录入</h3>
          <p>以下为您任课班级的补考场次，补考合格按60分记入最终成绩。</p>
        </div>
      </header>
      <div class="exam-timeline">
        <article v-for="session in teachingSessions" :key="session.id">
          <time>
            <b>{{ dateOnlyText(session.examDate) }}</b>
            <span>{{ periodLabel(session) }}</span>
          </time>
          <div>
            <span>{{ session.courseCode }} · {{ session.taskNumber }}</span>
            <h4>{{ session.courseName }}</h4>
            <p>
              {{ session.buildingName ? `${session.buildingName} · ${session.classroomName}` : '考场待定' }}
              · {{ session.enrolledCount }} 人 · 已录 {{ session.gradedCount }} 人
            </p>
          </div>
          <div class="exam-row-actions">
            <el-button type="warning" @click="openScoreDialog(session)">录入成绩</el-button>
          </div>
        </article>
      </div>
    </section>

    <section v-else-if="!isTeacher" class="exam-ticket-grid" v-loading="loading">
      <article v-for="item in personal" :key="item.id">
        <div class="exam-ticket-date">
          <b>{{ dateOnlyText(item.examDate) }}</b>
          <span>{{ timeText(item.startsAt) }}—{{ timeText(item.endsAt) }}</span>
        </div>
        <div>
          <span>{{ item.courseCode }} · {{ item.taskNumber }}</span>
          <h3>{{ item.courseName }}</h3>
          <p>{{ item.buildingName ? `${item.buildingName} · ${item.classroomName}` : '考场待定' }}</p>
          <p v-if="item.reason" style="margin-top: 4px">
            <span>补考原因：{{ item.reason === 1 ? '不及格' : item.reason === 2 ? '缺考' : '缓考通过' }}</span>
            <span v-if="item.makeupScore != null"> · 成绩：<b>{{ item.makeupScore }}</b></span>
          </p>
        </div>
        <footer>
          <el-icon><UserFilled /></el-icon>
          监考：{{ item.invigilatorNames?.join('、') || '待定' }}
        </footer>
      </article>
      <el-empty v-if="!personal.length" description="暂无已发布补考安排" />
    </section>

    <!-- Teacher invigilation schedule -->
    <section v-if="isTeacher && personal.length" class="exam-ticket-grid" v-loading="loading" style="margin-top: 0">
      <h3 style="margin-bottom: 16px; font-size: 16px; color: #666">补考监考安排</h3>
      <article v-for="item in personal" :key="'inv-' + item.id">
        <div class="exam-ticket-date">
          <b>{{ dateOnlyText(item.examDate) }}</b>
          <span>{{ timeText(item.startsAt) }}—{{ timeText(item.endsAt) }}</span>
        </div>
        <div>
          <span>{{ item.courseCode }} · {{ item.taskNumber }}</span>
          <h3>{{ item.courseName }}</h3>
          <p>{{ item.buildingName ? `${item.buildingName} · ${item.classroomName}` : '考场待定' }}</p>
        </div>
        <footer>
          <el-icon><UserFilled /></el-icon>
          {{ item.enrolledCount ?? item.studentCount ?? 0 }} 名考生
        </footer>
      </article>
    </section>

    <!-- Plan Dialog -->
    <el-dialog v-model="planDialog" title="新建补考计划" width="600px">
      <el-form label-position="top">
        <el-form-item label="学期">
          <el-select v-model="planForm.academicTermId">
            <el-option v-for="x in terms" :key="x.id" :label="x.name" :value="x.id" />
          </el-select>
        </el-form-item>
        <el-form-item label="计划名称"><el-input v-model="planForm.name" maxlength="120" /></el-form-item>
        <el-form-item label="说明"><el-input v-model="planForm.notes" type="textarea" maxlength="500" /></el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="planDialog = false">取消</el-button>
        <el-button type="primary" @click="savePlan">保存草稿</el-button>
      </template>
    </el-dialog>

    <!-- Session Dialog -->
    <el-dialog v-model="sessionDialog" :title="editingSession ? '编辑补考场次' : '安排补考场次'" width="720px" top="5vh">
      <el-form label-position="top">
        <el-form-item label="教学班">
          <el-select v-model="sessionForm.teachingTaskId" filterable placeholder="选择教学班">
            <el-option v-for="x in tasks" :key="x.id" :label="`${x.taskNumber} · ${x.courseName}`" :value="x.id" />
          </el-select>
        </el-form-item>
        <div class="form-grid">
          <el-form-item label="考试日期">
            <el-date-picker v-model="sessionForm.examDate" type="date" value-format="YYYY-MM-DD" placeholder="选择日期" />
          </el-form-item>
          <el-form-item label="起始节次">
            <el-select v-model="sessionForm.startPeriod">
              <el-option v-for="opt in periodOptions()" :key="opt.value" :label="opt.label" :value="opt.value" />
            </el-select>
          </el-form-item>
          <el-form-item label="持续节数">
            <el-select v-model="sessionForm.periodCount">
              <el-option v-for="opt in periodCountOptions()" :key="opt.value" :label="opt.label" :value="opt.value" />
            </el-select>
          </el-form-item>
        </div>
        <div class="form-grid">
          <el-form-item label="教学楼限制">
            <el-select v-model="sessionForm.requiredBuildingId" clearable placeholder="不限教学楼">
              <el-option v-for="x in buildings" :key="x.id" :label="x.name" :value="x.id" />
            </el-select>
          </el-form-item>
          <el-form-item label="监考人数">
            <el-input-number v-model="sessionForm.requiredInvigilatorCount" :min="1" :max="10" />
          </el-form-item>
        </div>
        <el-form-item label="考场（可留空，由自动编排分配）">
          <el-select v-model="sessionForm.classroomId" clearable filterable placeholder="留空由自动编排分配">
            <el-option v-for="x in filteredRooms()" :key="x.id" :label="classroomLabel(x)" :value="x.id" />
          </el-select>
        </el-form-item>
        <el-form-item label="监考教师（可留空，由自动编排分配）">
          <el-select v-model="sessionForm.invigilatorIds" multiple filterable clearable placeholder="留空由自动编排分配">
            <el-option v-for="x in teachers" :key="x.id" :label="`${x.teacherNumber} · ${x.name}`" :value="x.id" />
          </el-select>
        </el-form-item>
        <el-form-item label="备注">
          <el-input v-model="sessionForm.notes" maxlength="500" placeholder="可选" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="sessionDialog = false">取消</el-button>
        <el-button type="primary" @click="saveSession">{{ editingSession ? '保存修改' : '保存场次' }}</el-button>
      </template>
    </el-dialog>

    <!-- Roster Drawer -->
    <el-drawer v-model="rosterDrawer" title="考生名单" size="600px">
      <el-table v-if="roster" :data="roster.students">
        <el-table-column prop="studentNumber" label="学号" width="120" />
        <el-table-column prop="name" label="姓名" width="80" />
        <el-table-column prop="className" label="行政班" width="130" />
        <el-table-column label="补考原因" width="90">
          <template #default="scope">
            <span>{{ scope.row.reason === 1 ? '不及格' : scope.row.reason === 2 ? '缺考' : scope.row.reason === 3 ? '缓考通过' : '-' }}</span>
          </template>
        </el-table-column>
        <el-table-column label="成绩" width="70">
          <template #default="scope">
            {{ scope.row.makeupScore ?? '-' }}
          </template>
        </el-table-column>
      </el-table>
    </el-drawer>

    <!-- Enrollment Dialog -->
    <el-dialog v-model="enrollmentDialog" title="登记补考学生" width="700px">
      <div v-if="enrollmentSession">
        <p><b>{{ enrollmentSession.courseName }}</b> · {{ dateOnlyText(enrollmentSession.examDate) }} {{ periodLabel(enrollmentSession) }}</p>
        <p style="margin-bottom: 12px">当前已登记：<b>{{ enrollmentSession.enrolledCount ?? enrollmentSession.studentCount }}</b> 人</p>
        <el-button type="primary" :icon="Search" @click="loadEligibleStudents" :loading="eligibleLoading" style="margin-bottom: 12px">查询补考资格</el-button>
        <el-table v-if="eligibleStudents.length" :data="eligibleStudents" @selection-change="onEnrollmentSelection" ref="enrollmentTable">
          <el-table-column type="selection" width="40" />
          <el-table-column prop="studentNumber" label="学号" width="120" />
          <el-table-column prop="name" label="姓名" width="80" />
          <el-table-column prop="className" label="行政班" width="140" />
          <el-table-column label="补考原因" width="100">
            <template #default="scope">
              <span>{{ scope.row.reason === 1 ? '不及格' : scope.row.reason === 2 ? '缺考' : '缓考通过' }}</span>
            </template>
          </el-table-column>
          <el-table-column label="详情" min-width="120">
            <template #default="scope">
              <span v-if="scope.row.reason === 1 && scope.row.totalScore != null">原始成绩 {{ scope.row.totalScore }}</span>
              <span v-else-if="scope.row.reason === 3">已批准缓考</span>
              <span v-else>缺考</span>
            </template>
          </el-table-column>
        </el-table>
        <el-empty v-else-if="!eligibleLoading" description="该教学班暂无需要补考的学生，或所有符合资格的学生均已登记。" />
      </div>
      <template #footer>
        <el-button @click="enrollmentDialog = false">取消</el-button>
        <el-button type="primary" @click="enrollSelectedStudents" :disabled="!selectedStudentIds.length">登记所选 {{ selectedStudentIds.length }} 名学生</el-button>
      </template>
    </el-dialog>

    <!-- Score Dialog (teacher) -->
    <el-dialog v-model="scoreDialogVisible" title="录入补考成绩" width="680px">
      <template v-if="scoreSession">
        <p style="margin-bottom: 12px"><b>{{ scoreSession.courseName }}</b> · {{ dateOnlyText(scoreSession.examDate) }} · {{ scoreSession.enrolledCount }} 名考生</p>
        <el-alert type="warning" title="补考合格按60分记入最终成绩" :closable="false" style="margin-bottom: 12px" />
        <el-table :data="scoreSession.enrollments" size="small">
          <el-table-column prop="studentNumber" label="学号" width="120" />
          <el-table-column prop="name" label="姓名" width="80" />
          <el-table-column label="补考原因" width="90">
            <template #default="scope">
              <span>{{ scope.row.reason === 1 ? '不及格' : scope.row.reason === 2 ? '缺考' : '缓考通过' }}</span>
            </template>
          </el-table-column>
          <el-table-column label="补考成绩" width="150">
            <template #default="scope">
              <el-input-number :model-value="scoreMap[scope.row.studentId]" @update:model-value="onScoreInput(scope.row.studentId, $event)" :min="0" :max="100" :precision="1" size="small" controls-position="right" placeholder="0-100" />
            </template>
          </el-table-column>
        </el-table>
      </template>
      <template #footer>
        <el-button @click="scoreDialogVisible = false">取消</el-button>
        <el-button type="primary" @click="submitScores" :loading="scoreSaving">保存成绩</el-button>
      </template>
    </el-dialog>
  </div>
</template>

<style scoped>
.exam-actions {
  display: flex;
  gap: 8px;
}
.exam-timeline article.unassigned {
  border-left-color: #e6a23c;
}
.enrollment-header {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.enrollment-header p {
  margin: 0;
}
</style>
