<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, reactive, ref } from 'vue'
import { CopyDocument, EditPen, Plus, Promotion, Refresh, Search, Setting } from '@element-plus/icons-vue'
import http, { apiErrorMessage } from '../api/http'
import { academicTermLabel, academicTermOptionClass, defaultAcademicTermId } from '../utils/academicTerms'

const plans = ref<any[]>([])
const selected = ref<any | null>(null)
const terms = ref<any[]>([])
const tasks = ref<any[]>([])
const classrooms = ref<any[]>([])
const campuses = ref<any[]>([])
const buildings = ref<any[]>([])
const timeSlots = ref<any[]>([])
const constraints = ref<any[]>([])
const loading = ref(false)
const detailLoading = ref(false)
const autoJob = ref<any | null>(null)
const publishJob = ref<any | null>(null)
const planDialog = ref(false)
const cloneDialog = ref(false)
const entryDialog = ref(false)
const settingsDrawer = ref(false)
const settingsTab = ref('time')
const constraintDialog = ref(false)
const constraintBatchDialog = ref(false)
const constraintBatchSaving = ref(false)
const editingPlanId = ref('')
const editingEntryId = ref('')
const keyword = ref('')
const termId = ref<string | undefined>()
const planForm = reactive<Record<string, any>>({})
const cloneForm = reactive<Record<string, any>>({})
const revisingPublishedPlan = ref(false)
const entryForm = reactive<Record<string, any>>({})
const constraintForm = reactive<Record<string, any>>({})
const constraintBatchForm = reactive<Record<string, any>>({})
const constraintFilters = reactive({
  keyword: '',
  collegeId: undefined as string | undefined,
  schedulingMode: undefined as string | undefined,
  classroomMode: undefined as string | undefined,
  constraintState: undefined as string | undefined,
})
let autoPollTimer: ReturnType<typeof setTimeout> | undefined
let publishPollTimer: ReturnType<typeof setTimeout> | undefined

const weekdays = [
  { value: 1, label: '星期一' },
  { value: 2, label: '星期二' },
  { value: 3, label: '星期三' },
  { value: 4, label: '星期四' },
  { value: 5, label: '星期五' },
  { value: 6, label: '星期六' },
  { value: 7, label: '星期日' },
]
const experimentVenueNatures = [
  { value: 1, label: '普通教室' }, { value: 2, label: '实验室' },
  { value: 4, label: '实训室' }, { value: 8, label: '计算机机房' },
  { value: 16, label: '语音室' }, { value: 32, label: '体育场地' },
  { value: 64, label: '艺术场地' },
]
const venueNatureValue = (value: unknown) => {
  if (typeof value === 'number') return value
  if (typeof value !== 'string') return 0
  const names: Record<string, number> = {
    GeneralClassroom: 1, Laboratory: 2, TrainingRoom: 4, ComputerLab: 8,
    LanguageLab: 16, SportsVenue: 32, ArtsVenue: 64,
  }
  return value.split(',').reduce((sum, name) => sum | (names[name.trim()] ?? 0), 0)
}
const periods = computed(() => {
  const configured = timeSlots.value
    .filter((item) => item.isEnabled)
    .map((item) => item.periodNumber)
  return configured.length ? configured : Array.from({ length: 12 }, (_, index) => index + 1)
})
const statusLabels: Record<string, string> = {
  Draft: '草稿',
  Published: '已发布',
  Archived: '已归档',
}
const patternLabels: Record<string, string> = {
  All: '每周',
  Odd: '单周',
  Even: '双周',
}
const isDraft = computed(() => selected.value?.status === 'Draft')
const autoLoading = computed(() =>
  autoJob.value?.status === 'Queued' || autoJob.value?.status === 'Running',
)
const publishLoading = computed(() =>
  publishJob.value?.status === 'Queued' || publishJob.value?.status === 'Running',
)
const scheduleJobLoading = computed(() => autoLoading.value || publishLoading.value)
const autoProgress = computed(() => {
  if (!autoJob.value?.totalTasks) return 0
  return Math.min(
    100,
    Math.round(autoJob.value.processedTasks / autoJob.value.totalTasks * 100),
  )
})
const autoProgressStatus = computed(() => {
  if (autoJob.value?.status === 'Succeeded') return 'success'
  if (autoJob.value?.status === 'Failed') return 'exception'
  return undefined
})
const autoStatusText = computed(() => {
  if (!autoJob.value) return ''
  if (autoJob.value.status === 'Queued') return '任务已进入队列，等待后台执行'
  if (autoJob.value.status === 'Running') {
    return `正在处理 ${autoJob.value.processedTasks}/${autoJob.value.totalTasks} 个教学班，已规划 ${autoJob.value.createdEntries} 条安排`
  }
  if (autoJob.value.status === 'Succeeded') {
    return `后台排课已完成，共生成 ${autoJob.value.createdEntries} 条安排`
  }
  return autoJob.value.errorMessage || '后台排课失败，请稍后重试'
})
const publishProgress = computed(() => {
  if (!publishJob.value?.totalSteps) return 0
  return Math.min(
    100,
    Math.round(publishJob.value.completedSteps / publishJob.value.totalSteps * 100),
  )
})
const publishProgressStatus = computed(() => {
  if (publishJob.value?.status === 'Succeeded') return 'success'
  if (publishJob.value?.status === 'Failed') return 'exception'
  return undefined
})
const publishStatusText = computed(() => {
  if (!publishJob.value) return ''
  if (publishJob.value.status === 'Queued') return '发布任务已进入队列，等待后台检查'
  if (publishJob.value.status === 'Running') {
    return publishJob.value.currentStep || '正在检查并发布课表'
  }
  if (publishJob.value.status === 'Succeeded') return '检查已通过，课表发布成功'
  return publishJob.value.errorMessage || '课表检查未通过'
})
const selectedTaskConstraint = computed(() =>
  constraints.value.find((item) => item.id === entryForm.teachingTaskId),
)
const entryClassrooms = computed(() =>
  classrooms.value.filter((room) =>
    (entryForm.kind === 'Experiment' || !selectedTaskConstraint.value?.requiredCampusId
      || room.campusId === selectedTaskConstraint.value.requiredCampusId) &&
    (entryForm.kind === 'Experiment' || !selectedTaskConstraint.value?.requiredBuildingId
      || room.buildingId === selectedTaskConstraint.value.requiredBuildingId) &&
    (entryForm.kind === 'Experiment' || !selectedTaskConstraint.value?.allowedClassroomIds?.length
      || selectedTaskConstraint.value.allowedClassroomIds.includes(room.id)) &&
    (entryForm.kind !== 'Experiment' || !selectedTaskConstraint.value?.experimentRequiredCampusId
      || room.campusId === selectedTaskConstraint.value.experimentRequiredCampusId) &&
    (entryForm.kind !== 'Experiment' || !selectedTaskConstraint.value?.experimentRequiredBuildingId
      || room.buildingId === selectedTaskConstraint.value.experimentRequiredBuildingId) &&
    (entryForm.kind !== 'Experiment' ||
      !selectedTaskConstraint.value?.allowedExperimentClassroomIds?.length ||
      selectedTaskConstraint.value.allowedExperimentClassroomIds.includes(room.id)),
  ),
)
const entryWeekdays = computed(() => {
  const allowedDays = selectedTaskConstraint.value?.allowedDayOfWeeks ?? []
  return allowedDays.length
    ? weekdays.filter((day) => allowedDays.includes(day.value))
    : weekdays
})
const constraintColleges = computed(() => {
  const result = new Map<string, string>()
  constraints.value.forEach((item) => result.set(item.collegeId, item.collegeName))
  return [...result].map(([id, name]) => ({ id, name })).sort((a, b) =>
    a.name.localeCompare(b.name, 'zh-CN'),
  )
})
const filteredConstraints = computed(() => {
  const text = constraintFilters.keyword.trim().toLowerCase()
  return constraints.value.filter((item) => {
    const matchesKeyword = !text || [
      item.taskNumber,
      item.name,
      item.courseCode,
      item.courseName,
      ...item.teacherNames,
    ].some((value) => String(value).toLowerCase().includes(text))
    const matchesCollege = !constraintFilters.collegeId ||
      item.collegeId === constraintFilters.collegeId
    const matchesMode = !constraintFilters.schedulingMode ||
      item.schedulingMode === constraintFilters.schedulingMode
    const matchesClassroom = !constraintFilters.classroomMode ||
      (constraintFilters.classroomMode === 'required'
        ? item.requiresClassroom
        : !item.requiresClassroom)
    const matchesState = !constraintFilters.constraintState ||
      (constraintFilters.constraintState === 'custom'
        ? item.hasCustomConstraint
        : !item.hasCustomConstraint)
    return matchesKeyword && matchesCollege && matchesMode &&
      matchesClassroom && matchesState
  })
})
const filteredBuildings = computed(() =>
  constraintForm.requiredCampusId
    ? buildings.value.filter((item) => item.campusId === constraintForm.requiredCampusId)
    : buildings.value,
)
const filteredClassrooms = computed(() =>
  classrooms.value.filter((item) =>
    (!constraintForm.requiredCampusId || item.campusId === constraintForm.requiredCampusId) &&
    (!constraintForm.requiredBuildingId || item.buildingId === constraintForm.requiredBuildingId),
  ),
)
const filteredExperimentBuildings = computed(() =>
  constraintForm.experimentRequiredCampusId
    ? buildings.value.filter((item) => item.campusId === constraintForm.experimentRequiredCampusId)
    : buildings.value,
)
const filteredExperimentClassrooms = computed(() =>
  classrooms.value.filter((item) =>
    (!constraintForm.experimentRequiredCampusId ||
      item.campusId === constraintForm.experimentRequiredCampusId) &&
    (!constraintForm.experimentRequiredBuildingId ||
      item.buildingId === constraintForm.experimentRequiredBuildingId) &&
    (!(constraintForm.allowedExperimentVenueNatures ?? []).length ||
      (venueNatureValue(item.teachingVenueNature) & (constraintForm.allowedExperimentVenueNatures ?? [])
        .reduce((value: number, nature: number) => value | nature, 0)) !== 0),
  ),
)
const batchFilteredBuildings = computed(() =>
  constraintBatchForm.requiredCampusId
    ? buildings.value.filter((item) => item.campusId === constraintBatchForm.requiredCampusId)
    : buildings.value,
)
const batchFilteredClassrooms = computed(() =>
  classrooms.value.filter((item) =>
    (!constraintBatchForm.requiredCampusId ||
      item.campusId === constraintBatchForm.requiredCampusId) &&
    (!constraintBatchForm.requiredBuildingId ||
      item.buildingId === constraintBatchForm.requiredBuildingId),
  ),
)
const batchFilteredExperimentBuildings = computed(() =>
  constraintBatchForm.experimentRequiredCampusId
    ? buildings.value.filter((item) => item.campusId === constraintBatchForm.experimentRequiredCampusId)
    : buildings.value,
)
const batchFilteredExperimentClassrooms = computed(() =>
  classrooms.value.filter((item) =>
    (!constraintBatchForm.experimentRequiredCampusId ||
      item.campusId === constraintBatchForm.experimentRequiredCampusId) &&
    (!constraintBatchForm.experimentRequiredBuildingId ||
      item.buildingId === constraintBatchForm.experimentRequiredBuildingId) &&
    (!(constraintBatchForm.allowedExperimentVenueNatures ?? []).length ||
      (venueNatureValue(item.teachingVenueNature) &
        (constraintBatchForm.allowedExperimentVenueNatures ?? [])
          .reduce((value: number, nature: number) => value | nature, 0)) !== 0),
  ),
)
const filteredEntries = computed(() => {
  const text = keyword.value.trim().toLowerCase()
  if (!text) return selected.value?.entries ?? []
  return (selected.value?.entries ?? []).filter((entry: any) =>
    [
      entry.taskNumber,
      entry.taskName,
      entry.courseCode,
      entry.courseName,
      entry.classroomName,
      ...entry.teacherNames,
      ...entry.classNames,
    ].some((value) => String(value).toLowerCase().includes(text)),
  )
})

