<script setup lang="ts">
import { computed, onMounted, reactive, ref } from 'vue'
import {
  Calendar,
  CircleCheck,
  Clock,
  Plus,
  Refresh,
  Search,
  Tickets,
  UserFilled,
} from '@element-plus/icons-vue'
import http, { apiErrorMessage } from '../api/http'
import { useAuthStore } from '../stores/auth'
import { academicTermLabel, academicTermOptionClass, defaultAcademicTermId } from '../utils/academicTerms'

const auth = useAuthStore()
const managerRoles = ['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin']
const isManager = computed(() =>
  auth.user?.roles.some((role) => managerRoles.includes(role)) ?? false,
)
const canManageRounds = computed(() =>
  auth.user?.roles.some((role) => ['SuperAdmin', 'AcademicAdmin'].includes(role)) ?? false,
)
const isStudent = computed(() => auth.user?.roles.includes('Student') && !isManager.value)
const terms = ref<any[]>([])
const gradeOptions = ref<number[]>([])
const rounds = ref<any[]>([])
const selectedRound = ref<any | null>(null)
const offerings = ref<any[]>([])
const tasks = ref<any[]>([])
const enrollments = ref<any[]>([])
const loading = ref(false)
const detailLoading = ref(false)
const roundDialog = ref(false)
const offeringDialog = ref(false)
const rosterDrawer = ref(false)
const proxyDialog = ref(false)
const editingRoundId = ref('')
const editingOfferingId = ref('')
const roster = ref<any | null>(null)
const rosterLoading = ref(false)
const rosterKeyword = ref('')
const rosterGrade = ref<number | undefined>()
const rosterMajorId = ref<string | undefined>()
const rosterClassId = ref<string | undefined>()
const rosterStudentPage = ref(1)
const rosterWaitlistPage = ref(1)
const rosterPageSize = 20
const eligibleStudents = ref<any[]>([])
const eligibleTotal = ref(0)
const eligiblePage = ref(1)
const eligibleLoading = ref(false)
const proxySubmitting = ref(false)
const studentKeyword = ref('')
const selectedStudentIds = ref<string[]>([])
const offeringKeyword = ref('')
const offeringStatus = ref('All')
const previewOfferingId = ref('')
const roundForm = reactive<Record<string, any>>({})
const offeringForm = reactive<Record<string, any>>({})

const statusLabels: Record<string, string> = {
  Draft: '草稿',
  Open: '开放中',
  Closed: '已关闭',
}
const patternLabels: Record<string, string> = {
  All: '每周',
  Odd: '单周',
  Even: '双周',
}
const weekdayLabels = ['', '周一', '周二', '周三', '周四', '周五', '周六', '周日']
const selectedCredits = computed(() =>
  offerings.value
    .filter((item) => item.enrollmentStatus === 'Enrolled')
    .reduce((sum, item) => sum + Number(item.credits), 0),
)
const creditPercent = computed(() => {
  const maximum = Number(selectedRound.value?.maxCredits || 1)
  return Math.min(100, Math.round((selectedCredits.value / maximum) * 100))
})
const selectedCount = computed(() =>
  offerings.value.filter((item) => item.enrollmentStatus === 'Enrolled').length,
)
const selectedOfferings = computed(() =>
  offerings.value.filter((item) => item.enrollmentStatus === 'Enrolled'),
)
const previewOffering = computed(() =>
  offerings.value.find((item) => item.id === previewOfferingId.value) ?? null,
)
const rosterClasses = computed(() => (roster.value?.filterOptions?.classes ?? [])
  .filter((item: any) => !rosterMajorId.value || item.majorId === rosterMajorId.value))
const selectableTasks = computed(() => {
  const usedTaskIds = new Set(
    offerings.value
      .filter((item) => item.id !== editingOfferingId.value)
      .map((item) => item.teachingTaskId),
  )
  return tasks.value.filter((item) => !usedTaskIds.has(item.id))
})

function includesWeek(schedule: any, week: number) {
  return schedule.weekPattern === 'All'
    || schedule.weekPattern === 'Odd' && week % 2 === 1
    || schedule.weekPattern === 'Even' && week % 2 === 0
}

function schedulesOverlap(first: any, second: any) {
  if (first.dayOfWeek !== second.dayOfWeek) return false
  const periodsOverlap = first.startPeriod < second.startPeriod + second.periodCount
    && second.startPeriod < first.startPeriod + first.periodCount
  if (!periodsOverlap) return false
  const startWeek = Math.max(first.startWeek, second.startWeek)
  const endWeek = Math.min(first.endWeek, second.endWeek)
  for (let week = startWeek; week <= endWeek; week += 1) {
    if (includesWeek(first, week) && includesWeek(second, week)) return true
  }
  return false
}

function conflictingSelectedCourses(offering: any) {
  if (offering.enrollmentStatus === 'Enrolled' || offering.isFlexible) return []
  return selectedOfferings.value
    .filter((selected) =>
      selected.id !== offering.id
      && (selected.schedules ?? []).some((existing: any) =>
        (offering.schedules ?? []).some((candidate: any) =>
          schedulesOverlap(candidate, existing),
        ),
      ),
    )
    .map((item) => item.courseName)
}

function offeringEligibilityReason(offering: any) {
  if (['Enrolled', 'Waitlisted'].includes(offering.enrollmentStatus)) return ''
  if (!selectedRound.value?.isAvailableNow) return '当前不在选课开放时间内'
  if (!offering.isFlexible && !(offering.schedules ?? []).length) return '正式课表尚未发布'
  if (!offering.isRetake && selectedOfferings.value.some((item) =>
    item.id !== offering.id && item.courseCode === offering.courseCode,
  )) return '本学期已选择同一课程'
  if (selectedRound.value.maxCourseCount
    && selectedCount.value >= Number(selectedRound.value.maxCourseCount)) {
    return `已达到本轮最多 ${selectedRound.value.maxCourseCount} 门课程限制`
  }
  if (selectedCredits.value + Number(offering.credits) > Number(selectedRound.value.maxCredits)) {
    return `选择后将超过 ${selectedRound.value.maxCredits} 学分上限`
  }
  const conflicts = conflictingSelectedCourses(offering)
  if (conflicts.length) {
    if (!offering.isRetake) return `与已选”${conflicts.join('、')}”时间冲突`
    // Retake: calculate overlap (client-side rough estimate)
    if (!calcRetakeOverlapOk(offering)) return `重修时间冲突超过 50%，无法选课`
    return '' // retake with acceptable overlap
  }
  return ''
}

function effectiveCapacity(offering: any) {
  return offering.isRetake
    ? Math.ceil(offering.capacity * 1.15)
    : offering.capacity
}

function isOfferingFull(offering: any) {
  return offering.enrolledCount >= effectiveCapacity(offering)
}

function offeringBlockReason(offering: any) {
  const eligibilityReason = offeringEligibilityReason(offering)
  if (eligibilityReason) return eligibilityReason
  if (isOfferingFull(offering)) return '教学班名额已满，可加入候补'
  return ''
}

