<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, reactive, ref } from 'vue'
import { Delete, Download, Plus, Promotion, Refresh, UserFilled, Setting } from '@element-plus/icons-vue'
import http, { apiErrorMessage } from '../api/http'
import { downloadApiFile } from '../api/excel'
import { useAuthStore } from '../stores/auth'
import { academicTermLabel, academicTermOptionClass, defaultAcademicTermId } from '../utils/academicTerms'

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 sessionPage = ref(1)
const sessionPageSize = ref(20)
const sessionTotal = ref(0)
const planSessionTotal = ref(0)
const sessionLoading = ref(false)
const personal = ref<any[]>([])
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 arrangementJob = ref<any | null>(null)
let arrangementPollTimer: ReturnType<typeof setTimeout> | null = null
const arrangementJobRunning = computed(() =>
  ['Queued', 'Running'].includes(arrangementJob.value?.status))
const arrangementProgress = computed(() =>
  arrangementJob.value?.status === 'Queued' ? 10
    : arrangementJob.value?.status === 'Running' ? 60
      : arrangementJob.value?.status === 'Succeeded' ? 100 : 0)
const exportLoading = ref(false)
const exportJob = ref<any | null>(null)
let exportPollTimer: ReturnType<typeof setTimeout> | null = null
const exportJobRunning = computed(() =>
  ['Queued', 'Running'].includes(exportJob.value?.status))
const removeLoading = ref(false)
const deletePlanLoading = 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 selectedTaskIds = ref<string[]>([])
const selectedSessionIds = ref<string[]>([])
const planForm = reactive<Record<string, any>>({})
const sessionForm = reactive<Record<string, any>>({})
const taskFilter = reactive({ keyword: '', collegeId: '', courseNature: '' })
const sessionFilter = reactive({ keyword: '', allocation: '' })
const statusLabels: Record<string, string> = {
  Draft: '草稿', Published: '已发布', Archived: '已归档',
}
const courseNatureLabels: Record<string, string> = {
  GeneralRequired: '公共必修',
  GeneralElective: '公共选修',
  MajorRequired: '专业必修',
  MajorElective: '专业选修',
  Practice: '实践教学',
}
const taskColleges = computed(() => {
  const values = new Map<string, string>()
  tasks.value.forEach((task: any) => values.set(task.collegeId, task.collegeName))
  return Array.from(values, ([id, name]) => ({ id, name }))
})
const filteredTasks = computed(() => {
  const keyword = taskFilter.keyword.trim().toLowerCase()
  const arrangedIds = new Set<string>(selected.value?.scheduledTeachingTaskIds ?? [])
  if (editingSession.value?.teachingTaskId) {
    arrangedIds.delete(editingSession.value.teachingTaskId)
  }
  return tasks.value.filter((task: any) => {
    if (arrangedIds.has(task.id)) return false
    if (taskFilter.collegeId && task.collegeId !== taskFilter.collegeId) return false
    if (taskFilter.courseNature && task.courseNature !== taskFilter.courseNature) return false
    if (!keyword) return true
    return [task.taskNumber, task.courseCode, task.courseName, task.name,
      ...(task.teacherNames ?? []), ...(task.classNames ?? [])]
      .some((value: any) => String(value ?? '').toLowerCase().includes(keyword))
  })
})
const filteredSessions = computed(() => selected.value?.sessions ?? [])