function entriesAt(day: number, period: number) {
  return filteredEntries.value.filter(
    (entry: any) => entry.dayOfWeek === day && entry.startPeriod === period,
  )
}

function timeLabel(period: number) {
  const slot = timeSlots.value.find((item) => item.periodNumber === period)
  return slot ? `${slot.startsAt}—${slot.endsAt}` : `第 ${period} 节`
}

async function loadSchedulingSettings() {
  if (!termId.value) return
  try {
    const [timeRes, constraintRes] = await Promise.all([
      http.get('/schedules/time-slots', { params: { academicTermId: termId.value } }),
      http.get('/schedules/constraints', { params: { academicTermId: termId.value } }),
    ])
    timeSlots.value = timeRes.data
    constraints.value = constraintRes.data
    tasks.value = constraintRes.data.filter(
      (item: any) => item.schedulingMode === 'Standard',
    )
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  }
}

async function changeTerm() {
  await Promise.all([loadPlans(false), loadSchedulingSettings()])
}

function initializeTimeSlots() {
  const defaults = [
    ['08:00', '08:45'], ['08:55', '09:40'], ['10:00', '10:45'], ['10:55', '11:40'],
    ['14:00', '14:45'], ['14:55', '15:40'], ['16:00', '16:45'], ['16:55', '17:40'],
    ['19:00', '19:45'], ['19:55', '20:40'],
  ]
  timeSlots.value = defaults.map(([startsAt, endsAt], index) => ({
    periodNumber: index + 1,
    name: `第 ${index + 1} 节`,
    startsAt,
    endsAt,
    isEnabled: true,
  }))
}

function addTimeSlot() {
  const last = timeSlots.value.at(-1)
  timeSlots.value.push({
    periodNumber: (last?.periodNumber ?? 0) + 1,
    name: `第 ${(last?.periodNumber ?? 0) + 1} 节`,
    startsAt: '08:00',
    endsAt: '08:45',
    isEnabled: true,
  })
}

async function saveTimeSlots() {
  try {
    await http.put(`/schedules/time-slots/${termId.value}`, timeSlots.value)
    ElMessage.success('上课时间表已保存')
    await loadSchedulingSettings()
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  }
}

function openConstraint(item: any) {
  Object.assign(constraintForm, {
    teachingTaskId: item.id,
    title: `${item.taskNumber} · ${item.name}`,
    coursePracticeHours: item.coursePracticeHours,
    schedulingMode: item.schedulingMode,
    requiresClassroom: item.requiresClassroom,
    requiredCampusId: item.requiredCampusId,
    requiredBuildingId: item.requiredBuildingId,
    experimentRequiredCampusId: item.experimentRequiredCampusId,
    experimentRequiredBuildingId: item.experimentRequiredBuildingId,
    allowedClassroomIds: [...item.allowedClassroomIds],
    allowedExperimentClassroomIds: [...item.allowedExperimentClassroomIds],
    allowedExperimentVenueNatures: experimentVenueNatures
      .filter((nature) => (Number(item.allowedExperimentVenueNatures) & nature.value) !== 0)
      .map((nature) => nature.value),
    allowedDayOfWeeks: item.allowedDayOfWeeks.length
      ? [...item.allowedDayOfWeeks]
      : [1, 2, 3, 4, 5],
    earliestPeriod: item.earliestPeriod,
    latestPeriod: item.latestPeriod,
  })
  constraintDialog.value = true
}