function calcRetakeOverlapOk(offering: any): boolean {
  let totalPeriods = 0, overlapPeriods = 0
  for (const candidate of (offering.schedules ?? [])) {
    totalPeriods += candidate.periodCount
    for (const selected of selectedOfferings.value) {
      for (const existing of (selected.schedules ?? [])) {
        if (candidate.dayOfWeek !== existing.dayOfWeek) continue
        const overlapStart = Math.max(candidate.startPeriod, existing.startPeriod)
        const overlapEnd = Math.min(
          candidate.startPeriod + candidate.periodCount,
          existing.startPeriod + existing.periodCount,
        )
        if (overlapEnd > overlapStart) overlapPeriods += overlapEnd - overlapStart
      }
    }
  }
  return totalPeriods === 0 || (overlapPeriods / totalPeriods * 100) <= 50
}

const filteredOfferings = computed(() => {
  const keyword = offeringKeyword.value.trim().toLocaleLowerCase()
  return offerings.value.filter((offering) => {
    const matchesKeyword = !keyword || [
      offering.courseCode,
      offering.courseName,
      offering.taskNumber,
      ...(offering.teacherNames ?? []),
    ].some((value) => String(value ?? '').toLocaleLowerCase().includes(keyword))
    if (!matchesKeyword) return false
    if (offeringStatus.value === 'Selected') {
      return offering.enrollmentStatus === 'Enrolled'
    }
    if (offeringStatus.value === 'Waitlisted') {
      return offering.enrollmentStatus === 'Waitlisted'
    }
    const blocked = Boolean(offeringEligibilityReason(offering))
    if (offeringStatus.value === 'Selectable') {
      return !['Enrolled', 'Waitlisted'].includes(offering.enrollmentStatus) && !blocked
    }
    if (offeringStatus.value === 'Blocked') {
      return !['Enrolled', 'Waitlisted'].includes(offering.enrollmentStatus) && blocked
    }
    return true
  })
})

const timetableOfferings = computed(() => {
  const result = [...selectedOfferings.value]
  if (previewOffering.value
    && previewOffering.value.enrollmentStatus !== 'Enrolled') {
    result.push(previewOffering.value)
  }
  return result
})

const timetablePeriodCount = computed(() => Math.max(
  12,
  ...timetableOfferings.value.flatMap((offering) =>
    offering.schedules.map((schedule: any) =>
      schedule.startPeriod + schedule.periodCount - 1,
    ),
  ),
))
const timetablePeriods = computed(() =>
  Array.from({ length: timetablePeriodCount.value }, (_, index) => index + 1),
)
const timetableCells = computed(() =>
  timetablePeriods.value.flatMap((period) =>
    Array.from({ length: 7 }, (_, index) => ({
      key: `${index + 1}-${period}`,
      dayOfWeek: index + 1,
      period,
    })),
  ),
)
const timetableGroups = computed(() => {
  const entriesByDay = new Map<number, any[]>()
  timetableOfferings.value.forEach((offering, offeringIndex) => {
    offering.schedules.forEach((schedule: any) => {
      const dayEntries = entriesByDay.get(schedule.dayOfWeek) ?? []
      dayEntries.push({
        ...schedule,
        courseName: offering.courseName,
        taskNumber: offering.taskNumber,
        isPreview: offering.id === previewOfferingId.value,
        hasConflict: offering.id === previewOfferingId.value
          && conflictingSelectedCourses(offering).length > 0,
        tone: offeringIndex % 5,
      })
      entriesByDay.set(schedule.dayOfWeek, dayEntries)
    })
  })
  const groups: any[] = []
  entriesByDay.forEach((entries, dayOfWeek) => {
    entries
      .sort((first, second) =>
        first.startPeriod - second.startPeriod
        || first.periodCount - second.periodCount,
      )
      .forEach((entry) => {
        const endPeriod = entry.startPeriod + entry.periodCount
        const current = groups.at(-1)
        if (current
          && current.dayOfWeek === dayOfWeek
          && entry.startPeriod < current.endPeriod) {
          current.endPeriod = Math.max(current.endPeriod, endPeriod)
          current.periodCount = current.endPeriod - current.startPeriod
          current.entries.push(entry)
          return
        }
        groups.push({
          key: `${dayOfWeek}-${entry.startPeriod}-${groups.length}`,
          dayOfWeek,
          startPeriod: entry.startPeriod,
          endPeriod,
          periodCount: entry.periodCount,
          entries: [entry],
        })
      })
  })
  return groups
})
const flexibleTimetableOfferings = computed(() =>
  timetableOfferings.value.filter((offering) => offering.isFlexible),
)
const previewConflictNames = computed(() =>
  previewOffering.value
    ? conflictingSelectedCourses(previewOffering.value)
    : [],
)

function formatDateTime(value: string) {
  if (!value) return '—'
  return new Intl.DateTimeFormat('zh-CN', {
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
    hour12: false,
  }).format(new Date(value))
}

function roundGradeLabel(round: any) {
  const grades = (round?.eligibleGrades ?? []).map(Number)
  return grades.length ? grades.map((grade: number) => `${grade} 级`).join('、') : '全部年级'
}

function roundLimitLabel(round: any) {
  const courseLimit = round?.maxCourseCount
    ? `最多 ${round.maxCourseCount} 门`
    : '课程门数不限'
  return `${roundGradeLabel(round)} · ${courseLimit} · 最多 ${round.maxCredits} 学分`
}