function sessionRooms(session: any) {
  return session.examRooms ?? []
}
function hasRoomAssignment(session: any) {
  return sessionRooms(session).length > 0 || Boolean(session.classroomId)
}
function sessionInvigilatorNames(session: any) {
  const roomAssignments = sessionRooms(session)
  const names = roomAssignments.length > 0
    ? roomAssignments.flatMap((room: any) => room.invigilatorNames)
    : session.invigilatorNames
  return Array.from(new Set(names))
}

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('/exams/my-schedule')).data
      return
    }
    plans.value = (await http.get('/exams/plans')).data
    const plan = plans.value.find((x) => x.id === selected.value?.id)
      ?? plans.value.find((x) => x.termIsCurrent)
      ?? plans.value[0]
    if (plan) await selectPlan(plan.id)
    else {
      selected.value = null
      selectedSessionIds.value = []
      sessionPage.value = 1
      sessionTotal.value = 0
      planSessionTotal.value = 0
      tasks.value = []
      timeSlots.value = []
    }
  } catch (error) { ElMessage.error(apiErrorMessage(error)) }
  finally { loading.value = false }
}
async function selectPlan(id: string) {
  stopArrangementPolling()
  stopExportPolling()
  exportJob.value = null
  sessionPage.value = 1
  selectedSessionIds.value = []
  await loadSelectedPlan(id)
  await Promise.all([
    loadPlanResources(selected.value.academicTermId),
    restoreArrangementJob(id),
  ])
}
async function loadSelectedPlan(id: string) {
  sessionLoading.value = true
  try {
    const { data } = await http.get(`/exams/plans/${id}`, {
      params: {
        page: sessionPage.value,
        pageSize: sessionPageSize.value,
        keyword: sessionFilter.keyword.trim() || undefined,
        allocation: sessionFilter.allocation || undefined,
      },
    })
    selected.value = data
    sessionPage.value = data.sessionPage
    sessionPageSize.value = data.sessionPageSize
    sessionTotal.value = data.filteredSessionCount
    planSessionTotal.value = data.totalSessionCount
    const planSummary = plans.value.find((plan: any) => plan.id === data.id)
    if (planSummary) planSummary.sessionCount = data.totalSessionCount
  } finally {
    sessionLoading.value = false
  }
}
async function reloadSelectedPlan() {
  if (!selected.value) return
  await loadSelectedPlan(selected.value.id)
}
async function applySessionFilters() {
  if (!selected.value) return
  sessionPage.value = 1
  try {
    await reloadSelectedPlan()
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  }
}
async function changeSessionPage(page: number) {
  sessionPage.value = page
  try {
    await reloadSelectedPlan()
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  }
}
async function changeSessionPageSize(pageSize: number) {
  sessionPageSize.value = pageSize
  sessionPage.value = 1
  try {
    await reloadSelectedPlan()
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  }
}
async function loadPlanResources(academicTermId: string) {
  const [taskRes, slotRes] = await Promise.all([
    http.get('/teaching-tasks/options', {
      params: { academicTermId, status: 'Published' },
    }),
    http.get('/exams/time-slots-for-term', { params: { academicTermId } }),
  ])
  tasks.value = taskRes.data
  timeSlots.value = slotRes.data
}
function openPlan() {
  Object.assign(planForm, {
    academicTermId: defaultAcademicTermId(terms.value),
    name: '', notes: '',
  })
  planDialog.value = true
}
async function savePlan() {
  try {
    await http.post('/exams/plans', planForm)
    planDialog.value = false
    await load()
  } catch (error) { ElMessage.error(apiErrorMessage(error)) }
}
async function openSession(existing?: any) {
  if (existing && sessionRooms(existing).length > 0) {
    try {
      await ElMessageBox.confirm(
        '修改该教学班的课程或考试时间会清除相关混排考场和座位，需要重新执行考场编排。',
        '修改混排场次',
        { type: 'warning', confirmButtonText: '继续修改' },
      )
    } catch {
      return
    }
  }
  editingSession.value = existing ?? null
  selectedTaskIds.value = existing ? [existing.teachingTaskId] : []
  Object.assign(taskFilter, { keyword: '', collegeId: '', courseNature: '' })
  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,
    requiredBuildingIds: parseBuildingIds(existing?.requiredBuildingIds),
    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(`/exams/plans/${selected.value.id}/sessions/${editingSession.value.id}`, payload)
    } else {
      if (selectedTaskIds.value.length === 0) {
        ElMessage.warning('请至少选择一个教学班')
        return
      }
      const res = await http.post(`/exams/plans/${selected.value.id}/sessions/batch`, {
        teachingTaskIds: selectedTaskIds.value,
        examDate: payload.examDate,
        startPeriod: payload.startPeriod,
        periodCount: payload.periodCount,
        requiredBuildingId: payload.requiredBuildingId,
        requiredBuildingIds: payload.requiredBuildingIds,
        requiredInvigilatorCount: payload.requiredInvigilatorCount,
        notes: payload.notes,
      })
      ElMessage.success(`已批量创建 ${res.data.createdCount} 个考试场次`)
    }
    sessionDialog.value = false
    editingSession.value = null
    await reloadSelectedPlan()
  } catch (error) { ElMessage.error(apiErrorMessage(error)) }
}
async function removeSession(row: any) {
  try {
    await ElMessageBox.confirm(`移除"${row.courseName}"考试场次？`, '移除场次', { type: 'warning' })
    await http.delete(`/exams/plans/${selected.value.id}/sessions/${row.id}`)
    selectedSessionIds.value = selectedSessionIds.value.filter(id => id !== row.id)
    await reloadSelectedPlan()
  } catch (error: any) {
    if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error))
  }
}
async function removeSelectedSessions() {
  if (!selected.value || selectedSessionIds.value.length === 0) return
  const count = selectedSessionIds.value.length
  if (count > 100) {
    ElMessage.warning('一次最多移除100个考试场次，请分批选择')
    return
  }
  try {
    await ElMessageBox.confirm(
      `确定从当前考试计划移除已选的 ${count} 个考试场次吗？`,
      '批量移除考试场次',
      {
        type: 'warning',
        confirmButtonText: '确认移除',
      },
    )
    removeLoading.value = true
    const res = await http.post(
      `/exams/plans/${selected.value.id}/sessions/batch-remove`,
      { sessionIds: selectedSessionIds.value },
    )
    ElMessage.success(`已移除 ${res.data.removedCount} 个考试场次`)
    selectedSessionIds.value = []
    await reloadSelectedPlan()
  } catch (error: any) {
    if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error))
  } finally {
    removeLoading.value = false
  }
}
function selectFilteredTasks() {
  selectedTaskIds.value = Array.from(new Set([
    ...selectedTaskIds.value,
    ...filteredTasks.value.map((task: any) => task.id),
  ])).slice(0, 100)
}
function clearFilteredTasks() {
  const visibleIds = new Set(filteredTasks.value.map((task: any) => task.id))
  selectedTaskIds.value = selectedTaskIds.value.filter(id => !visibleIds.has(id))
}
function toggleTaskSelection(id: string, checked: boolean) {
  if (checked && selectedTaskIds.value.length >= 100) {
    ElMessage.warning('一次最多选择100个教学班')
    return
  }
  selectedTaskIds.value = checked
    ? Array.from(new Set([...selectedTaskIds.value, id]))
    : selectedTaskIds.value.filter(value => value !== id)
}
function selectFilteredSessions() {
  const selectedIds = Array.from(new Set([
    ...selectedSessionIds.value,
    ...filteredSessions.value.map((session: any) => session.id),
  ]))
  if (selectedIds.length > 100) {
    ElMessage.warning('一次最多选择100个考试场次')
  }
  selectedSessionIds.value = selectedIds.slice(0, 100)
}
function clearSessionSelection() {
  selectedSessionIds.value = []
}
function toggleSessionSelection(id: string, checked: boolean) {
  selectedSessionIds.value = checked
    ? Array.from(new Set([...selectedSessionIds.value, id]))
    : selectedSessionIds.value.filter(value => value !== id)
}
async function autoArrange(mode: 'rooms' | 'invigilators' | 'all') {
  try {
    const target = selectedSessionIds.value.length
      ? `所选 ${selectedSessionIds.value.length} 个场次`
      : '当前计划全部场次'
    const action = mode === 'rooms' ? '分配考场'
      : mode === 'invigilators' ? '分配监考教师' : '分配考场和监考教师'
    const arrangementNotice = mode === 'invigilators'
      ? '已有考场和座位不会改变。'
      : '系统会按同一课程、同一时间生成完整混排组，并重建相关考场和座位。'
    await ElMessageBox.confirm(
      `系统将为${target}${action}。${arrangementNotice}`,
      `一键${action}`, { type: 'info', confirmButtonText: '开始分配' })
    arrangeLoading.value = true
    const res = await http.post(`/exams/plans/${selected.value.id}/auto-arrange`, {
      sessionIds: selectedSessionIds.value,
      assignClassrooms: mode !== 'invigilators',
      assignInvigilators: mode !== 'rooms',
    })
    arrangementJob.value = {
      id: res.data.jobId,
      planId: selected.value.id,
      status: res.data.status,
      currentStep: '等待后台编排',
    }
    ElMessage.success(res.data.message)
    scheduleArrangementPoll(true)
  } catch (error: any) {
    if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error))
  } finally { arrangeLoading.value = false }
}
async function restoreArrangementJob(planId: string) {
  try {
    const res = await http.get(`/exams/plans/${planId}/arrangement-job`)
    if (res.status === 204 || !res.data ||
      !['Queued', 'Running'].includes(res.data.status)) {
      arrangementJob.value = null
      return
    }
    arrangementJob.value = res.data
    scheduleArrangementPoll(false)
  } catch {
    arrangementJob.value = null
  }
}
function scheduleArrangementPoll(notifyTerminal: boolean) {
  stopArrangementPolling()
  arrangementPollTimer = setTimeout(
    () => pollArrangementJob(notifyTerminal),
    1500,
  )
}
async function pollArrangementJob(notifyTerminal: boolean) {
  if (!arrangementJob.value?.id) return
  const jobId = arrangementJob.value.id
  const planId = selected.value?.id
  try {
    const { data: job } = await http.get(
      `/exams/arrangement-jobs/${jobId}`,
    )
    if (selected.value?.id !== planId ||
      arrangementJob.value?.id !== jobId) return
    arrangementJob.value = job
    if (['Queued', 'Running'].includes(job.status)) {
      scheduleArrangementPoll(notifyTerminal)
      return
    }
    stopArrangementPolling()
    if (job.status === 'Succeeded') {
      selectedSessionIds.value = []
      await reloadSelectedPlan()
      if (notifyTerminal) ElMessage.success(job.resultMessage || '考试编排完成')
    } else if (notifyTerminal) {
      ElMessage.error(job.errorMessage || '考试编排失败')
    }
  } catch {
    if (selected.value?.id === planId &&
      arrangementJob.value?.id === jobId) {
      scheduleArrangementPoll(notifyTerminal)
    }
  }
}
function stopArrangementPolling() {
  if (arrangementPollTimer) {
    clearTimeout(arrangementPollTimer)
    arrangementPollTimer = null
  }
}
async function publishPlan() {
  try {
    await ElMessageBox.confirm('发布后考试时间、考场与监考安排将锁定。', '发布考试计划', {
      type: 'warning', confirmButtonText: '确认发布',
    })
    await http.post(`/exams/plans/${selected.value.id}/publish`)
    await load()
  } catch (error: any) {
    if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error))
  }
}
async function deleteDraftPlan() {
  if (!selected.value || selected.value.status !== 'Draft') return
  try {
    await ElMessageBox.confirm(
      `确定删除草稿考试计划“${selected.value.name}”吗？计划内 ${planSessionTotal.value} 个考试场次也会一并删除，此操作无法撤销。`,
      '删除考试计划',
      {
        type: 'warning',
        confirmButtonText: '删除草稿',
      },
    )
    deletePlanLoading.value = true
    await http.delete(`/exams/plans/${selected.value.id}`)
    ElMessage.success('草稿考试计划已删除')
    selected.value = null
    selectedSessionIds.value = []
    await load()
  } catch (error: any) {
    if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error))
  } finally {
    deletePlanLoading.value = false
  }
}
async function exportSignInSheets() {
  if (!selected.value) return
  exportLoading.value = true
  try {
    const res = await http.post(`/exams/plans/${selected.value.id}/sign-in-export`)
    exportJob.value = {
      id: res.data.jobId,
      planId: selected.value.id,
      status: res.data.status,
      currentStep: '等待后台生成',
    }
    scheduleExportPoll(true)
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  } finally {
    exportLoading.value = false
  }
}
function scheduleExportPoll(notifyTerminal: boolean) {
  stopExportPolling()
  exportPollTimer = setTimeout(
    () => pollExportJob(notifyTerminal),
    2000,
  )
}
async function pollExportJob(notifyTerminal: boolean) {
  if (!exportJob.value?.id) return
  const jobId = exportJob.value.id
  const planId = selected.value?.id
  try {
    const { data: job } = await http.get(`/exams/sign-in-exports/${jobId}`)
    if (selected.value?.id !== planId || exportJob.value?.id !== jobId) return
    exportJob.value = job
    if (['Queued', 'Running'].includes(job.status)) {
      scheduleExportPoll(notifyTerminal)
      return
    }
    stopExportPolling()
    if (job.status === 'Succeeded') {
      await downloadApiFile(
        `/exams/sign-in-exports/${jobId}/download`,
        job.fileName || `考场签名单.xlsx`,
      )
      if (notifyTerminal) ElMessage.success('考场签名单已导出')
    } else if (notifyTerminal) {
      ElMessage.error(job.errorMessage || '考场签名单导出失败')
    }
  } catch {
    if (selected.value?.id === planId && exportJob.value?.id === jobId) {
      scheduleExportPoll(notifyTerminal)
    }
  }
}
function stopExportPolling() {
  if (exportPollTimer) {
    clearTimeout(exportPollTimer)
    exportPollTimer = null
  }
}
async function showRoster(row: any) {
  try {
    roster.value = (await http.get(`/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.requiredBuildingIds || sessionForm.requiredBuildingIds.length === 0) return rooms.value
  return rooms.value.filter((r: any) => sessionForm.requiredBuildingIds.includes(r.buildingId))
}
function parseBuildingIds(raw: string | undefined | null): string[] {
  if (!raw) return []
  try { return JSON.parse(raw) } catch { return [] }
}

onMounted(async () => {
  try {
    if (isManager.value) {
      const [termRes, roomRes, teacherRes, buildingRes] = await Promise.all([
        http.get('/base-data/terms'),
        http.get('/base-data/classrooms'),
        http.get('/personnel/teachers', { params: { page: 1, pageSize: 200, teacherStatus: 'Active' } }),
        http.get('/base-data/buildings'),
      ])
      terms.value = termRes.data
      rooms.value = roomRes.data
      teachers.value = teacherRes.data.items
      buildings.value = buildingRes.data
    }
    await load()
  } catch (error) { ElMessage.error(apiErrorMessage(error)) }
})
onBeforeUnmount(() => {
  stopArrangementPolling()
  stopExportPolling()
})
</script>

<template>
  <div class="page-stack exam-page">
    <section class="page-intro">
      <div>
        <span class="section-kicker">EXAMINATION OFFICE</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, 'historical-record': !plan.termIsCurrent && !plan.termIsArchived, 'archived-record': plan.termIsArchived }" @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>EXAM TIMELINE</span>
            <h3>{{ selected.name }}</h3>
            <p>{{ selected.termName }} · {{ planSessionTotal }} 个考试场次</p>
          </div>
          <div class="exam-actions">
            <el-button
              :icon="Download"
              :loading="exportLoading"
              :disabled="planSessionTotal === 0"
              @click="exportSignInSheets"
            >导出考场签名单</el-button>
            <el-button v-if="selected.status === 'Draft'" @click="autoArrange('rooms')" :loading="arrangeLoading" :disabled="arrangementJobRunning">一键分配考场</el-button>
            <el-button v-if="selected.status === 'Draft'" @click="autoArrange('invigilators')" :loading="arrangeLoading" :disabled="arrangementJobRunning">一键分配监考</el-button>
            <el-button v-if="selected.status === 'Draft'" :icon="Setting" @click="autoArrange('all')" :loading="arrangeLoading" :disabled="arrangementJobRunning">一键完成</el-button>
            <el-button v-if="selected.status === 'Draft'" :icon="Plus" :disabled="arrangementJobRunning" @click="openSession()">批量安排场次</el-button>
            <el-button v-if="selected.status === 'Draft'" type="primary" :icon="Promotion" :disabled="arrangementJobRunning" @click="publishPlan">发布计划</el-button>
            <el-button
              v-if="selected.status === 'Draft'"
              type="danger"
              plain
              :icon="Delete"
              :loading="deletePlanLoading"
              :disabled="arrangementJobRunning"
              @click="deleteDraftPlan"
            >删除草稿</el-button>
          </div>
        </header>
        <el-alert
          v-if="arrangementJob"
          :title="arrangementJob.status === 'Failed' ? '后台编排失败' : arrangementJob.status === 'Succeeded' ? '后台编排完成' : '后台正在编排考试'"
          :type="arrangementJob.status === 'Failed' ? 'error' : arrangementJob.status === 'Succeeded' ? 'success' : 'info'"
          :closable="false"
          show-icon
          style="margin-bottom: 16px"
        >
          <template #default>
            <p>{{ arrangementJob.errorMessage || arrangementJob.resultMessage || arrangementJob.currentStep || '等待后台任务处理' }}</p>
            <el-progress
              v-if="arrangementJobRunning"
              :percentage="arrangementProgress"
              :show-text="false"
              :stroke-width="6"
              :indeterminate="arrangementJob.status === 'Running'"
            />
          </template>
        </el-alert>
        <el-alert
          v-if="exportJob"
          :title="exportJob.status === 'Failed' ? '签名单导出失败' : exportJob.status === 'Succeeded' ? '签名单导出完成' : '正在生成考场签名单'"
          :type="exportJob.status === 'Failed' ? 'error' : exportJob.status === 'Succeeded' ? 'success' : 'info'"
          :closable="false"
          show-icon
          style="margin-bottom: 16px"
        >
          <template #default>
            <p>{{ exportJob.errorMessage || exportJob.currentStep || '等待后台任务处理' }}</p>
            <el-progress
              v-if="exportJobRunning"
              :percentage="exportJob.status === 'Running' ? 60 : 10"
              :show-text="false"
              :stroke-width="6"
              :indeterminate="exportJob.status === 'Running'"
            />
          </template>
        </el-alert>
        <div class="exam-filter-bar">
          <el-input
            v-model="sessionFilter.keyword"
            clearable
            placeholder="筛选课程、课程号或教学班号"
            @keyup.enter="applySessionFilters"
            @clear="applySessionFilters"
          />
          <el-select
            v-model="sessionFilter.allocation"
            clearable
            placeholder="全部分配状态"
            @change="applySessionFilters"
          >
            <el-option label="待分配考场" value="room" />
            <el-option label="待补足监考" value="invigilator" />
            <el-option label="已完成分配" value="complete" />
          </el-select>
          <el-button :loading="sessionLoading" @click="applySessionFilters">查询</el-button>
          <span>
            本页 {{ filteredSessions.length }} 个，共 {{ sessionTotal }} 个符合条件
            <template v-if="sessionTotal !== planSessionTotal">
              （计划共 {{ planSessionTotal }} 个）
            </template>
          </span>
          <template v-if="selected.status === 'Draft'">
            <el-button link type="primary" @click="selectFilteredSessions">选择本页</el-button>
            <el-button link @click="clearSessionSelection">清空选择</el-button>
            <el-tag v-if="selectedSessionIds.length" type="info">已选 {{ selectedSessionIds.length }} 个</el-tag>
            <el-button
              type="danger"
              plain
              :icon="Delete"
              :loading="removeLoading"
              :disabled="selectedSessionIds.length === 0"
              @click="removeSelectedSessions"
            >批量移除已选场次</el-button>
          </template>
        </div>
        <div class="exam-timeline" v-loading="sessionLoading">
          <article v-for="session in filteredSessions" :key="session.id" :class="{ unassigned: !hasRoomAssignment(session) }">
            <time>
              <el-checkbox
                v-if="selected.status === 'Draft'"
                :model-value="selectedSessionIds.includes(session.id)"
                @change="toggleSessionSelection(session.id, Boolean($event))"
              >选择</el-checkbox>
              <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="sessionRooms(session).length">
                  <span v-for="room in sessionRooms(session)" :key="room.examRoomId" class="exam-room-chip">
                    {{ room.buildingName }} · {{ room.classroomName }}
                    · 本班{{ room.seatCount }}人/全场{{ room.totalSeatCount }}人
                    <el-tag v-if="room.isMixed" size="small" type="success">混排</el-tag>
                  </span>
                </template>
                <template v-else-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>{{ sessionInvigilatorNames(session).length ? sessionInvigilatorNames(session).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="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="planSessionTotal === 0"
            description="尚未安排考试场次，点击「批量安排场次」开始。"
          />
          <el-empty
            v-else-if="sessionTotal === 0"
            description="没有符合筛选条件的考试场次"
          />
        </div>
        <el-pagination
          v-if="sessionTotal > 0"
          class="exam-pagination"
          background
          :current-page="sessionPage"
          :page-size="sessionPageSize"
          :page-sizes="[10, 20, 50, 100]"
          :total="sessionTotal"
          layout="total, sizes, prev, pager, next"
          @current-change="changeSessionPage"
          @size-change="changeSessionPageSize"
        />
      </section>
    </template>

    <section v-else 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>
          <el-tag v-if="!isTeacher && item.seatNumber" size="small" type="success">座位号 {{ item.seatNumber }}</el-tag>
        </div>
        <footer>
          <el-icon><UserFilled /></el-icon>
          {{ isTeacher ? `${item.studentCount} 名考生` : `监考：${item.invigilatorNames.join('、') || '待定'}` }}
        </footer>
      </article>
      <el-empty v-if="!personal.length" description="暂无已发布考试安排" />
    </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="academicTermLabel(x)" :value="x.id" :class="academicTermOptionClass(x)" />
          </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="960px" top="3vh">
      <el-form label-position="top">
        <el-form-item v-if="editingSession" 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>
        <template v-else>
          <div class="task-filter-grid">
            <el-input v-model="taskFilter.keyword" clearable placeholder="课程、教学班、教师或行政班" />
            <el-select v-model="taskFilter.collegeId" clearable placeholder="全部开课单位">
              <el-option v-for="x in taskColleges" :key="x.id" :label="x.name" :value="x.id" />
            </el-select>
            <el-select v-model="taskFilter.courseNature" clearable placeholder="全部课程性质">
              <el-option v-for="(label, value) in courseNatureLabels" :key="value" :label="label" :value="value" />
            </el-select>
          </div>
          <div class="task-selection-actions">
            <span>可选 {{ filteredTasks.length }} 个，已选 {{ selectedTaskIds.length }} 个</span>
            <el-button link type="primary" @click="selectFilteredTasks">选择全部筛选结果</el-button>
            <el-button link @click="clearFilteredTasks">清除筛选结果选择</el-button>
          </div>
          <el-table :data="filteredTasks" height="260" size="small">
            <el-table-column label="选择" width="64">
              <template #default="scope">
                <el-checkbox
                  :model-value="selectedTaskIds.includes(scope.row.id)"
                  @click.stop
                  @update:model-value="toggleTaskSelection(scope.row.id, Boolean($event))"
                />
              </template>
            </el-table-column>
            <el-table-column prop="taskNumber" label="教学班号" width="130" />
            <el-table-column prop="courseName" label="课程" min-width="150" />
            <el-table-column prop="collegeName" label="开课单位" min-width="130" />
            <el-table-column label="任课教师" min-width="120">
              <template #default="scope">{{ scope.row.teacherNames?.join('、') || '待定' }}</template>
            </el-table-column>
            <el-table-column label="行政班" min-width="150">
              <template #default="scope">{{ scope.row.classNames?.join('、') || '-' }}</template>
            </el-table-column>
          </el-table>
        </template>
        <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.requiredBuildingIds" multiple 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 v-if="editingSession" 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 v-if="editingSession" 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 ? '保存修改' : `批量创建 ${selectedTaskIds.length} 个场次` }}
        </el-button>
      </template>
    </el-dialog>

    <!-- Roster Drawer -->
    <el-drawer v-model="rosterDrawer" title="考生名单" size="560px">
      <el-table v-if="roster" :data="roster.students">
        <el-table-column prop="buildingName" label="教学楼" width="110" />
        <el-table-column prop="classroomName" label="考场" width="90" />
        <el-table-column prop="seatNumber" label="座位号" width="80" />
        <el-table-column prop="studentNumber" label="学号" width="130" />
        <el-table-column prop="name" label="姓名" width="90" />
        <el-table-column prop="className" label="行政班" />
      </el-table>
    </el-drawer>
  </div>
</template>

<style scoped>
.exam-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  justify-content: flex-end;
}
.exam-filter-bar,
.task-filter-grid,
.task-selection-actions {
  display: flex;
  align-items: center;
  gap: 10px;
}
.exam-filter-bar {
  margin-bottom: 16px;
  flex-wrap: wrap;
}
.exam-filter-bar .el-input {
  width: min(320px, 100%);
}
.exam-filter-bar .el-select {
  width: 180px;
}
.task-filter-grid {
  display: grid;
  grid-template-columns: minmax(240px, 1.5fr) minmax(160px, 1fr) minmax(160px, 1fr);
  margin-bottom: 10px;
}
.task-selection-actions {
  justify-content: flex-end;
  margin-bottom: 8px;
}
.task-selection-actions span {
  margin-right: auto;
}
.exam-timeline article.unassigned {
  border-left-color: #e6a23c;
}
.exam-room-chip {
  display: block;
  margin: 3px 0;
}
.exam-pagination {
  justify-content: flex-end;
  margin-top: 18px;
}
@media (max-width: 720px) {
  .exam-actions {
    justify-content: flex-start;
  }
  .task-filter-grid {
    grid-template-columns: 1fr;
  }
  .task-selection-actions {
    align-items: flex-start;
    flex-direction: column;
  }
  .exam-pagination {
    justify-content: center;
  }
}
</style>