async function saveConstraint() {
  try {
    const payload = {
      schedulingMode: constraintForm.schedulingMode,
      requiresClassroom: constraintForm.requiresClassroom,
      requiredCampusId: constraintForm.requiredCampusId || null,
      requiredBuildingId: constraintForm.requiredBuildingId || null,
      experimentRequiredCampusId: constraintForm.experimentRequiredCampusId || null,
      experimentRequiredBuildingId: constraintForm.experimentRequiredBuildingId || null,
      allowedClassroomIds: constraintForm.allowedClassroomIds ?? [],
      allowedExperimentClassroomIds: constraintForm.allowedExperimentClassroomIds ?? [],
      allowedExperimentVenueNatures: (constraintForm.allowedExperimentVenueNatures ?? [])
        .reduce((value: number, nature: number) => value | nature, 0),
      allowedDayOfWeeks: constraintForm.allowedDayOfWeeks ?? [],
      earliestPeriod: constraintForm.earliestPeriod || null,
      latestPeriod: constraintForm.latestPeriod || null,
    }
    await http.put(`/schedules/constraints/${constraintForm.teachingTaskId}`, payload)
    constraintDialog.value = false
    ElMessage.success('排课约束已保存')
    await loadSchedulingSettings()
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  }
}

function resetConstraintFilters() {
  Object.assign(constraintFilters, {
    keyword: '',
    collegeId: undefined,
    schedulingMode: undefined,
    classroomMode: undefined,
    constraintState: undefined,
  })
}

function openConstraintBatch() {
  if (!filteredConstraints.value.length) {
    ElMessage.warning('当前筛选结果中没有可修改的教学任务。')
    return
  }
  Object.keys(constraintBatchForm).forEach((key) => delete constraintBatchForm[key])
  Object.assign(constraintBatchForm, {
    updateSchedulingMode: false,
    schedulingMode: 'Standard',
    updateRequiresClassroom: false,
    requiresClassroom: true,
    updateClassroomScope: false,
    requiredCampusId: undefined,
    requiredBuildingId: undefined,
    allowedClassroomIds: [],
    updateExperimentClassroomScope: false,
    experimentRequiredCampusId: undefined,
    experimentRequiredBuildingId: undefined,
    allowedExperimentVenueNatures: [],
    allowedExperimentClassroomIds: [],
    updateDays: false,
    allowedDayOfWeeks: [1, 2, 3, 4, 5],
    updatePeriodRange: false,
    earliestPeriod: undefined,
    latestPeriod: undefined,
  })
  constraintBatchDialog.value = true
}

async function saveConstraintBatch() {
  if (!constraintBatchForm.updateSchedulingMode &&
       !constraintBatchForm.updateRequiresClassroom &&
       !constraintBatchForm.updateClassroomScope &&
       !constraintBatchForm.updateExperimentClassroomScope &&
      !constraintBatchForm.updateDays &&
      !constraintBatchForm.updatePeriodRange) {
    ElMessage.warning('请至少勾选一项需要批量修改的设置。')
    return
  }
  const targets = [...filteredConstraints.value]
  try {
    await ElMessageBox.confirm(
      `将修改当前筛选到的 ${targets.length} 个教学任务，确定继续吗？`,
      '批量修改排课约束',
      { type: 'warning', confirmButtonText: '修改当前结果', cancelButtonText: '取消' },
    )
    constraintBatchSaving.value = true
    const flexible = constraintBatchForm.updateSchedulingMode &&
      constraintBatchForm.schedulingMode === 'Flexible'
    const updateClassroomScope = !flexible &&
      !(constraintBatchForm.updateRequiresClassroom &&
        !constraintBatchForm.requiresClassroom) &&
      constraintBatchForm.updateClassroomScope
    const updateExperimentClassroomScope = !flexible &&
      !(constraintBatchForm.updateRequiresClassroom && !constraintBatchForm.requiresClassroom) &&
      constraintBatchForm.updateExperimentClassroomScope
    const { data } = await http.put('/schedules/constraints/batch', {
      academicTermId: termId.value,
      teachingTaskIds: targets.map((item) => item.id),
      schedulingMode: constraintBatchForm.updateSchedulingMode
        ? constraintBatchForm.schedulingMode
        : null,
      requiresClassroom: !flexible && constraintBatchForm.updateRequiresClassroom
        ? constraintBatchForm.requiresClassroom
        : null,
      updateClassroomScope,
      requiredCampusId: updateClassroomScope
        ? constraintBatchForm.requiredCampusId || null
        : null,
      requiredBuildingId: updateClassroomScope
        ? constraintBatchForm.requiredBuildingId || null
        : null,
      allowedClassroomIds: updateClassroomScope
        ? constraintBatchForm.allowedClassroomIds
        : null,
      updateExperimentClassroomScope,
      experimentRequiredCampusId: updateExperimentClassroomScope
        ? constraintBatchForm.experimentRequiredCampusId || null
        : null,
      experimentRequiredBuildingId: updateExperimentClassroomScope
        ? constraintBatchForm.experimentRequiredBuildingId || null
        : null,
      allowedExperimentVenueNatures: updateExperimentClassroomScope
        ? (constraintBatchForm.allowedExperimentVenueNatures ?? [])
          .reduce((value: number, nature: number) => value | nature, 0)
        : null,
      allowedExperimentClassroomIds: updateExperimentClassroomScope
        ? constraintBatchForm.allowedExperimentClassroomIds
        : null,
      allowedDayOfWeeks: !flexible && constraintBatchForm.updateDays
        ? constraintBatchForm.allowedDayOfWeeks
        : null,
      updatePeriodRange: !flexible && constraintBatchForm.updatePeriodRange,
      earliestPeriod: !flexible && constraintBatchForm.updatePeriodRange
        ? constraintBatchForm.earliestPeriod || null
        : null,
      latestPeriod: !flexible && constraintBatchForm.updatePeriodRange
        ? constraintBatchForm.latestPeriod || null
        : null,
    })
    constraintBatchDialog.value = false
    ElMessage.success(`已批量更新 ${data.affectedCount} 个教学任务`)
    await loadSchedulingSettings()
  } catch (error: any) {
    if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error))
  } finally {
    constraintBatchSaving.value = false
  }
}

function openManualHandling() {
  settingsTab.value = 'constraints'
  settingsDrawer.value = true
}

async function autoSchedule() {
  try {
    await ElMessageBox.confirm(
      '系统会保留当前手工安排，同时补齐理论课和实验课。实验课仅使用实验室、实训室、机房等场地；生成后仍可手工调整。',
      '开始自动排课',
      { type: 'warning', confirmButtonText: '生成排课', cancelButtonText: '取消' },
    )
    const planId = selected.value.id
    const { data } = await http.post(`/schedules/plans/${planId}/auto-schedule`)
    autoJob.value = data
    ElMessage.success('自动排课任务已提交，可留在当前页面查看进度')
    scheduleAutoSchedulePoll(data.id, planId)
  } catch (error: any) {
    if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error))
  }
}

function clearPublishPoll() {
  if (publishPollTimer) clearTimeout(publishPollTimer)
  publishPollTimer = undefined
}

function schedulePublishPoll(jobId: string, planId: string) {
  clearPublishPoll()
  publishPollTimer = setTimeout(() => pollPublishJob(jobId, planId), 1000)
}

async function pollPublishJob(jobId: string, planId: string) {
  try {
    const { data } = await http.get(`/schedules/publish-jobs/${jobId}`)
    if (selected.value?.id !== planId) return
    publishJob.value = data
    if (data.status === 'Queued' || data.status === 'Running') {
      schedulePublishPoll(jobId, planId)
      return
    }
    clearPublishPoll()
    if (data.status === 'Succeeded') {
      ElMessage.success('课表检查通过，已发布')
      await loadPlans()
    } else {
      ElMessage.error(data.errorMessage || '课表检查未通过，请调整后重试')
    }
  } catch {
    if (selected.value?.id === planId) {
      schedulePublishPoll(jobId, planId)
    }
  }
}

async function resumePublish(planId: string) {
  clearPublishPoll()
  publishJob.value = null
  const { data } = await http.get(`/schedules/plans/${planId}/publish-job`)
  if (selected.value?.id !== planId || !data) return
  publishJob.value = data
  if (data.status === 'Queued' || data.status === 'Running') {
    schedulePublishPoll(data.id, planId)
  }
}

function clearAutoSchedulePoll() {
  if (autoPollTimer) clearTimeout(autoPollTimer)
  autoPollTimer = undefined
}

function scheduleAutoSchedulePoll(jobId: string, planId: string) {
  clearAutoSchedulePoll()
  autoPollTimer = setTimeout(() => pollAutoScheduleJob(jobId, planId), 1000)
}