function toPickerValue(value: string) {
  if (!value) return ''
  const date = new Date(value)
  const pad = (number: number) => String(number).padStart(2, '0')
  return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:00`
}

function toIso(value: string) {
  return new Date(value.replace(' ', 'T')).toISOString()
}

function formatSchedule(schedule: any) {
  const periods = schedule.periodCount === 1
    ? `第 ${schedule.startPeriod} 节`
    : `第 ${schedule.startPeriod}—${schedule.startPeriod + schedule.periodCount - 1} 节`
  return `${weekdayLabels[schedule.dayOfWeek]} ${periods} · ${schedule.startWeek}—${schedule.endWeek} 周${patternLabels[schedule.weekPattern] === '每周' ? '' : ` · ${patternLabels[schedule.weekPattern]}`} · ${schedule.classroomName}`
}

async function loadRounds(keepSelection = true) {
  loading.value = true
  try {
    rounds.value = (await http.get('/course-selections/rounds')).data
    const previousId = keepSelection ? selectedRound.value?.id : undefined
    const preferred = rounds.value.find((item) => item.id === previousId)
      ?? rounds.value.find((item) => item.isAvailableNow && item.termIsCurrent)
      ?? rounds.value.find((item) => item.termIsCurrent)
      ?? rounds.value.find((item) => item.isAvailableNow)
      ?? rounds.value[0]
    if (preferred) await selectRound(preferred)
    else {
      selectedRound.value = null
      offerings.value = []
    }
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  } finally {
    loading.value = false
  }
}

async function selectRound(round: any) {
  selectedRound.value = round
  offerings.value = []
  enrollments.value = []
  previewOfferingId.value = ''
  detailLoading.value = true
  try {
    if (isStudent.value) {
      const [optionResponse, enrollmentResponse] = await Promise.all([
        http.get('/course-selections/student/options', { params: { roundId: round.id } }),
        http.get('/course-selections/student/enrollments', {
          params: { academicTermId: round.academicTermId },
        }),
      ])
      offerings.value = optionResponse.data.offerings
      enrollments.value = enrollmentResponse.data
    } else {
      offerings.value = (
        await http.get(`/course-selections/rounds/${round.id}/offerings`)
      ).data
      if (round.status === 'Draft') await loadTasks(round.academicTermId)
    }
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  } finally {
    detailLoading.value = false
  }
}

async function loadTasks(academicTermId: string) {
  const { data } = await http.get('/teaching-tasks/options', {
    params: {
      academicTermId,
      status: 'Published',
    },
  })
  tasks.value = data
}

function openRound(round?: any) {
  editingRoundId.value = round?.id ?? ''
  const now = new Date()
  const ends = new Date(now.getTime() + 7 * 86400000)
  const withdrawal = new Date(now.getTime() + 14 * 86400000)
  Object.assign(roundForm, {
    academicTermId: round?.academicTermId ?? defaultAcademicTermId(terms.value),
    name: round?.name ?? '',
    startsAt: round ? toPickerValue(round.startsAt) : toPickerValue(now.toISOString()),
    endsAt: round ? toPickerValue(round.endsAt) : toPickerValue(ends.toISOString()),
    withdrawalEndsAt: round
      ? toPickerValue(round.withdrawalEndsAt)
      : toPickerValue(withdrawal.toISOString()),
    maxCredits: round?.maxCredits ?? 30,
    maxCourseCount: round?.maxCourseCount ?? undefined,
    eligibleGrades: [...(round?.eligibleGrades ?? [])],
    notes: round?.notes ?? '',
  })
  roundDialog.value = true
}

async function saveRound() {
  if (!roundForm.academicTermId || !roundForm.name?.trim()) {
    ElMessage.warning('请选择学期并填写批次名称。')
    return
  }
  try {
    const payload = {
      ...roundForm,
      maxCourseCount: roundForm.maxCourseCount || null,
      eligibleGrades: roundForm.eligibleGrades ?? [],
      startsAt: toIso(roundForm.startsAt),
      endsAt: toIso(roundForm.endsAt),
      withdrawalEndsAt: toIso(roundForm.withdrawalEndsAt),
    }
    if (editingRoundId.value) {
      await http.put(`/course-selections/rounds/${editingRoundId.value}`, payload)
    } else {
      await http.post('/course-selections/rounds', payload)
    }
    roundDialog.value = false
    ElMessage.success(editingRoundId.value ? '选课批次已更新' : '选课批次草稿已创建')
    await loadRounds(false)
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  }
}

async function deleteRound(round: any) {
  try {
    await ElMessageBox.confirm(`删除选课批次“${round.name}”？`, '删除草稿', {
      type: 'warning',
    })
    await http.delete(`/course-selections/rounds/${round.id}`)
    await loadRounds(false)
  } catch (error: any) {
    if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error))
  }
}

async function openSelection(round: any) {
  try {
    await ElMessageBox.confirm(
      '开放后批次及教学班配置将锁定，学生可在设置的时间窗口内选课。',
      '开放选课',
      { type: 'warning', confirmButtonText: '确认开放', cancelButtonText: '取消' },
    )
    await http.post(`/course-selections/rounds/${round.id}/open`)
    ElMessage.success('选课批次已开放')
    await loadRounds()
  } catch (error: any) {
    if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error))
  }
}

async function closeSelection(round: any) {
  try {
    await ElMessageBox.confirm(
      '关闭后学生将不能继续选课或退课，系统会向本轮参与学生发送最终选课结果。',
      '关闭选课',
      { type: 'warning', confirmButtonText: '确认关闭', cancelButtonText: '取消' },
    )
    const { data } = await http.post(`/course-selections/rounds/${round.id}/close`)
    ElMessage.success(`选课批次已关闭，已向 ${data.notifiedStudentCount} 名学生发送结果通知`)
    await loadRounds()
  } catch (error: any) {
    if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error))
  }
}

function openOffering(offering?: any) {
  editingOfferingId.value = offering?.id ?? ''
  Object.assign(offeringForm, {
    teachingTaskId: offering?.teachingTaskId,
    capacity: offering?.capacity ?? 60,
    isOpenToAll: offering?.isOpenToAll ?? false,
    notes: offering?.notes ?? '',
  })
  offeringDialog.value = true
}

function onTaskChanged(taskId: string) {
  const task = tasks.value.find((item) => item.id === taskId)
  if (task) offeringForm.capacity = task.capacity
}

async function saveOffering() {
  if (!offeringForm.teachingTaskId) {
    ElMessage.warning('请选择要进入选课的教学班。')
    return
  }
  try {
    const base = `/course-selections/rounds/${selectedRound.value.id}/offerings`
    if (editingOfferingId.value) {
      await http.put(`${base}/${editingOfferingId.value}`, offeringForm)
    } else {
      await http.post(base, offeringForm)
    }
    offeringDialog.value = false
    ElMessage.success(editingOfferingId.value ? '教学班配置已更新' : '教学班已加入本轮选课')
    await selectRound(selectedRound.value)
    await loadRounds()
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  }
}

async function deleteOffering(offering: any) {
  try {
    await ElMessageBox.confirm(`从本轮移除“${offering.taskName}”？`, '移除教学班', {
      type: 'warning',
    })
    await http.delete(
      `/course-selections/rounds/${selectedRound.value.id}/offerings/${offering.id}`,
    )
    await selectRound(selectedRound.value)
  } catch (error: any) {
    if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error))
  }
}

async function showRoster(offering: any) {
  rosterKeyword.value = ''
  rosterGrade.value = undefined
  rosterMajorId.value = undefined
  rosterClassId.value = undefined
  rosterStudentPage.value = 1
  rosterWaitlistPage.value = 1
  rosterDrawer.value = true
  await loadRoster(offering.id)
}

async function loadRoster(offeringId: string) {
  rosterLoading.value = true
  try {
    roster.value = (
      await http.get(`/course-selections/offerings/${offeringId}/roster`, {
        params: {
          keyword: rosterKeyword.value.trim() || undefined,
          grade: rosterGrade.value,
          majorId: rosterMajorId.value,
          administrativeClassId: rosterClassId.value,
          studentPage: rosterStudentPage.value,
          studentPageSize: rosterPageSize,
          waitlistPage: rosterWaitlistPage.value,
          waitlistPageSize: rosterPageSize,
        },
      })
    ).data
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  } finally {
    rosterLoading.value = false
  }
}

function filterRoster() {
  rosterStudentPage.value = 1
  rosterWaitlistPage.value = 1
  if (roster.value) void loadRoster(roster.value.id)
}

async function openProxyEnrollment() {
  studentKeyword.value = ''
  selectedStudentIds.value = []
  eligiblePage.value = 1
  proxyDialog.value = true
  await loadEligibleStudents()
}

async function loadEligibleStudents(page = eligiblePage.value) {
  if (!roster.value) return
  eligibleLoading.value = true
  eligiblePage.value = page
  try {
    const { data } = await http.get(
      `/course-selections/offerings/${roster.value.id}/eligible-students`,
      {
        params: {
          keyword: studentKeyword.value.trim() || undefined,
          page,
          pageSize: 20,
        },
      },
    )
    eligibleStudents.value = data.items
    eligibleTotal.value = data.total
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  } finally {
    eligibleLoading.value = false
  }
}

function onEligibleSelectionChanged(rows: any[]) {
  selectedStudentIds.value = rows.map((item) => item.id)
}

async function proxyEnroll() {
  if (!roster.value || selectedStudentIds.value.length === 0) {
    ElMessage.warning('请至少选择一名学生。')
    return
  }
  proxySubmitting.value = true
  try {
    const { data } = await http.post(
      `/course-selections/offerings/${roster.value.id}/admin-enrollments`,
      { studentIds: selectedStudentIds.value },
    )
    ElMessage.success(`已为 ${data.enrolledCount} 名学生完成代选`)
    proxyDialog.value = false
    await loadRoster(roster.value.id)
    if (selectedRound.value) await selectRound(selectedRound.value)
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  } finally {
    proxySubmitting.value = false
  }
}

const forceDialog = ref(false)
const forceSubmitting = ref(false)

function openForceEnrollment() {
  studentKeyword.value = ''
  selectedStudentIds.value = []
  eligiblePage.value = 1
  forceDialog.value = true
  loadForceEligibleStudents()
}

async function loadForceEligibleStudents(page = eligiblePage.value) {
  if (!roster.value) return
  eligibleLoading.value = true
  eligiblePage.value = page
  try {
    const { data } = await http.get(
      `/course-selections/offerings/${roster.value.id}/eligible-students`,
      {
        params: {
          keyword: studentKeyword.value.trim() || undefined,
          page,
          pageSize: 20,
          forceMode: true,
        },
      },
    )
    eligibleStudents.value = data.items
    eligibleTotal.value = data.total
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  } finally {
    eligibleLoading.value = false
  }
}

async function forceEnrollSubmit() {
  if (!roster.value || selectedStudentIds.value.length === 0) {
    ElMessage.warning('请至少选择一名学生。')
    return
  }
  forceSubmitting.value = true
  try {
    const { data } = await http.post(
      `/course-selections/offerings/${roster.value.id}/force-enroll`,
      { studentIds: selectedStudentIds.value },
    )
    ElMessage.success(`已强制选入 ${data.enrolledCount} 名学生（忽略所有限制）`)
    forceDialog.value = false
    await loadRoster(roster.value.id)
    if (selectedRound.value) await selectRound(selectedRound.value)
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  } finally {
    forceSubmitting.value = false
  }
}

async function removeFromRoster(student: any) {
  if (!roster.value) return
  try {
    await ElMessageBox.confirm(
      `确认将 ${student.studentNumber} ${student.name} 移出“${roster.value.courseName}”教学班名单？`,
      '调整教学班名单',
      { type: 'warning', confirmButtonText: '确认移出', cancelButtonText: '取消' },
    )
    await http.delete(
      `/course-selections/offerings/${roster.value.id}/admin-enrollments/${student.id}`,
    )
    ElMessage.success('已移出教学班名单')
    await loadRoster(roster.value.id)
    if (selectedRound.value) await selectRound(selectedRound.value)
  } catch (error: any) {
    if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error))
  }
}

async function removeFromWaitlist(student: any) {
  if (!roster.value) return
  try {
    await ElMessageBox.confirm(
      `确认将 ${student.studentNumber} ${student.name} 移出“${roster.value.courseName}”候补队列？`,
      '调整候补队列',
      { type: 'warning', confirmButtonText: '确认移出', cancelButtonText: '取消' },
    )
    await http.delete(
      `/course-selections/offerings/${roster.value.id}/waitlist/${student.id}`,
    )
    ElMessage.success('已移出候补队列')
    await loadRoster(roster.value.id)
    if (selectedRound.value) await selectRound(selectedRound.value)
  } catch (error: any) {
    if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error))
  }
}

async function enroll(offering: any) {
  try {
    await http.post('/course-selections/student/enrollments', {
      offeringId: offering.id,
    })
    ElMessage.success(`已选“${offering.courseName}”`)
    await selectRound(selectedRound.value)
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  }
}

async function joinWaitlist(offering: any) {
  try {
    const { data } = await http.post('/course-selections/student/waitlist', {
      offeringId: offering.id,
    })
    ElMessage.success(`已加入“${offering.courseName}”候补，当前第 ${data.position} 位`)
    await selectRound(selectedRound.value)
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  }
}

function togglePreview(offering: any) {
  previewOfferingId.value = previewOfferingId.value === offering.id
    ? ''
    : offering.id
}

async function withdraw(offering: any) {
  const enrollment = enrollments.value.find(
    (item) =>
      item.courseSelectionOfferingId === offering.id &&
      item.status === 'Enrolled',
  )
  if (!enrollment) return
  try {
    await ElMessageBox.confirm(
      `确定退选“${offering.courseName}”吗？名额释放后可能被其他同学选择。`,
      '确认退课',
      { type: 'warning', confirmButtonText: '确认退选', cancelButtonText: '暂不退选' },
    )
    await http.delete(`/course-selections/student/enrollments/${enrollment.id}`)
    ElMessage.success('已退选')
    await selectRound(selectedRound.value)
  } catch (error: any) {
    if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error))
  }
}

async function cancelWaitlist(offering: any) {
  const enrollment = enrollments.value.find(
    (item) =>
      item.courseSelectionOfferingId === offering.id &&
      item.status === 'Waitlisted',
  )
  if (!enrollment) return
  try {
    await ElMessageBox.confirm(
      `确定取消“${offering.courseName}”候补吗？取消后将失去当前顺位。`,
      '取消候补',
      { type: 'warning', confirmButtonText: '确认取消', cancelButtonText: '继续候补' },
    )
    await http.delete(`/course-selections/student/enrollments/${enrollment.id}`)
    ElMessage.success('已取消候补')
    await selectRound(selectedRound.value)
  } catch (error: any) {
    if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error))
  }
}

onMounted(async () => {
  try {
    if (isManager.value) {
      const [termResponse, optionResponse] = await Promise.all([
        http.get('/base-data/terms'),
        canManageRounds.value
          ? http.get('/course-selections/configuration-options')
          : Promise.resolve({ data: { grades: [] } }),
      ])
      terms.value = termResponse.data
      gradeOptions.value = optionResponse.data.grades
    }
    await loadRounds(false)
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  }
})
</script>

<template>
  <div class="page-stack selection-page">
    <section class="page-intro">
      <div>
        <span class="section-kicker">COURSE REGISTRATION</span>
        <h2>{{ isStudent ? '学生选课' : '选课管理' }}</h2>
        <p v-if="isStudent">在开放时间内安排本学期课程，系统会实时校验容量、学分与上课时间。</p>
        <p v-else>设置选课窗口、投放教学班，并以实时名单掌握教学班容量。</p>
      </div>
      <el-button v-if="canManageRounds" type="primary" :icon="Plus" @click="openRound()">
        新建选课批次
      </el-button>
      <el-button v-else :icon="Refresh" @click="loadRounds()">刷新名额</el-button>
    </section>

    <section v-if="rounds.length" class="selection-round-strip" v-loading="loading">
      <button
        v-for="round in rounds"
        :key="round.id"
        type="button"
        :class="{
          active: selectedRound?.id === round.id,
          'historical-record': !round.termIsCurrent && !round.termIsArchived,
          'archived-record': round.termIsArchived,
        }"
        @click="selectRound(round)"
      >
        <span>{{ round.termName }}</span>
        <b>{{ round.name }}</b>
        <small>{{ formatDateTime(round.startsAt) }} — {{ formatDateTime(round.endsAt) }}</small>
        <small>{{ roundLimitLabel(round) }}</small>
        <i :class="round.status.toLowerCase()">{{ statusLabels[round.status] }}</i>
      </button>
    </section>

    <template v-if="selectedRound">
      <section class="selection-window">
        <div class="window-seal">
          <el-icon><Clock /></el-icon>
          <span>{{ selectedRound.isAvailableNow ? '正在开放' : statusLabels[selectedRound.status] }}</span>
        </div>
        <div class="window-copy">
          <span>SELECTION WINDOW · {{ selectedRound.termName }}</span>
          <h3>{{ selectedRound.name }}</h3>
          <p>
            选课 {{ formatDateTime(selectedRound.startsAt) }}—{{ formatDateTime(selectedRound.endsAt) }}
            <em>退课截止 {{ formatDateTime(selectedRound.withdrawalEndsAt) }}</em>
            <em>{{ roundLimitLabel(selectedRound) }}</em>
          </p>
        </div>
        <div v-if="isStudent" class="credit-meter">
          <div>
            <span>已选学分</span>
            <b>{{ selectedCredits }}</b>
            <small>/ {{ selectedRound.maxCredits }}</small>
          </div>
          <div class="credit-track"><i :style="{ width: `${creditPercent}%` }" /></div>
          <p>
            {{ selectedCount }}{{ selectedRound.maxCourseCount ? ` / ${selectedRound.maxCourseCount}` : '' }} 门课程
            · 剩余可选 {{ Math.max(0, selectedRound.maxCredits - selectedCredits) }} 学分
          </p>
        </div>
        <div v-else class="round-actions">
          <el-button
            v-if="canManageRounds && selectedRound.status === 'Draft'"
            @click="openRound(selectedRound)"
          >编辑批次</el-button>
          <el-button
            v-if="canManageRounds && selectedRound.status === 'Draft'"
            type="success"
            @click="openSelection(selectedRound)"
          >开放选课</el-button>
          <el-button
            v-if="canManageRounds && selectedRound.status === 'Open'"
            type="warning"
            @click="closeSelection(selectedRound)"
          >关闭选课</el-button>
          <el-button
            v-if="canManageRounds && selectedRound.status === 'Draft'"
            type="danger"
            plain
            @click="deleteRound(selectedRound)"
          >删除草稿</el-button>
        </div>
      </section>

      <section v-if="isManager" class="data-card" v-loading="detailLoading">
        <div class="selection-ledger-head">
          <div>
            <span>OFFERING LEDGER</span>
            <h3>本轮教学班</h3>
            <p>{{ offerings.length }} 个教学班 · 开放后配置锁定，名单随学生选退实时更新</p>
          </div>
          <el-button
            v-if="selectedRound.status === 'Draft'"
            type="primary"
            :icon="Plus"
            @click="openOffering()"
          >加入教学班</el-button>
        </div>
        <el-table :data="offerings" class="data-table">
          <el-table-column label="教学班 / 课程" min-width="260">
            <template #default="{ row }">
              <div class="course-name">
                <b>{{ row.taskName }}</b>
                <span>{{ row.taskNumber }} · {{ row.courseCode }} {{ row.courseName }}</span>
              </div>
            </template>
          </el-table-column>
          <el-table-column label="教师 / 行政班" min-width="200">
            <template #default="{ row }">
              <div class="course-name">
                <b>{{ row.teacherNames.join('、') || '未安排' }}</b>
                <span>
                  {{ row.isOpenToAll ? '全校学生' : row.classNames.join('、') }}
                  · {{ roundGradeLabel(selectedRound) }}
                </span>
              </div>
            </template>
          </el-table-column>
          <el-table-column label="学分" width="75" prop="credits" />
          <el-table-column label="名单 / 容量" width="145">
            <template #default="{ row }">
              <b class="capacity-number">{{ row.enrolledCount }} / {{ row.capacity }}</b>
              <small v-if="row.waitlistedCount" class="waitlist-count">
                候补 {{ row.waitlistedCount }} 人
              </small>
            </template>
          </el-table-column>
          <el-table-column label="操作" width="190" fixed="right">
            <template #default="{ row }">
              <el-button link type="primary" @click="showRoster(row)">名单管理</el-button>
              <el-button
                v-if="selectedRound.status === 'Draft'"
                link
                type="primary"
                @click="openOffering(row)"
              >编辑</el-button>
              <el-button
                v-if="selectedRound.status === 'Draft'"
                link
                type="danger"
                @click="deleteOffering(row)"
              >移除</el-button>
            </template>
          </el-table-column>
          <template #empty><el-empty description="本轮尚未加入教学班" /></template>
        </el-table>
      </section>

      <template v-else>
        <section class="data-card selection-timetable-card" v-loading="detailLoading">
          <div class="selection-timetable-head">
            <div>
              <span>WEEKLY ARRANGEMENT</span>
              <h3>已选课程表</h3>
              <p>
                {{ selectedCount ? `已排入 ${selectedCount} 门课程` : '选择课程后将在此形成周课表' }}
                <template v-if="previewOffering">
                  · 正在试排“{{ previewOffering.courseName }}”
                </template>
              </p>
            </div>
            <el-button
              v-if="previewOffering"
              plain
              @click="previewOfferingId = ''"
            >结束试排</el-button>
          </div>
          <el-alert
            v-if="previewConflictNames.length"
            class="selection-preview-alert"
            type="error"
            :closable="false"
            :title="`试排课程与“${previewConflictNames.join('、')}”时间冲突，不能同时选择。`"
          />
          <div class="selection-timetable-scroll">
            <div
              class="selection-timetable-grid"
              :style="{ gridTemplateRows: `42px repeat(${timetablePeriodCount}, 64px)` }"
            >
              <div class="timetable-corner">
                <el-icon><Calendar /></el-icon>
                节次
              </div>
              <div
                v-for="day in 7"
                :key="`day-${day}`"
                class="timetable-day"
                :style="{ gridColumn: day + 1, gridRow: 1 }"
              >{{ weekdayLabels[day] }}</div>
              <div
                v-for="period in timetablePeriods"
                :key="`period-${period}`"
                class="timetable-period"
                :style="{ gridColumn: 1, gridRow: period + 1 }"
              >
                <b>{{ period }}</b>
                <span>第 {{ period }} 节</span>
              </div>
              <div
                v-for="cell in timetableCells"
                :key="cell.key"
                class="timetable-cell"
                :style="{
                  gridColumn: cell.dayOfWeek + 1,
                  gridRow: cell.period + 1,
                }"
              />
              <div
                v-for="group in timetableGroups"
                :key="group.key"
                :class="['timetable-course', { multiple: group.entries.length > 1 }]"
                :style="{
                  gridColumn: group.dayOfWeek + 1,
                  gridRow: `${group.startPeriod + 1} / span ${group.periodCount}`,
                }"
              >
                <article
                  v-for="entry in group.entries"
                  :key="`${entry.taskNumber}-${entry.startWeek}-${entry.weekPattern}`"
                  :class="[
                    `tone-${entry.tone}`,
                    { preview: entry.isPreview, conflict: entry.hasConflict },
                  ]"
                >
                  <b>{{ entry.courseName }}</b>
                  <span>
                    {{ entry.startWeek }}—{{ entry.endWeek }} 周
                    {{ patternLabels[entry.weekPattern] === '每周' ? '' : patternLabels[entry.weekPattern] }}
                  </span>
                  <small>{{ entry.classroomName }}</small>
                </article>
              </div>
            </div>
          </div>
          <div
            v-if="flexibleTimetableOfferings.length"
            class="flexible-course-row"
          >
            <b>非排时课程</b>
            <span
              v-for="offering in flexibleTimetableOfferings"
              :key="offering.id"
              :class="{ preview: offering.id === previewOfferingId }"
            >
              {{ offering.courseName }} · 不占固定节次与教室
            </span>
          </div>
        </section>

        <section class="data-card selection-catalog">
          <div class="selection-catalog-head">
            <div>
              <span>AVAILABLE CLASSES</span>
              <h3>本轮可选教学班</h3>
              <p>先试排再选课；冲突、容量和学分限制会提前显示，提交时服务端会再次校验。</p>
            </div>
            <b>{{ filteredOfferings.length }} / {{ offerings.length }}</b>
          </div>
          <div class="selection-catalog-filter">
            <el-input
              v-model="offeringKeyword"
              :prefix-icon="Search"
              clearable
              placeholder="搜索课程代码、课程名称、教学班或教师"
            />
            <el-segmented
              v-model="offeringStatus"
              :options="[
                { label: '全部', value: 'All' },
                { label: '可选', value: 'Selectable' },
                { label: '候补', value: 'Waitlisted' },
                { label: '已选', value: 'Selected' },
                { label: '受限', value: 'Blocked' },
              ]"
            />
          </div>
        </section>

        <section class="offering-grid" v-loading="detailLoading">
        <article
          v-for="offering in filteredOfferings"
          :key="offering.id"
          :class="[
            'offering-ticket',
            {
              selected: offering.enrollmentStatus === 'Enrolled',
              waitlisted: offering.enrollmentStatus === 'Waitlisted',
              previewing: offering.id === previewOfferingId,
              blocked: !['Enrolled', 'Waitlisted'].includes(offering.enrollmentStatus)
                && offeringEligibilityReason(offering),
            },
          ]"
        >
          <header>
            <div>
              <span>{{ offering.courseCode }} · {{ offering.taskNumber }}</span>
              <h3>{{ offering.courseName }}</h3>
              <p>
                {{ offering.teacherNames.join('、') || '教师待定' }} · {{ offering.credits }} 学分
                <el-tag v-if="offering.isRetake && offering.enrollmentStatus !== 'Enrolled'" size="small" type="warning" effect="plain" style="margin-left:6px">重修</el-tag>
              </p>
            </div>
            <i v-if="offering.enrollmentStatus === 'Enrolled'" class="selected-mark">
              <el-icon><CircleCheck /></el-icon> 已选
            </i>
            <el-tag
              v-else-if="offering.enrollmentStatus === 'Waitlisted'"
              type="warning"
              effect="dark"
            >候补第 {{ offering.waitlistPosition }} 位</el-tag>
          </header>
          <div class="ticket-schedules">
            <div v-for="schedule in (offering.schedules ?? [])" :key="formatSchedule(schedule)">
              <el-icon><Tickets /></el-icon>
              <span>{{ formatSchedule(schedule) }}</span>
            </div>
            <span v-if="offering.isFlexible" class="schedule-missing">
              非排时课程 · 不占正常时间与场地
            </span>
            <span v-else-if="!(offering.schedules ?? []).length" class="schedule-missing">课表尚未发布</span>
          </div>
          <div
            v-if="!['Enrolled', 'Waitlisted'].includes(offering.enrollmentStatus) && offeringBlockReason(offering)"
            class="selection-block-reason"
          >
            {{ offeringBlockReason(offering) }}
          </div>
          <footer>
            <div class="seat-meter">
              <template v-if="offering.isRetake && offering.enrollmentStatus !== 'Enrolled'">
                <span>剩余 {{ Math.max(0, Math.ceil(offering.capacity * 1.15) - offering.enrolledCount) }} / {{ Math.ceil(offering.capacity * 1.15) }} 席 <em>(重修扩容)</em></span>
                <div><i :style="{ width: `${Math.min(100, offering.enrolledCount / (Math.ceil(offering.capacity * 1.15)) * 100)}%` }" /></div>
              </template>
              <template v-else>
                <span>剩余 {{ Math.max(0, offering.capacity - offering.enrolledCount) }} / {{ offering.capacity }} 席</span>
                <div><i :style="{ width: `${Math.min(100, offering.enrolledCount / offering.capacity * 100)}%` }" /></div>
              </template>
              <small v-if="offering.waitlistedCount">
                当前候补 {{ offering.waitlistedCount }} 人
              </small>
            </div>
            <el-button
              v-if="(offering.schedules ?? []).length || offering.isFlexible"
              text
              :type="offering.id === previewOfferingId ? 'warning' : 'primary'"
              @click="togglePreview(offering)"
            >{{ offering.id === previewOfferingId ? '取消试排' : '课表试排' }}</el-button>
            <el-button
              v-if="offering.enrollmentStatus === 'Enrolled'"
              type="danger"
              plain
              :disabled="selectedRound.status !== 'Open'"
              @click="withdraw(offering)"
            >退选</el-button>
            <el-button
              v-else-if="offering.enrollmentStatus === 'Waitlisted'"
              type="warning"
              plain
              :disabled="selectedRound.status !== 'Open'"
              @click="cancelWaitlist(offering)"
            >取消候补</el-button>
            <el-button
              v-else-if="isOfferingFull(offering) && !offeringEligibilityReason(offering)"
              type="warning"
              @click="joinWaitlist(offering)"
            >加入候补</el-button>
            <el-button
              v-else
              type="primary"
              :disabled="Boolean(offeringEligibilityReason(offering))"
              @click="enroll(offering)"
            >{{ offering.enrollmentStatus === 'Withdrawn' ? '重新选择' : '选择课程' }}</el-button>
          </footer>
        </article>
        <el-empty
          v-if="!filteredOfferings.length"
          :description="offerings.length ? '没有符合当前筛选条件的教学班' : '本轮没有适合你所在班级的课程'"
        />
        </section>
      </template>
    </template>

    <el-empty v-else-if="!loading" description="暂无选课批次" />

    <el-dialog
      v-model="roundDialog"
      :title="editingRoundId ? '编辑选课批次' : '新建选课批次'"
      width="720px"
    >
      <el-form label-position="top">
        <div class="form-grid">
          <el-form-item label="开课学期" required>
            <el-select v-model="roundForm.academicTermId">
              <el-option v-for="term in terms" :key="term.id" :label="academicTermLabel(term)" :value="term.id" :class="academicTermOptionClass(term)" />
            </el-select>
          </el-form-item>
          <el-form-item label="批次名称" required>
            <el-input v-model="roundForm.name" placeholder="如 第一轮选课" />
          </el-form-item>
        </div>
        <div class="form-grid">
          <el-form-item label="选课开始时间" required>
            <el-date-picker v-model="roundForm.startsAt" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" />
          </el-form-item>
          <el-form-item label="选课结束时间" required>
            <el-date-picker v-model="roundForm.endsAt" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" />
          </el-form-item>
        </div>
        <div class="form-grid">
          <el-form-item label="退课截止时间" required>
            <el-date-picker v-model="roundForm.withdrawalEndsAt" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" />
          </el-form-item>
          <el-form-item label="本轮学分上限">
            <el-input-number v-model="roundForm.maxCredits" :min="0.5" :max="99" :step="0.5" />
          </el-form-item>
        </div>
        <div class="form-grid">
          <el-form-item label="适用年级">
            <el-select
              v-model="roundForm.eligibleGrades"
              multiple
              collapse-tags
              collapse-tags-tooltip
              clearable
              placeholder="不选择表示全部年级"
            >
              <el-option
                v-for="grade in gradeOptions"
                :key="grade"
                :label="`${grade} 级`"
                :value="grade"
              />
            </el-select>
          </el-form-item>
          <el-form-item label="最多课程门数">
            <el-input-number
              v-model="roundForm.maxCourseCount"
              :min="1"
              :max="100"
              placeholder="不限制"
            />
          </el-form-item>
        </div>
        <el-alert
          type="info"
          :closable="false"
          title="适用年级会同时限制学生可见批次、自主选课、候补、管理员代选和自动递补；留空表示全部年级。"
        />
        <el-form-item label="说明">
          <el-input v-model="roundForm.notes" type="textarea" :rows="3" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="roundDialog = false">取消</el-button>
        <el-button type="primary" @click="saveRound">保存草稿</el-button>
      </template>
    </el-dialog>

    <el-dialog
      v-model="offeringDialog"
      :title="editingOfferingId ? '编辑教学班配置' : '加入教学班'"
      width="620px"
    >
      <el-form label-position="top">
        <el-form-item label="已发布教学班" required>
          <el-select
            v-model="offeringForm.teachingTaskId"
            filterable
            @change="onTaskChanged"
          >
            <el-option
              v-for="task in selectableTasks"
              :key="task.id"
              :label="`${task.taskNumber} · ${task.courseCode} ${task.courseName}${task.schedulingMode === 'Flexible' ? '（非排时）' : ''}`"
              :value="task.id"
            />
          </el-select>
        </el-form-item>
        <div class="form-grid compact">
          <el-form-item label="选课容量">
            <el-input-number v-model="offeringForm.capacity" :min="1" />
          </el-form-item>
          <el-form-item label="选课对象">
            <el-switch
              v-model="offeringForm.isOpenToAll"
              active-text="全校学生"
              inactive-text="教学任务关联班级"
            />
          </el-form-item>
        </div>
        <el-form-item label="说明">
          <el-input v-model="offeringForm.notes" type="textarea" :rows="2" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="offeringDialog = false">取消</el-button>
        <el-button type="primary" @click="saveOffering">保存</el-button>
      </template>
    </el-dialog>

    <el-drawer v-model="rosterDrawer" title="教学班名单管理" size="720px">
      <template v-if="roster">
        <div class="roster-summary">
          <el-icon><UserFilled /></el-icon>
          <div>
            <span>{{ roster.courseCode }} · {{ roster.taskNumber }}</span>
            <b>{{ roster.taskName }}</b>
            <small>
              正式 {{ roster.enrolledCount }} / {{ roster.capacity }} 人
              <template v-if="roster.waitlistedCount"> · 候补 {{ roster.waitlistedCount }} 人</template>
            </small>
          </div>
          <div class="roster-actions">
            <el-button
              v-if="roster.canProxyEnroll"
              type="primary"
              :icon="Plus"
              @click="openProxyEnrollment"
            >代选学生</el-button>
            <el-button
              v-if="isManager"
              type="danger"
              plain
              :icon="Plus"
              @click="openForceEnrollment"
            >强制选课</el-button>
          </div>
        </div>
        <el-alert
          v-if="roster.canProxyEnroll"
          class="roster-notice"
          type="info"
          :closable="false"
          title="公共必修课支持校级教务代选；系统仍会校验年级范围、教学班容量、课程门数、学分上限、重复课程和课表冲突。"
        />
        <el-alert
          v-if="isManager"
          class="roster-notice force-notice"
          type="warning"
          :closable="false"
          title="强制选课将忽略容量、时间冲突、学分上限和重复课程等限制，直接加入名单。"
        />
        <div class="roster-filter">
          <el-input
            v-model="rosterKeyword"
            clearable
            placeholder="按学号、姓名或行政班筛选"
            @keyup.enter="filterRoster"
            @clear="filterRoster"
          />
          <el-select v-model="rosterGrade" clearable placeholder="全部年级" @change="filterRoster">
            <el-option v-for="grade in roster.filterOptions?.grades" :key="grade" :label="`${grade} 级`" :value="grade" />
          </el-select>
          <el-select v-model="rosterMajorId" clearable placeholder="全部专业" @change="() => { rosterClassId = undefined; filterRoster() }">
            <el-option v-for="major in roster.filterOptions?.majors" :key="major.majorId" :label="major.majorName" :value="major.majorId" />
          </el-select>
          <el-select v-model="rosterClassId" clearable placeholder="全部行政班" @change="filterRoster">
            <el-option v-for="item in rosterClasses" :key="item.classId" :label="item.className" :value="item.classId" />
          </el-select>
          <el-button :icon="Search" @click="filterRoster">查询</el-button>
        </div>
        <el-table v-loading="rosterLoading" :data="roster.students">
          <el-table-column prop="studentNumber" label="学号" width="130" />
          <el-table-column prop="name" label="姓名" width="90" />
          <el-table-column label="年级" width="80">
            <template #default="{ row }">{{ row.grade }} 级</template>
          </el-table-column>
          <el-table-column prop="className" label="行政班" min-width="150" />
          <el-table-column label="选课时间" min-width="130">
            <template #default="{ row }">{{ formatDateTime(row.enrolledAt) }}</template>
          </el-table-column>
          <el-table-column v-if="roster.canProxyEnroll" label="操作" width="80" fixed="right">
            <template #default="{ row }">
              <el-button link type="danger" @click="removeFromRoster(row)">移出</el-button>
            </template>
          </el-table-column>
          <template #empty><el-empty description="暂无学生选课" /></template>
        </el-table>
        <el-pagination
          v-if="roster.studentTotal > rosterPageSize"
          small background layout="total, prev, pager, next"
          :current-page="rosterStudentPage" :page-size="rosterPageSize" :total="roster.studentTotal"
          @current-change="(page: number) => { rosterStudentPage = page; loadRoster(roster.id) }"
        />
        <section class="waitlist-panel">
          <div class="waitlist-panel-head">
            <div>
              <span>WAITLIST</span>
              <b>候补队列</b>
            </div>
            <small>退课释放名额后，系统按顺位重新校验并自动递补</small>
          </div>
          <el-table :data="roster.waitlist">
            <el-table-column prop="position" label="顺位" width="70" />
            <el-table-column prop="studentNumber" label="学号" width="130" />
            <el-table-column prop="name" label="姓名" width="90" />
            <el-table-column label="年级" width="80">
              <template #default="{ row }">{{ row.grade }} 级</template>
            </el-table-column>
            <el-table-column prop="className" label="行政班" min-width="150" />
            <el-table-column label="候补时间" min-width="130">
              <template #default="{ row }">{{ formatDateTime(row.waitlistedAt) }}</template>
            </el-table-column>
            <el-table-column
              v-if="roster.canManageWaitlist"
              label="操作"
              width="80"
              fixed="right"
            >
              <template #default="{ row }">
                <el-button link type="danger" @click="removeFromWaitlist(row)">移出</el-button>
              </template>
            </el-table-column>
            <template #empty><el-empty description="暂无候补学生" /></template>
          </el-table>
          <el-pagination
            v-if="roster.waitlistTotal > rosterPageSize"
            small background layout="total, prev, pager, next"
            :current-page="rosterWaitlistPage" :page-size="rosterPageSize" :total="roster.waitlistTotal"
            @current-change="(page: number) => { rosterWaitlistPage = page; loadRoster(roster.id) }"
          />
        </section>
      </template>
    </el-drawer>

    <el-dialog v-model="proxyDialog" title="公共必修课代选" width="760px">
      <template v-if="roster">
        <div class="proxy-course-note">
          <b>{{ roster.courseName }}</b>
          <span>{{ roster.taskNumber }} · 当前 {{ roster.enrolledCount }} / {{ roster.capacity }} 人</span>
        </div>
        <div class="proxy-search">
          <el-input
            v-model="studentKeyword"
            clearable
            placeholder="输入学号、姓名或行政班"
            @keyup.enter="loadEligibleStudents(1)"
            @clear="loadEligibleStudents(1)"
          />
          <el-button type="primary" @click="loadEligibleStudents(1)">查询学生</el-button>
        </div>
        <el-table
          v-loading="eligibleLoading"
          :data="eligibleStudents"
          row-key="id"
          height="360"
          @selection-change="onEligibleSelectionChanged"
        >
          <el-table-column type="selection" width="48" />
          <el-table-column prop="studentNumber" label="学号" width="130" />
          <el-table-column prop="name" label="姓名" width="90" />
          <el-table-column label="年级" width="80">
            <template #default="{ row }">{{ row.grade }} 级</template>
          </el-table-column>
          <el-table-column prop="className" label="行政班" min-width="150" />
          <el-table-column prop="majorName" label="专业" min-width="150" />
          <template #empty><el-empty description="没有可代选的在籍学生" /></template>
        </el-table>
        <el-pagination
          v-if="eligibleTotal > 20"
          class="proxy-pagination"
          layout="prev, pager, next, total"
          :current-page="eligiblePage"
          :page-size="20"
          :total="eligibleTotal"
          @current-change="loadEligibleStudents"
        />
      </template>
      <template #footer>
        <div class="proxy-dialog-footer">
          <span class="proxy-selected-count">已选择 {{ selectedStudentIds.length }} 人</span>
          <el-button @click="proxyDialog = false">取消</el-button>
          <el-button
            type="primary"
            :loading="proxySubmitting"
            :disabled="selectedStudentIds.length === 0"
            @click="proxyEnroll"
          >确认代选</el-button>
        </div>
      </template>
    </el-dialog>

    <el-dialog v-model="forceDialog" title="强制选课（忽略所有限制）" width="760px">
      <template v-if="roster">
        <div class="proxy-course-note">
          <b>{{ roster.courseName }}</b>
          <span>{{ roster.taskNumber }} · 当前 {{ roster.enrolledCount }} / {{ roster.capacity }} 人</span>
        </div>
        <el-alert
          class="roster-notice"
          type="error"
          :closable="false"
          title="强制选课将忽略容量、时间冲突、学分上限、重复课程等全部限制，请谨慎操作。"
        />
        <div class="proxy-search" style="margin-top:12px">
          <el-input
            v-model="studentKeyword"
            clearable
            :prefix-icon="Search"
            placeholder="学号、姓名或班级"
            @keyup.enter="loadForceEligibleStudents(1)"
            @clear="loadForceEligibleStudents(1)"
          />
          <el-button type="primary" @click="loadForceEligibleStudents(1)">查询学生</el-button>
        </div>
        <el-table
          v-loading="eligibleLoading"
          :data="eligibleStudents"
          row-key="id"
          height="360"
          @selection-change="onEligibleSelectionChanged"
        >
          <el-table-column type="selection" width="48" />
          <el-table-column prop="studentNumber" label="学号" width="130" />
          <el-table-column prop="name" label="姓名" width="90" />
          <el-table-column label="年级" width="80">
            <template #default="{ row }">{{ row.grade }} 级</template>
          </el-table-column>
          <el-table-column prop="className" label="行政班" min-width="150" />
          <el-table-column prop="collegeName" label="学院" min-width="120" />
          <template #empty><el-empty description="没有可强制选课的在籍学生" /></template>
        </el-table>
        <el-pagination
          v-if="eligibleTotal > 20"
          class="proxy-pagination"
          layout="prev, pager, next, total"
          :current-page="eligiblePage"
          :page-size="20"
          :total="eligibleTotal"
          @current-change="loadForceEligibleStudents"
        />
      </template>
      <template #footer>
        <div class="proxy-dialog-footer">
          <span class="proxy-selected-count">已选择 {{ selectedStudentIds.length }} 人</span>
          <el-button @click="forceDialog = false">取消</el-button>
          <el-button
            type="danger"
            :loading="forceSubmitting"
            :disabled="selectedStudentIds.length === 0"
            @click="forceEnrollSubmit"
          >确认强制选课</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>

<style scoped>
.roster-actions { display: flex; gap: 8px; }
.force-notice { margin-top: 8px; }
.waitlist-count { display: block; margin-top: 3px; color: #b7791f; }
.offering-ticket.waitlisted { border-color: #e6a23c; box-shadow: 0 10px 28px rgb(230 162 60 / 10%); }
.seat-meter > small { display: block; margin-top: 5px; color: #b7791f; }
.waitlist-panel { margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--line); }
.roster-filter { display: flex; flex-wrap: wrap; gap: 8px; margin: 14px 0; }
.roster-filter .el-input { max-width: 260px; }
.roster-filter .el-select { width: 140px; }
.roster-summary ~ :deep(.el-pagination),
.waitlist-panel :deep(.el-pagination) { justify-content: flex-end; margin-top: 10px; }
.waitlist-panel-head {
  display: flex;
  align-items: end;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 12px;
}
.waitlist-panel-head div { display: grid; gap: 3px; }
.waitlist-panel-head span { color: #b7791f; font: 700 9px/1 Consolas, monospace; letter-spacing: .16em; }
.waitlist-panel-head b { font-size: 17px; }
.waitlist-panel-head small { color: var(--muted); text-align: right; }

@media (max-width: 640px) {
  .roster-filter { align-items: stretch; flex-direction: column; }
  .roster-filter .el-input,
  .roster-filter .el-select { width: 100%; max-width: none; }
  .waitlist-panel-head { align-items: start; flex-direction: column; }
  .waitlist-panel-head small { text-align: left; }
}
</style>