async function pollAutoScheduleJob(jobId: string, planId: string) {
  try {
    const { data } = await http.get(`/schedules/auto-schedule-jobs/${jobId}`)
    if (selected.value?.id !== planId) return
    autoJob.value = data
    if (data.status === 'Queued' || data.status === 'Running') {
      scheduleAutoSchedulePoll(jobId, planId)
      return
    }
    clearAutoSchedulePoll()
    if (data.status === 'Succeeded') {
      await loadDetail(planId, false)
      const summary = plans.value.find((item) => item.id === planId)
      if (summary) summary.entryCount = selected.value.entries.length
      if (data.messages.length) {
        ElMessage.warning(
          `后台排课完成，已生成 ${data.createdEntries} 条安排，仍有 ${data.messages.length} 个任务需人工处理`,
        )
      } else {
        ElMessage.success(`后台排课完成，共生成 ${data.createdEntries} 条安排`)
      }
    } else {
      ElMessage.error(data.errorMessage || '后台排课失败，请稍后重试')
    }
  } catch {
    if (selected.value?.id === planId) {
      scheduleAutoSchedulePoll(jobId, planId)
    }
  }
}

async function resumeAutoSchedule(planId: string) {
  clearAutoSchedulePoll()
  autoJob.value = null
  const { data } = await http.get(`/schedules/plans/${planId}/auto-schedule-job`)
  if (selected.value?.id !== planId || !data) return
  autoJob.value = data
  scheduleAutoSchedulePoll(data.id, planId)
}

async function loadPlans(keepSelection = true) {
  loading.value = true
  try {
    plans.value = (await http.get('/schedules/plans', {
      params: { academicTermId: termId.value },
    })).data
    const id = keepSelection && selected.value
      ? selected.value.id
      : plans.value[0]?.id
    if (id && plans.value.some((item) => item.id === id)) {
      await loadDetail(id)
    } else {
      selected.value = null
      clearAutoSchedulePoll()
      autoJob.value = null
      clearPublishPoll()
      publishJob.value = null
    }
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  } finally {
    loading.value = false
  }
}

async function loadDetail(id: string, resumeJob = true) {
  detailLoading.value = true
  try {
    selected.value = (await http.get(`/schedules/plans/${id}`)).data
    if (resumeJob && selected.value.status === 'Draft') {
      await Promise.all([resumeAutoSchedule(id), resumePublish(id)])
    } else if (resumeJob) {
      clearAutoSchedulePoll()
      autoJob.value = null
      await resumePublish(id)
    }
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  } finally {
    detailLoading.value = false
  }
}

function openPlan(plan?: any) {
  editingPlanId.value = plan?.id ?? ''
  Object.assign(planForm, {
    academicTermId: plan?.academicTermId ?? termId.value,
    name: plan?.name ?? '',
    version: plan?.version ?? `V${plans.value.length + 1}`,
    notes: plan?.notes ?? '',
  })
  planDialog.value = true
}

async function savePlan() {
  if (!planForm.academicTermId || !planForm.name?.trim() || !planForm.version?.trim()) {
    ElMessage.warning('请填写学期、版本名称和版本号。')
    return
  }
  try {
    if (editingPlanId.value) {
      await http.put(`/schedules/plans/${editingPlanId.value}`, planForm)
    } else {
      await http.post('/schedules/plans', planForm)
    }
    planDialog.value = false
    await loadPlans(false)
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  }
}

function openClone() {
  revisingPublishedPlan.value = selected.value.status === 'Published'
  Object.assign(cloneForm, {
    name: `${selected.value.name}（修订版）`,
    version: `V${plans.value.length + 1}`,
  })
  cloneDialog.value = true
}

async function clonePlan() {
  try {
    const { data } = await http.post(
      `/schedules/plans/${selected.value.id}/clone`,
      cloneForm,
    )
    cloneDialog.value = false
    ElMessage.success(
      revisingPublishedPlan.value
        ? '已建立修订草稿，原已发布课表继续生效'
        : '已复制为可调整的草稿版本',
    )
    await loadPlans(false)
    if (data.id && selected.value?.id !== data.id) await loadDetail(data.id)
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  }
}

async function publishPlan() {
  try {
    await ElMessageBox.confirm(
      '系统将再次检查全部教师、行政班和教室冲突；发布后本版本锁定并替换旧课表，后续仍可通过“发布后修改”建立修订版。',
      '发布课表',
      { type: 'warning', confirmButtonText: '检查并发布', cancelButtonText: '取消' },
    )
    const planId = selected.value.id
    const { data } = await http.post(`/schedules/plans/${planId}/publish`)
    publishJob.value = data
    ElMessage.success('检查并发布任务已提交，可在当前页面查看进度')
    schedulePublishPoll(data.id, planId)
  } catch (error: any) {
    if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error))
  }
}

async function deletePlan() {
  try {
    await ElMessageBox.confirm('删除该草稿及全部排课条目？', '删除排课草稿', {
      type: 'warning',
    })
    await http.delete(`/schedules/plans/${selected.value.id}`)
    await loadPlans(false)
  } catch (error: any) {
    if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error))
  }
}

function openEntry(entry?: any, day?: number, period?: number) {
  editingEntryId.value = entry?.id ?? ''
  Object.assign(entryForm, {
    teachingTaskId: entry?.teachingTaskId,
    kind: entry?.kind ?? 'Lecture',
    classroomId: entry?.classroomId,
    dayOfWeek: entry?.dayOfWeek ?? day ?? 1,
    startPeriod: entry?.startPeriod ?? period ?? 1,
    periodCount: entry?.periodCount ?? 2,
    startWeek: entry?.startWeek ?? 1,
    endWeek: entry?.endWeek ?? 16,
    weekPattern: entry?.weekPattern ?? 'All',
    notes: entry?.notes ?? '',
  })
  entryDialog.value = true
}

function changeEntryTask() {
  if (editingEntryId.value) return
  const task = selectedTaskConstraint.value
  if (!task) return
  entryForm.startWeek = task.startWeek
  entryForm.endWeek = task.endWeek
  if (task.allowedDayOfWeeks?.length &&
      !task.allowedDayOfWeeks.includes(entryForm.dayOfWeek)) {
    entryForm.dayOfWeek = task.allowedDayOfWeeks[0]
  }
  if (task.requiresClassroom === false && entryForm.kind !== 'Experiment') {
    entryForm.classroomId = null
    return
  }
  const room = classrooms.value.find((item) => item.id === entryForm.classroomId)
  if (room && (
    (entryForm.kind !== 'Experiment' && task.requiredCampusId && room.campusId !== task.requiredCampusId) ||
    (entryForm.kind !== 'Experiment' && task.requiredBuildingId && room.buildingId !== task.requiredBuildingId) ||
    (entryForm.kind !== 'Experiment' && task.allowedClassroomIds?.length && !task.allowedClassroomIds.includes(room.id)) ||
    (entryForm.kind === 'Experiment' && task.experimentRequiredCampusId && room.campusId !== task.experimentRequiredCampusId) ||
    (entryForm.kind === 'Experiment' && task.experimentRequiredBuildingId && room.buildingId !== task.experimentRequiredBuildingId) ||
    (entryForm.kind === 'Experiment' && task.allowedExperimentClassroomIds?.length &&
      !task.allowedExperimentClassroomIds.includes(room.id))
  )) {
    entryForm.classroomId = null
  }
}

function changeEntryKind() {
  const room = classrooms.value.find((item) => item.id === entryForm.classroomId)
  const task = selectedTaskConstraint.value
  if (entryForm.kind === 'Experiment' && room && task?.allowedExperimentClassroomIds?.length &&
      !task.allowedExperimentClassroomIds.includes(room.id)) {
    entryForm.classroomId = null
  }
}

async function preflightSchedule() {
  try {
    const { data } = await http.get(`/schedules/plans/${selected.value.id}/preflight`)
    const message = data.unscheduledTasks
      ? `尚有 ${data.unscheduledTasks} 个教学班未安排：\n${data.messages.join('\n')}`
      : '当前草稿已覆盖全部需要排课的教学班。'
    await ElMessageBox.alert(message, `排课前检查 · 已覆盖 ${data.scheduledTasks}/${data.totalTasks}`, { confirmButtonText: '知道了' })
  } catch (error) { ElMessage.error(apiErrorMessage(error)) }
}

async function saveEntry() {
  if (!entryForm.teachingTaskId ||
      ((entryForm.kind === 'Experiment' ||
        selectedTaskConstraint.value?.requiresClassroom !== false) &&
        !entryForm.classroomId)) {
    ElMessage.warning('请选择教学任务，并按课程要求选择教室。')
    return
  }
  const task = selectedTaskConstraint.value
  if (task && (
    entryForm.startWeek < task.startWeek ||
    entryForm.endWeek > task.endWeek
  )) {
    ElMessage.warning(
      `排课周次必须位于该教学任务的第 ${task.startWeek}—${task.endWeek} 周内。`,
    )
    return
  }
  if (task?.allowedDayOfWeeks?.length &&
      !task.allowedDayOfWeeks.includes(entryForm.dayOfWeek)) {
    ElMessage.warning('所选星期不在该教学任务允许的上课日内。')
    return
  }
  if (entryForm.kind !== 'Experiment' &&
      selectedTaskConstraint.value?.requiresClassroom === false) entryForm.classroomId = null
  try {
    const base = `/schedules/plans/${selected.value.id}/entries`
    if (editingEntryId.value) await http.put(`${base}/${editingEntryId.value}`, entryForm)
    else await http.post(base, entryForm)
    entryDialog.value = false
    ElMessage.success(editingEntryId.value ? '排课已调整' : '排课已添加')
    await loadDetail(selected.value.id)
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  }
}

async function deleteEntry(entry: any) {
  try {
    await ElMessageBox.confirm(`移除“${entry.taskName}”的这条安排？`, '移除排课', {
      type: 'warning',
    })
    await http.delete(`/schedules/plans/${selected.value.id}/entries/${entry.id}`)
    await loadDetail(selected.value.id)
  } catch (error: any) {
    if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error))
  }
}

onMounted(async () => {
  const [termRes, classroomRes, campusRes, buildingRes] = await Promise.all([
    http.get('/base-data/terms'),
    http.get('/base-data/classrooms'),
    http.get('/base-data/campuses'),
    http.get('/base-data/buildings'),
  ])
  terms.value = termRes.data
  classrooms.value = classroomRes.data.filter((item: any) => item.isEnabled)
  campuses.value = campusRes.data.filter((item: any) => item.isEnabled)
  buildings.value = buildingRes.data.filter((item: any) => item.isEnabled)
  termId.value = defaultAcademicTermId(terms.value)
  await Promise.all([loadPlans(false), loadSchedulingSettings()])
})

onBeforeUnmount(() => {
  clearAutoSchedulePoll()
  clearPublishPoll()
})
</script>

<template>
  <div class="page-stack">
    <section class="page-intro">
      <div>
        <span class="section-kicker">TIMETABLE BOARD</span>
        <h2>排课与课表</h2>
        <p>用作息时间和课程约束驱动教师、班级、场地自动分配，生成后仍可逐项微调。</p>
      </div>
      <div class="page-actions">
        <el-button :icon="Setting" @click="settingsDrawer = true">排课规则与作息</el-button>
        <el-button type="primary" :icon="Plus" @click="openPlan()">新建排课版本</el-button>
      </div>
    </section>

    <section class="schedule-toolbar">
      <el-select v-model="termId" placeholder="选择学期" @change="changeTerm">
        <el-option v-for="item in terms" :key="item.id" :label="academicTermLabel(item)" :value="item.id" :class="academicTermOptionClass(item)" />
      </el-select>
      <div class="schedule-version-strip">
        <button
          v-for="plan in plans"
          :key="plan.id"
          type="button"
          :class="{ active: selected?.id === plan.id }"
          @click="loadDetail(plan.id)"
        >
          <b>{{ plan.version }}</b>
          <span>{{ statusLabels[plan.status] }}</span>
          <small>{{ plan.entryCount }} 条安排</small>
        </button>
        <span v-if="plans.length === 0">该学期还没有排课版本</span>
      </div>
    </section>

    <section v-if="selected" v-loading="detailLoading" class="schedule-sheet">
      <header class="schedule-sheet-head">
        <div>
          <span>{{ selected.termName }} · {{ selected.version }}</span>
          <h3>{{ selected.name }}</h3>
          <p>共 {{ selected.entries.length }} 条安排 · {{ statusLabels[selected.status] }}</p>
        </div>
        <div class="plan-actions">
          <el-button v-if="isDraft" :disabled="scheduleJobLoading" @click="openPlan(selected)">编辑版本</el-button>
          <el-button
            v-if="selected.status === 'Published'"
            type="warning"
            plain
            :icon="EditPen"
            @click="openClone"
          >
            发布后修改
          </el-button>
          <el-button
            v-else
            :icon="CopyDocument"
            :disabled="scheduleJobLoading"
            @click="openClone"
          >
            复制调整
          </el-button>
          <el-button
            v-if="isDraft"
            :disabled="scheduleJobLoading"
            @click="preflightSchedule"
          >
            排课前检查
          </el-button>
          <el-button
            v-if="isDraft"
            type="warning"
            plain
            :icon="Promotion"
            :loading="autoLoading"
            :disabled="publishLoading"
            @click="autoSchedule"
          >
            自动排课
          </el-button>
          <el-button v-if="isDraft" type="primary" :icon="Plus" :disabled="scheduleJobLoading" @click="openEntry()">添加排课</el-button>
          <el-button
            v-if="isDraft"
            type="success"
            :icon="Promotion"
            :loading="publishLoading"
            :disabled="autoLoading"
            @click="publishPlan"
          >
            {{ publishLoading ? '检查并发布中' : '发布课表' }}
          </el-button>
          <el-button v-if="isDraft" type="danger" plain :disabled="scheduleJobLoading" @click="deletePlan">删除草稿</el-button>
        </div>
      </header>

      <div
        v-if="autoJob"
        class="auto-schedule-progress"
        :class="`is-${String(autoJob.status).toLowerCase()}`"
      >
        <div>
          <b>{{ autoStatusText }}</b>
          <span v-if="autoJob.status === 'Running'">
            后台运行中，离开页面不会中断任务
          </span>
          <span v-else-if="autoJob.status === 'Succeeded' && autoJob.messages.length">
            {{ autoJob.messages.length }} 个教学任务仍需人工处理
          </span>
        </div>
        <el-progress
          :percentage="autoProgress"
          :status="autoProgressStatus"
          :indeterminate="autoJob.status === 'Queued'"
          :duration="2"
        />
        <div
          v-if="autoJob.status === 'Succeeded' && autoJob.messages.length"
          class="auto-schedule-issues"
        >
          <div class="auto-schedule-issues-head">
            <b>未完成任务与处理建议</b>
            <el-button size="small" type="warning" plain @click="openManualHandling">
              检查排课约束
            </el-button>
          </div>
          <ol>
            <li v-for="message in autoJob.messages" :key="message">{{ message }}</li>
          </ol>
        </div>
      </div>

      <div
        v-if="publishJob"
        class="auto-schedule-progress"
        :class="`is-${String(publishJob.status).toLowerCase()}`"
      >
        <div>
          <b>{{ publishStatusText }}</b>
          <span v-if="publishLoading">后台运行中，离开页面不会中断任务</span>
          <span v-else-if="publishJob.status === 'Failed'">
            草稿未发布，请根据上方原因调整后重试
          </span>
        </div>
        <el-progress
          :percentage="publishProgress"
          :status="publishProgressStatus"
          :indeterminate="publishJob.status === 'Queued'"
          :duration="2"
        />
      </div>

      <div class="schedule-search">
        <el-input v-model="keyword" :prefix-icon="Search" clearable placeholder="筛选课程、教师、行政班或教室" />
        <el-button :icon="Refresh" @click="keyword = ''">清除筛选</el-button>
        <span>单双周和周次范围显示在课程卡片内</span>
      </div>

      <div class="timetable-scroll">
        <div class="timetable-grid">
          <div class="timetable-corner">节次</div>
          <div v-for="day in weekdays" :key="day.value" class="timetable-day">{{ day.label }}</div>
          <template v-for="period in periods" :key="period">
            <div class="timetable-period">
              <b>{{ period }}</b>
              <span>{{ timeLabel(period) }}</span>
            </div>
            <div
              v-for="day in weekdays"
              :key="`${day.value}-${period}`"
              class="timetable-cell"
              :class="{ editable: isDraft }"
              @dblclick="isDraft && openEntry(undefined, day.value, period)"
            >
              <article
                v-for="entry in entriesAt(day.value, period)"
                :key="entry.id"
                class="schedule-card"
                :class="{ readonly: !isDraft, experiment: entry.kind === 'Experiment' }"
                @click="isDraft && openEntry(entry)"
              >
                <span>
                  {{ entry.kind === 'Experiment' ? '实验课' : '理论课' }} ·
                  {{ entry.courseCode }} · {{ patternLabels[entry.weekPattern] }}
                </span>
                <b>{{ entry.courseName }}</b>
                <small>{{ entry.teacherNames.join('、') }} · {{ entry.classroomName || '不占用教室' }}</small>
                <i>{{ entry.startWeek }}—{{ entry.endWeek }} 周 / 连上 {{ entry.periodCount }} 节</i>
                <button v-if="isDraft" type="button" @click.stop="deleteEntry(entry)">×</button>
              </article>
              <span v-if="isDraft && entriesAt(day.value, period).length === 0" class="cell-add">双击添加</span>
            </div>
          </template>
        </div>
      </div>
    </section>
    <el-empty v-else description="请选择或新建一个排课版本" />

    <el-dialog v-model="planDialog" :title="editingPlanId ? '编辑排课版本' : '新建排课版本'" width="560px">
      <el-form label-position="top">
        <el-form-item label="学期" required><el-select v-model="planForm.academicTermId"><el-option v-for="item in terms" :key="item.id" :label="academicTermLabel(item)" :value="item.id" :class="academicTermOptionClass(item)" /></el-select></el-form-item>
        <el-form-item label="版本名称" required><el-input v-model="planForm.name" placeholder="如：第一轮正式课表" /></el-form-item>
        <el-form-item label="版本号" required><el-input v-model="planForm.version" placeholder="如 V1" /></el-form-item>
        <el-form-item label="备注"><el-input v-model="planForm.notes" type="textarea" :rows="2" /></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>

    <el-dialog
      v-model="cloneDialog"
      :title="revisingPublishedPlan ? '发布后修改课表' : '复制排课版本'"
      width="520px"
    >
      <el-alert
        v-if="revisingPublishedPlan"
        title="系统会建立一份可编辑的修订草稿；原课表在修订版重新发布前继续生效，不会影响教师和学生查课。"
        type="info"
        :closable="false"
        show-icon
      />
      <el-form label-position="top">
        <el-form-item label="新版本名称" required><el-input v-model="cloneForm.name" /></el-form-item>
        <el-form-item label="新版本号" required><el-input v-model="cloneForm.version" /></el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="cloneDialog = false">取消</el-button>
        <el-button type="primary" @click="clonePlan">
          {{ revisingPublishedPlan ? '建立修订草稿' : '复制' }}
        </el-button>
      </template>
    </el-dialog>

    <el-dialog v-model="entryDialog" :title="editingEntryId ? '调整排课' : '添加排课'" width="680px">
      <el-form label-position="top">
        <el-form-item label="教学任务" required>
          <el-select v-model="entryForm.teachingTaskId" filterable @change="changeEntryTask">
            <el-option v-for="item in tasks" :key="item.id" :label="`${item.taskNumber} · ${item.name}`" :value="item.id" />
          </el-select>
        </el-form-item>
        <el-form-item label="课次类型" required>
          <el-radio-group v-model="entryForm.kind" @change="changeEntryKind">
            <el-radio-button value="Lecture">理论课</el-radio-button>
            <el-radio-button value="Experiment" :disabled="!selectedTaskConstraint?.coursePracticeHours">
              实验课
            </el-radio-button>
          </el-radio-group>
        </el-form-item>
        <el-alert
          v-if="selectedTaskConstraint"
          :title="`课程共 ${selectedTaskConstraint.courseTotalHours} 学时：理论 ${selectedTaskConstraint.courseTotalHours - selectedTaskConstraint.coursePracticeHours} 学时、实验 ${selectedTaskConstraint.coursePracticeHours} 学时，均须在本课表排足；可排第 ${selectedTaskConstraint.startWeek}—${selectedTaskConstraint.endWeek} 周；允许上课日：${entryWeekdays.map((day) => day.label).join('、')}`"
          type="info"
          :closable="false"
          show-icon
        />
        <el-alert
          v-if="selectedTaskConstraint?.requiresClassroom === false && entryForm.kind !== 'Experiment'"
          title="该课程不占用教室，仍会校验教师和行政班时间冲突。"
          type="info"
          :closable="false"
          show-icon
        />
        <el-form-item
          v-else
           :label="entryForm.kind === 'Experiment' ? '教学场地' : '教室'"
          required
          :hint="selectedTaskConstraint?.requiredBuildingId ? '仅显示约束范围内教室' : ''"
        >
          <el-select v-model="entryForm.classroomId" filterable>
            <el-option
              v-for="item in entryClassrooms"
              :key="item.id"
              :label="`${item.campusName} / ${item.buildingName} / ${item.name}（${item.roomType}，${item.capacity}人）`"
              :value="item.id"
            />
          </el-select>
        </el-form-item>
        <div class="form-grid three">
          <el-form-item label="星期"><el-select v-model="entryForm.dayOfWeek"><el-option v-for="day in entryWeekdays" :key="day.value" :label="day.label" :value="day.value" /></el-select></el-form-item>
          <el-form-item label="开始节次">
            <el-select v-model="entryForm.startPeriod">
              <el-option
                v-for="period in periods"
                :key="period"
                :label="`第 ${period} 节 · ${timeLabel(period)}`"
                :value="period"
              />
            </el-select>
          </el-form-item>
          <el-form-item label="连续节数"><el-input-number v-model="entryForm.periodCount" :min="1" :max="6" /></el-form-item>
        </div>
        <div class="form-grid three">
          <el-form-item label="开始周"><el-input-number v-model="entryForm.startWeek" :min="1" :max="30" /></el-form-item>
          <el-form-item label="结束周"><el-input-number v-model="entryForm.endWeek" :min="1" :max="30" /></el-form-item>
          <el-form-item label="单双周"><el-select v-model="entryForm.weekPattern"><el-option v-for="(label, value) in patternLabels" :key="value" :label="label" :value="value" /></el-select></el-form-item>
        </div>
        <el-form-item label="备注"><el-input v-model="entryForm.notes" type="textarea" :rows="2" /></el-form-item>
      </el-form>
      <template #footer><el-button @click="entryDialog = false">取消</el-button><el-button type="primary" @click="saveEntry">检查冲突并保存</el-button></template>
    </el-dialog>

    <el-drawer v-model="settingsDrawer" title="排课规则与作息" size="760px" class="schedule-settings-drawer">
      <el-tabs v-model="settingsTab">
        <el-tab-pane label="上课时间表" name="time">
          <div class="settings-lead">
            <div>
              <b>上课时间表</b>
              <span>当前学期的节次、上下课时间和可排课状态。自动排课只使用启用节次。</span>
            </div>
            <div>
              <el-button v-if="!timeSlots.length" @click="initializeTimeSlots">载入常用作息</el-button>
              <el-button @click="addTimeSlot">增加节次</el-button>
              <el-button type="primary" @click="saveTimeSlots">保存时间表</el-button>
            </div>
          </div>
          <el-table :data="timeSlots" class="settings-table">
            <el-table-column label="节次" width="100">
              <template #default="{ row }"><el-input-number v-model="row.periodNumber" :min="1" :max="30" controls-position="right" /></template>
            </el-table-column>
            <el-table-column label="名称" min-width="130">
              <template #default="{ row }"><el-input v-model="row.name" /></template>
            </el-table-column>
            <el-table-column label="上课" width="130">
              <template #default="{ row }"><el-time-select v-model="row.startsAt" start="06:00" step="00:05" end="23:00" /></template>
            </el-table-column>
            <el-table-column label="下课" width="130">
              <template #default="{ row }"><el-time-select v-model="row.endsAt" start="06:00" step="00:05" end="23:00" /></template>
            </el-table-column>
            <el-table-column label="可排课" width="90">
              <template #default="{ row }"><el-switch v-model="row.isEnabled" /></template>
            </el-table-column>
            <el-table-column width="60">
              <template #default="{ $index }"><el-button link type="danger" @click="timeSlots.splice($index, 1)">移除</el-button></template>
            </el-table-column>
          </el-table>
        </el-tab-pane>

         <el-tab-pane label="课程排课约束" name="constraints">
           <div class="settings-lead">
             <div>
               <b>课程排课约束</b>
               <span>筛选后可批量修改当前结果；非排时课程不占用正常时间和场地。</span>
             </div>
             <div>
               <el-button @click="resetConstraintFilters">重置筛选</el-button>
               <el-button
                 type="primary"
                 :disabled="!filteredConstraints.length"
                 @click="openConstraintBatch"
               >批量修改当前结果（{{ filteredConstraints.length }}）</el-button>
             </div>
           </div>
           <div class="constraint-filter-grid">
             <el-input
               v-model="constraintFilters.keyword"
               clearable
               placeholder="任务、课程或教师"
               :prefix-icon="Search"
             />
             <el-select v-model="constraintFilters.collegeId" clearable placeholder="全部开课单位">
               <el-option v-for="item in constraintColleges" :key="item.id" :label="item.name" :value="item.id" />
             </el-select>
             <el-select v-model="constraintFilters.schedulingMode" clearable placeholder="全部授课方式">
               <el-option label="正常排课" value="Standard" />
               <el-option label="非排时课程" value="Flexible" />
             </el-select>
             <el-select v-model="constraintFilters.classroomMode" clearable placeholder="全部场地要求">
               <el-option label="需要教室" value="required" />
               <el-option label="不占教室" value="not-required" />
             </el-select>
             <el-select v-model="constraintFilters.constraintState" clearable placeholder="全部约束状态">
               <el-option label="已自定义约束" value="custom" />
               <el-option label="使用默认约束" value="default" />
             </el-select>
           </div>
           <div class="constraint-result-summary">
             共 {{ constraints.length }} 个教学任务，当前显示 {{ filteredConstraints.length }} 个
           </div>
           <div class="constraint-list">
             <article v-for="item in filteredConstraints" :key="item.id">
               <div>
                 <span>{{ item.taskNumber }} · 每周 {{ item.weeklyHours }} 学时</span>
                 <b>{{ item.name }} · {{ item.courseName }}</b>
                 <small>{{ item.collegeName }} · {{ item.teacherNames.join('、') || '未分配教师' }} · {{ item.capacity }} 人</small>
               </div>
               <div class="constraint-badges">
                 <el-tag v-if="item.schedulingMode === 'Flexible'" type="success">非排时课程</el-tag>
                 <el-tag :type="item.requiresClassroom ? 'primary' : 'info'">
                   {{ item.requiresClassroom ? '占用教室' : '不占教室' }}
                 </el-tag>
                <el-tag v-if="item.requiredBuildingId" type="warning">限定教学楼</el-tag>
                <el-tag v-if="item.allowedClassroomIds.length" type="warning">
                  指定 {{ item.allowedClassroomIds.length }} 间教室
                </el-tag>
              </div>
               <el-button @click="openConstraint(item)">设置约束</el-button>
             </article>
             <el-empty v-if="!filteredConstraints.length" description="没有符合当前筛选条件的教学任务" />
           </div>
        </el-tab-pane>
      </el-tabs>
    </el-drawer>

    <el-dialog v-model="constraintDialog" title="设置课程排课约束" width="720px">
       <div class="constraint-title">{{ constraintForm.title }}</div>
       <el-form label-position="top">
         <el-form-item label="授课方式">
           <el-radio-group v-model="constraintForm.schedulingMode">
             <el-radio-button value="Standard">正常排课</el-radio-button>
             <el-radio-button value="Flexible">非排时课程</el-radio-button>
           </el-radio-group>
           <small class="field-hint">非排时课程不进入自动排课，并在班级和个人课表中单独显示。</small>
         </el-form-item>
         <template v-if="constraintForm.schedulingMode === 'Standard'">
         <el-form-item>
          <el-switch
            v-model="constraintForm.requiresClassroom"
            active-text="需要占用教室"
            inactive-text="不占用教室"
          />
        </el-form-item>
        <template v-if="constraintForm.requiresClassroom">
          <div class="form-grid">
            <el-form-item label="限定校区">
              <el-select v-model="constraintForm.requiredCampusId" clearable @change="constraintForm.requiredBuildingId = undefined; constraintForm.allowedClassroomIds = []">
                <el-option v-for="item in campuses" :key="item.id" :label="item.name" :value="item.id" />
              </el-select>
            </el-form-item>
            <el-form-item label="限定教学楼">
              <el-select v-model="constraintForm.requiredBuildingId" clearable @change="constraintForm.allowedClassroomIds = []">
                <el-option v-for="item in filteredBuildings" :key="item.id" :label="item.name" :value="item.id" />
              </el-select>
            </el-form-item>
          </div>
           <el-form-item label="指定可用教室">
            <el-select v-model="constraintForm.allowedClassroomIds" multiple filterable collapse-tags>
              <el-option
                v-for="item in filteredClassrooms"
                :key="item.id"
                :label="`${item.buildingName} / ${item.name}（${item.capacity}人）`"
                :value="item.id"
              />
            </el-select>
           </el-form-item>
            <el-form-item label="实验课允许的场地性质">
             <el-checkbox-group v-model="constraintForm.allowedExperimentVenueNatures">
               <el-checkbox v-for="nature in experimentVenueNatures" :key="nature.value" :value="nature.value">{{ nature.label }}</el-checkbox>
             </el-checkbox-group>
              <small class="field-hint">仅约束实验课；不勾选时可使用全部实验教学场地。</small>
            </el-form-item>
            <section v-if="constraintForm.coursePracticeHours" class="experiment-classroom-scope">
              <div class="experiment-classroom-scope__title">实验课指定可用场地</div>
              <div class="form-grid">
                <el-form-item label="实验课限定校区">
                  <el-select v-model="constraintForm.experimentRequiredCampusId" clearable @change="constraintForm.experimentRequiredBuildingId = undefined; constraintForm.allowedExperimentClassroomIds = []">
                    <el-option v-for="item in campuses" :key="item.id" :label="item.name" :value="item.id" />
                  </el-select>
                </el-form-item>
                <el-form-item label="实验课限定教学楼">
                  <el-select v-model="constraintForm.experimentRequiredBuildingId" clearable @change="constraintForm.allowedExperimentClassroomIds = []">
                    <el-option v-for="item in filteredExperimentBuildings" :key="item.id" :label="item.name" :value="item.id" />
                  </el-select>
                </el-form-item>
              </div>
              <el-form-item label="实验课指定可用场地">
                <el-select v-model="constraintForm.allowedExperimentClassroomIds" multiple filterable collapse-tags>
                  <el-option
                    v-for="item in filteredExperimentClassrooms"
                    :key="item.id"
                    :label="`${item.buildingName} / ${item.name}（${item.roomType}，${item.capacity}人）`"
                    :value="item.id"
                  />
                </el-select>
                <small class="field-hint">可在上方场地性质范围内指定实验室；不选择时按场地性质自动筛选。</small>
              </el-form-item>
            </section>
        </template>
        <el-form-item label="允许上课日">
          <el-checkbox-group v-model="constraintForm.allowedDayOfWeeks">
            <el-checkbox v-for="day in weekdays" :key="day.value" :value="day.value">{{ day.label }}</el-checkbox>
          </el-checkbox-group>
        </el-form-item>
        <div class="form-grid">
          <el-form-item label="最早开始节次">
            <el-select v-model="constraintForm.earliestPeriod" clearable>
              <el-option v-for="period in periods" :key="period" :label="`第 ${period} 节 · ${timeLabel(period)}`" :value="period" />
            </el-select>
          </el-form-item>
          <el-form-item label="最晚结束节次">
            <el-select v-model="constraintForm.latestPeriod" clearable>
              <el-option v-for="period in periods" :key="period" :label="`第 ${period} 节 · ${timeLabel(period)}`" :value="period" />
            </el-select>
          </el-form-item>
         </div>
         </template>
       </el-form>
      <template #footer>
        <el-button @click="constraintDialog = false">取消</el-button>
        <el-button type="primary" @click="saveConstraint">保存约束</el-button>
      </template>
     </el-dialog>

    <el-dialog v-model="constraintBatchDialog" title="批量修改当前筛选结果" width="720px">
      <div class="constraint-title">
        将作用于当前筛选到的 {{ filteredConstraints.length }} 个教学任务
      </div>
      <el-alert
        title="仅勾选需要修改的项目，未勾选的设置保持原值。"
        type="info"
        :closable="false"
        show-icon
      />
      <el-form label-position="top" class="constraint-batch-form">
        <el-checkbox v-model="constraintBatchForm.updateSchedulingMode">修改授课方式</el-checkbox>
        <el-form-item v-if="constraintBatchForm.updateSchedulingMode" label="统一授课方式">
          <el-radio-group v-model="constraintBatchForm.schedulingMode">
            <el-radio-button value="Standard">正常排课</el-radio-button>
            <el-radio-button value="Flexible">非排时课程</el-radio-button>
          </el-radio-group>
        </el-form-item>
        <template v-if="!(constraintBatchForm.updateSchedulingMode && constraintBatchForm.schedulingMode === 'Flexible')">
          <el-checkbox v-model="constraintBatchForm.updateRequiresClassroom">修改场地要求</el-checkbox>
          <el-form-item v-if="constraintBatchForm.updateRequiresClassroom" label="统一场地要求">
            <el-switch
              v-model="constraintBatchForm.requiresClassroom"
              active-text="需要占用教室"
              inactive-text="不占用教室"
              @change="!constraintBatchForm.requiresClassroom && (constraintBatchForm.updateClassroomScope = false)"
            />
          </el-form-item>
          <template v-if="!(constraintBatchForm.updateRequiresClassroom && !constraintBatchForm.requiresClassroom)">
            <el-checkbox v-model="constraintBatchForm.updateClassroomScope">
              批量指定教室范围
            </el-checkbox>
             <div v-if="constraintBatchForm.updateClassroomScope" class="batch-classroom-scope">
              <el-alert
                title="将统一设置为需要教室；校区、教学楼和教室均不选择时，表示清除原有限定并允许系统自动分配。"
                type="warning"
                :closable="false"
                show-icon
              />
              <div class="form-grid">
                <el-form-item label="统一限定校区">
                  <el-select
                    v-model="constraintBatchForm.requiredCampusId"
                    clearable
                    @change="constraintBatchForm.requiredBuildingId = undefined; constraintBatchForm.allowedClassroomIds = []"
                  >
                    <el-option v-for="item in campuses" :key="item.id" :label="item.name" :value="item.id" />
                  </el-select>
                </el-form-item>
                <el-form-item label="统一限定教学楼">
                  <el-select
                    v-model="constraintBatchForm.requiredBuildingId"
                    clearable
                    @change="constraintBatchForm.allowedClassroomIds = []"
                  >
                    <el-option v-for="item in batchFilteredBuildings" :key="item.id" :label="item.name" :value="item.id" />
                  </el-select>
                </el-form-item>
             </div>
              <el-form-item label="统一指定可用教室">
                <el-select
                  v-model="constraintBatchForm.allowedClassroomIds"
                  multiple
                  filterable
                  collapse-tags
                  collapse-tags-tooltip
                  placeholder="不选择则允许范围内任意教室"
                >
                  <el-option
                    v-for="item in batchFilteredClassrooms"
                    :key="item.id"
                    :label="`${item.buildingName} / ${item.name}（${item.capacity}人）`"
                    :value="item.id"
                  />
                </el-select>
              </el-form-item>
            </div>
          </template>
          <el-checkbox v-model="constraintBatchForm.updateExperimentClassroomScope">
            批量指定实验课场地
          </el-checkbox>
          <div v-if="constraintBatchForm.updateExperimentClassroomScope" class="batch-classroom-scope">
            <el-alert
              title="仅作用于含实验学时的教学任务；不选择具体场地时，按场地性质自动分配。"
              type="warning"
              :closable="false"
              show-icon
            />
            <el-form-item label="统一实验课允许的场地性质">
              <el-checkbox-group v-model="constraintBatchForm.allowedExperimentVenueNatures">
                <el-checkbox v-for="nature in experimentVenueNatures" :key="nature.value" :value="nature.value">{{ nature.label }}</el-checkbox>
              </el-checkbox-group>
            </el-form-item>
            <div class="form-grid">
              <el-form-item label="统一实验课限定校区">
                <el-select
                  v-model="constraintBatchForm.experimentRequiredCampusId"
                  clearable
                  @change="constraintBatchForm.experimentRequiredBuildingId = undefined; constraintBatchForm.allowedExperimentClassroomIds = []"
                >
                  <el-option v-for="item in campuses" :key="item.id" :label="item.name" :value="item.id" />
                </el-select>
              </el-form-item>
              <el-form-item label="统一实验课限定教学楼">
                <el-select
                  v-model="constraintBatchForm.experimentRequiredBuildingId"
                  clearable
                  @change="constraintBatchForm.allowedExperimentClassroomIds = []"
                >
                  <el-option v-for="item in batchFilteredExperimentBuildings" :key="item.id" :label="item.name" :value="item.id" />
                </el-select>
              </el-form-item>
            </div>
            <el-form-item label="统一实验课指定可用场地">
              <el-select v-model="constraintBatchForm.allowedExperimentClassroomIds" multiple filterable collapse-tags placeholder="不选择则允许符合性质的任意实验场地">
                <el-option
                  v-for="item in batchFilteredExperimentClassrooms"
                  :key="item.id"
                  :label="`${item.buildingName} / ${item.name}（${item.roomType}，${item.capacity}人）`"
                  :value="item.id"
                />
              </el-select>
            </el-form-item>
          </div>
          <el-checkbox v-model="constraintBatchForm.updateDays">修改允许上课日</el-checkbox>
          <el-form-item v-if="constraintBatchForm.updateDays" label="统一允许上课日">
            <el-checkbox-group v-model="constraintBatchForm.allowedDayOfWeeks">
              <el-checkbox v-for="day in weekdays" :key="day.value" :value="day.value">{{ day.label }}</el-checkbox>
            </el-checkbox-group>
          </el-form-item>
          <el-checkbox v-model="constraintBatchForm.updatePeriodRange">修改节次范围</el-checkbox>
          <div v-if="constraintBatchForm.updatePeriodRange" class="form-grid">
            <el-form-item label="统一最早开始节次">
              <el-select v-model="constraintBatchForm.earliestPeriod" clearable>
                <el-option v-for="period in periods" :key="period" :label="`第 ${period} 节`" :value="period" />
              </el-select>
            </el-form-item>
            <el-form-item label="统一最晚结束节次">
              <el-select v-model="constraintBatchForm.latestPeriod" clearable>
                <el-option v-for="period in periods" :key="period" :label="`第 ${period} 节`" :value="period" />
              </el-select>
            </el-form-item>
          </div>
        </template>
      </el-form>
      <template #footer>
        <el-button @click="constraintBatchDialog = false">取消</el-button>
        <el-button type="primary" :loading="constraintBatchSaving" @click="saveConstraintBatch">
          修改当前结果
        </el-button>
      </template>
    </el-dialog>
  </div>
</template>
