<script setup lang="ts">
import { computed, onMounted, ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { useRoute, useRouter } from 'vue-router'

import {
  applyBatchIntoSchedule,
  createTaskClass,
  deleteScheduleEntries,
  deleteTaskClassItem,
  getTaskClassDetail,
  getTaskClassList,
  getWeekSchedule,
  smartPlanning,
  smartPlanningMulti,
} from '@/api/scheduleCenter'
import CreateTaskClassDialog from '@/components/schedule/CreateTaskClassDialog.vue'
import TaskClassSidebar from '@/components/schedule/TaskClassSidebar.vue'
import WeekPlanningBoard from '@/components/schedule/WeekPlanningBoard.vue'
import type { ApplyBatchIntoScheduleItem, ScheduleWeekData, ScheduleWeekEvent, TaskClassDetail, TaskClassListItem } from '@/types/schedule'
import { formatHeaderDate } from '@/utils/date'

interface SidebarItem {
  key: 'home' | 'task' | 'calendar' | 'ai'
  label: string
  short: string
  to?: '/dashboard' | '/assistant' | '/schedule'
}

interface WeekDayHeader {
  dayOfWeek: number
  label: string
  dateLabel: string
}

interface SchedulePreviewRuntimeState {
  weeks: ScheduleWeekData[] | null
  taskClassIds: number[]
  currentWeek: number | null
}

interface PreviewMovePayload {
  week: number
  sourceDayOfWeek: number
  sourceOrder: number
  targetDayOfWeek: number
  targetOrder: number
}

interface SuggestedPreviewItem {
  id: number
  name: string
  type: string
  span: number
}

interface SchedulePreviewSlotRef {
  week: number
  dayOfWeek: number
  order: number
}

type SchedulePageWindow = Window & {
  __schedulePreviewBeforeUnloadRegistered__?: boolean
}

// schedulePreviewRuntimeState 负责在“单页应用未刷新”的生命周期内保留智能编排预览。
//
// 设计说明：
// 1. 这里故意不用 sessionStorage/localStorage，因为用户明确要求“刷新就丢”，所以只保留运行时内存。
// 2. 这里负责跨路由回到 /schedule 时恢复预览；不负责持久化到浏览器磁盘。
// 3. 真正需要清空预览的时机，统一由 clearPreviewState 显式控制，避免切周时误清空。
const schedulePreviewRuntimeState: SchedulePreviewRuntimeState = {
  weeks: null,
  taskClassIds: [],
  currentWeek: null,
}

const EMPTY_EMBEDDED_TASK_INFO = {
  id: 0,
  name: '',
  type: 'task',
}

const SCHEDULE_SECTION_TIME_MAP: Record<number, [string, string]> = {
  1: ['08:00', '09:40'],
  2: ['10:15', '11:55'],
  3: ['14:00', '15:40'],
  4: ['16:15', '17:55'],
  5: ['19:00', '20:40'],
  6: ['20:50', '22:30'],
}

// handleSchedulePreviewBeforeUnload 负责在存在未应用预览时阻止页面刷新/关闭。
//
// 职责边界：
// 1. 这里只负责触发浏览器原生确认弹框，不负责展示自定义 UI。
// 2. 只有存在未应用的智能编排结果时才拦截，避免影响正常刷新体验。
function handleSchedulePreviewBeforeUnload(event: BeforeUnloadEvent) {
  if (!schedulePreviewRuntimeState.weeks?.length) {
    return
  }

  event.preventDefault()
  event.returnValue = ''
}

if (typeof window !== 'undefined') {
  const schedulePageWindow = window as SchedulePageWindow
  if (!schedulePageWindow.__schedulePreviewBeforeUnloadRegistered__) {
    window.addEventListener('beforeunload', handleSchedulePreviewBeforeUnload)
    schedulePageWindow.__schedulePreviewBeforeUnloadRegistered__ = true
  }
}

const router = useRouter()
const route = useRoute()

const sidebarItems: SidebarItem[] = [
  { key: 'home', label: '总览', short: '总', to: '/dashboard' },
  { key: 'task', label: '任务', short: '任' },
  { key: 'calendar', label: '日程', short: '程', to: '/schedule' },
  { key: 'ai', label: '助手', short: 'AI', to: '/assistant' },
]

const taskClassLoading = ref(false)
const taskClassDetailLoading = ref(false)
const weekLoading = ref(false)
const smartPlanningLoading = ref(false)
const applyingLoading = ref(false)
const deletingLoading = ref(false)
const createDialogVisible = ref(false)
const createDialogLoading = ref(false)

const taskClasses = ref<TaskClassListItem[]>([])
const expandedTaskClassId = ref<number | null>(null)
const expandedTaskClassDetail = ref<TaskClassDetail | null>(null)
const taskClassMultiSelectMode = ref(false)
const selectedTaskClassIds = ref<number[]>([])
const scheduleSelectionMode = ref(false)
const selectedScheduleEventIds = ref<number[]>([])

const liveWeeks = ref<ScheduleWeekData[]>([])
const previewWeeks = ref<ScheduleWeekData[] | null>(schedulePreviewRuntimeState.weeks)
const previewTaskClassIds = ref<number[]>([...schedulePreviewRuntimeState.taskClassIds])
const currentWeek = ref<number | null>(schedulePreviewRuntimeState.currentWeek)
const weekBase = ref<number | null>(null)
const baseMonday = ref<Date | null>(null)
const lastStableWeekData = ref<ScheduleWeekData | null>(null)
const weekScheduleCache = ref<Record<number, ScheduleWeekData>>({})

const MIN_SCHEDULE_WEEK = 1
const MAX_SCHEDULE_WEEK = 24

let weekRequestSequence = 0
let activeWeekRequestSequence = 0

const activeSidebarKey = computed<SidebarItem['key']>(() => {
  if (route.path.startsWith('/assistant')) {
    return 'ai'
  }
  if (route.path.startsWith('/schedule')) {
    return 'calendar'
  }
  return 'home'
})

const effectiveSelectedTaskClassIds = computed(() => {
  if (taskClassMultiSelectMode.value) {
    return selectedTaskClassIds.value
  }

  return expandedTaskClassId.value ? [expandedTaskClassId.value] : []
})

const previewWeekLookup = computed(() => {
  const map = new Map<number, ScheduleWeekData>()

  for (const item of previewWeeks.value ?? []) {
    map.set(item.week, item)
  }

  return map
})

const liveWeekLookup = computed(() => {
  const map = new Map<number, ScheduleWeekData>()

  for (const item of liveWeeks.value) {
    map.set(item.week, item)
  }

  return map
})

const hasPendingPreview = computed(() => Boolean(previewWeeks.value?.length))

const resolvedCurrentWeekData = computed(() => {
  if (!previewWeeks.value?.length && !liveWeeks.value.length) {
    return null
  }

  if (currentWeek.value === null) {
    return previewWeeks.value?.[0] ?? liveWeeks.value[0] ?? null
  }

  return previewWeekLookup.value.get(currentWeek.value)
    ?? liveWeekLookup.value.get(currentWeek.value)
    ?? null
})

const currentWeekData = computed(() =>
  resolvedCurrentWeekData.value ?? lastStableWeekData.value,
)

const weekHeaders = computed<WeekDayHeader[]>(() => {
  const weekdayMap = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']

  return Array.from({ length: 7 }, (_, index) => {
    const dayOfWeek = index + 1
    const date = resolveDateByWeekDay(currentWeek.value, dayOfWeek)
    return {
      dayOfWeek,
      label: weekdayMap[index],
      dateLabel: date ? `${date.getDate()}日` : '',
    }
  })
})

const weekLabel = computed(() => {
  if (!currentWeek.value) {
    return '第--周'
  }
  return `第${numberToChinese(currentWeek.value)}周`
})

const currentTermLabel = computed(() => {
  const now = new Date()
  const semester = now.getMonth() + 1 >= 8 ? '秋季学期' : '春季学期'
  return `${now.getFullYear()}学年${semester}`
})

const showSmartPlanningButton = computed(() =>
  !scheduleSelectionMode.value && effectiveSelectedTaskClassIds.value.length > 0,
)

const showDeleteModeButton = computed(() =>
  !scheduleSelectionMode.value && effectiveSelectedTaskClassIds.value.length === 0,
)

const showApplyButton = computed(() =>
  !scheduleSelectionMode.value &&
  hasPendingPreview.value,
)

const canGoPreviousWeek = computed(() =>
  currentWeek.value !== null && currentWeek.value > MIN_SCHEDULE_WEEK,
)

const canGoNextWeek = computed(() =>
  currentWeek.value !== null && currentWeek.value < MAX_SCHEDULE_WEEK,
)

function handleSidebarNavigate(item: SidebarItem) {
  if (item.to) {
    if (route.path !== item.to) {
      void router.push(item.to)
    }
    return
  }

  ElMessage.info(`${item.label} 页面正在开发中`)
}

function startOfWeek(date: Date) {
  const next = new Date(date)
  const day = next.getDay()
  const diff = day === 0 ? -6 : 1 - day
  next.setDate(next.getDate() + diff)
  next.setHours(0, 0, 0, 0)
  return next
}

function resolveDateByWeekDay(week: number | null, dayOfWeek: number) {
  if (weekBase.value === null || !baseMonday.value || week === null) {
    return null
  }

  const date = new Date(baseMonday.value)
  date.setDate(baseMonday.value.getDate() + (week - weekBase.value) * 7 + (dayOfWeek - 1))
  return date
}

function numberToChinese(value: number) {
  const digits = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九']
  if (value <= 10) {
    return value === 10 ? '十' : digits[value]
  }
  if (value < 20) {
    return `十${digits[value % 10]}`
  }
  const tens = Math.floor(value / 10)
  const units = value % 10
  return `${digits[tens]}十${units ? digits[units] : ''}`
}

// clampWeekIntoRange 负责把周次限制在后端允许的 1-24 周内。
//
// 职责边界：
// 1. 这里只做前端边界保护，不替代后端校验。
// 2. 调用方若需要交互提示，应在函数外自行决定是否提示。
function clampWeekIntoRange(week: number) {
  return Math.min(MAX_SCHEDULE_WEEK, Math.max(MIN_SCHEDULE_WEEK, week))
}

// syncLiveWeeksFromCache 负责把按周缓存拍平成页面当前使用的 liveWeeks 数组。
//
// 设计说明：
// 1. 页面内部仍按数组消费周数据，因此缓存层统一在这里做结构转换。
// 2. 固定按 week 升序输出，避免切周过快时 currentWeekData 出现不稳定回退。
function syncLiveWeeksFromCache() {
  liveWeeks.value = Object.values(weekScheduleCache.value)
    .sort((left, right) => left.week - right.week)
}

// cacheWeekSchedules 负责把成功请求到的周数据写入页面级本地缓存。
//
// 设计说明：
// 1. 只缓存 1-24 周的合法结果，越界数据直接丢弃，避免污染页面状态。
// 2. 相同周次直接覆盖，保证强刷后的新结果能够替换旧缓存。
function cacheWeekSchedules(weeks: ScheduleWeekData[]) {
  for (const item of weeks) {
    if (item.week < MIN_SCHEDULE_WEEK || item.week > MAX_SCHEDULE_WEEK) {
      continue
    }

    weekScheduleCache.value[item.week] = item
  }

  syncLiveWeeksFromCache()
}

// setPreviewState 负责同步“组件内预览态”和“模块级运行时预览态”。
//
// 设计说明：
// 1. 统一从这个入口写预览，避免 ref 和模块级缓存出现一边更新、一边遗漏。
// 2. taskClassIds 记录本次预览来自哪个任务类，用于刷新前提示、回到页面后继续应用。
// 3. 这里不负责决定何时清空预览，清空策略由 clearPreviewState 和具体业务动作控制。
function setPreviewState(weeks: ScheduleWeekData[] | null, taskClassIds: number[] = []) {
  previewWeeks.value = weeks
  previewTaskClassIds.value = [...taskClassIds]
  schedulePreviewRuntimeState.weeks = weeks
  schedulePreviewRuntimeState.taskClassIds = [...taskClassIds]
}

// clearPreviewState 负责显式清空未应用的智能编排结果。
//
// 职责边界：
// 1. 这里只做状态清理，不做消息提示。
// 2. 调用方负责在“应用成功 / 删除后重算 / 用户主动替换预览”等时机决定是否清空。
function clearPreviewState() {
  setPreviewState(null, [])
}

function isSuggestedPreviewEvent(event?: ScheduleWeekEvent) {
  return Boolean(event && event.status === 'suggested')
}

function cloneScheduleEvent(event: ScheduleWeekEvent): ScheduleWeekEvent {
  return {
    ...event,
    embedded_task_info: event.embedded_task_info
      ? { ...event.embedded_task_info }
      : { ...EMPTY_EMBEDDED_TASK_INFO },
  }
}

// clonePreviewWeeks 负责复制 preview 周数据，确保拖拽编辑只改前端预览态，不污染原引用。
//
// 设计说明：
// 1. 这里只做前端内存数据的浅层结构复制 + 事件深复制，足够支撑拖拽改位。
// 2. 不负责校验事件语义是否合法，语义校验由后面的 move helper 负责。
function clonePreviewWeeks(weeks: ScheduleWeekData[]) {
  return weeks.map((week) => ({
    ...week,
    events: week.events.map((event) => cloneScheduleEvent(event)),
  }))
}

function findPreviewEventIndex(weeks: ScheduleWeekData[], slot: SchedulePreviewSlotRef) {
  const weekIndex = weeks.findIndex((week) => week.week === slot.week)
  if (weekIndex < 0) {
    return { weekIndex: -1, eventIndex: -1 }
  }

  const eventIndex = weeks[weekIndex]!.events.findIndex((event) =>
    event.day_of_week === slot.dayOfWeek && event.order === slot.order)

  return { weekIndex, eventIndex }
}

function buildEmptyPreviewEvent(slot: SchedulePreviewSlotRef): ScheduleWeekEvent {
  const [startTime, endTime] = SCHEDULE_SECTION_TIME_MAP[slot.order] ?? ['', '']

  return {
    id: 0,
    order: slot.order,
    day_of_week: slot.dayOfWeek,
    name: '空白',
    start_time: startTime,
    end_time: endTime,
    location: '',
    type: 'empty',
    span: 2,
    status: 'normal',
    embedded_task_info: { ...EMPTY_EMBEDDED_TASK_INFO },
  }
}

function extractSuggestedPreviewItem(event?: ScheduleWeekEvent): SuggestedPreviewItem | null {
  if (!isSuggestedPreviewEvent(event)) {
    return null
  }

  if (event!.type === 'course' && event!.embedded_task_info?.id) {
    return {
      id: event!.embedded_task_info.id,
      name: event!.embedded_task_info.name,
      type: event!.embedded_task_info.type || 'task',
      span: event!.span || 2,
    }
  }

  return {
    id: event!.id,
    name: event!.name,
    type: event!.type || 'task',
    span: event!.span || 2,
  }
}

function canDropSuggestedIntoCell(event?: ScheduleWeekEvent) {
  if (!event || event.type === 'empty') {
    return true
  }

  if (event.status === 'suggested') {
    return true
  }

  return event.type === 'course'
}

// buildPreviewEventWithSuggested 负责把“某个格子的底板”与“某个 suggested 任务”重新拼装成最终事件。
//
// 职责边界：
// 1. 课程格收到 suggested 时，转换为“课程 + embedded_task_info + suggested 状态”。
// 2. 空白格收到 suggested 时，转换为独立 task 建议块。
// 3. 不带 suggested 时，会把格子还原成普通课程或空白格，保证拖拽前后 JSON 与画面一致。
function buildPreviewEventWithSuggested(
  baseEvent: ScheduleWeekEvent | undefined,
  slot: SchedulePreviewSlotRef,
  suggestedItem: SuggestedPreviewItem | null,
): ScheduleWeekEvent {
  if (baseEvent?.type === 'course') {
    return {
      ...cloneScheduleEvent(baseEvent),
      status: suggestedItem ? 'suggested' : 'normal',
      embedded_task_info: suggestedItem
        ? {
            id: suggestedItem.id,
            name: suggestedItem.name,
            type: suggestedItem.type || 'task',
          }
        : { ...EMPTY_EMBEDDED_TASK_INFO },
    }
  }

  if (!suggestedItem) {
    return buildEmptyPreviewEvent(slot)
  }

  const [startTime, endTime] = SCHEDULE_SECTION_TIME_MAP[slot.order] ?? ['', '']
  return {
    id: suggestedItem.id,
    order: slot.order,
    day_of_week: slot.dayOfWeek,
    name: suggestedItem.name,
    start_time: startTime,
    end_time: endTime,
    location: '',
    type: suggestedItem.type || 'task',
    span: suggestedItem.span || 2,
    status: 'suggested',
    embedded_task_info: { ...EMPTY_EMBEDDED_TASK_INFO },
  }
}

function replacePreviewEventAtSlot(
  weeks: ScheduleWeekData[],
  slot: SchedulePreviewSlotRef,
  nextEvent: ScheduleWeekEvent,
) {
  const { weekIndex, eventIndex } = findPreviewEventIndex(weeks, slot)
  if (weekIndex < 0) {
    return
  }

  if (eventIndex >= 0) {
    weeks[weekIndex]!.events[eventIndex] = nextEvent
  } else {
    weeks[weekIndex]!.events.push(nextEvent)
  }

  weeks[weekIndex]!.events.sort((left, right) => {
    if (left.day_of_week !== right.day_of_week) {
      return left.day_of_week - right.day_of_week
    }
    return left.order - right.order
  })
}

// handleMovePreviewEvent 负责把用户拖拽后的 suggested 位置回写到前端预览 JSON。
//
// 处理步骤：
// 1. 只允许修改当前内存中的 previewWeeks；正式课表与后端缓存都不在这里改。
// 2. 源格必须是 suggested；目标格允许是空白、课程或另一个 suggested（交换）。
// 3. 修改完成后立即回写 setPreviewState，保证界面显示与最终 apply 用到的 JSON 完全一致。
function handleMovePreviewEvent(payload: PreviewMovePayload) {
  if (!previewWeeks.value?.length) {
    return
  }

  const sourceSlot: SchedulePreviewSlotRef = {
    week: payload.week,
    dayOfWeek: payload.sourceDayOfWeek,
    order: payload.sourceOrder,
  }
  const targetSlot: SchedulePreviewSlotRef = {
    week: payload.week,
    dayOfWeek: payload.targetDayOfWeek,
    order: payload.targetOrder,
  }

  if (
    sourceSlot.dayOfWeek === targetSlot.dayOfWeek &&
    sourceSlot.order === targetSlot.order
  ) {
    return
  }

  const nextWeeks = clonePreviewWeeks(previewWeeks.value)
  const sourceLocate = findPreviewEventIndex(nextWeeks, sourceSlot)
  const targetLocate = findPreviewEventIndex(nextWeeks, targetSlot)
  if (sourceLocate.weekIndex < 0 || sourceLocate.eventIndex < 0) {
    return
  }

  const sourceEvent = nextWeeks[sourceLocate.weekIndex]!.events[sourceLocate.eventIndex]
  const targetEvent = targetLocate.weekIndex >= 0 && targetLocate.eventIndex >= 0
    ? nextWeeks[targetLocate.weekIndex]!.events[targetLocate.eventIndex]
    : undefined

  const sourceSuggestedItem = extractSuggestedPreviewItem(sourceEvent)
  if (!sourceSuggestedItem || !canDropSuggestedIntoCell(targetEvent)) {
    return
  }

  const targetSuggestedItem = extractSuggestedPreviewItem(targetEvent)
  const nextSourceEvent = buildPreviewEventWithSuggested(sourceEvent, sourceSlot, targetSuggestedItem)
  const nextTargetEvent = buildPreviewEventWithSuggested(targetEvent, targetSlot, sourceSuggestedItem)

  replacePreviewEventAtSlot(nextWeeks, sourceSlot, nextSourceEvent)
  replacePreviewEventAtSlot(nextWeeks, targetSlot, nextTargetEvent)
  setPreviewState(nextWeeks, previewTaskClassIds.value)
}

async function loadTaskClasses() {
  taskClassLoading.value = true
  try {
    taskClasses.value = await getTaskClassList()
  } catch (error) {
    ElMessage.error(error instanceof Error ? error.message : '任务类列表加载失败')
  } finally {
    taskClassLoading.value = false
  }
}

async function loadWeekData(week?: number, options: { force?: boolean } = {}) {
  const normalizedWeek = typeof week === 'number' ? clampWeekIntoRange(week) : undefined

  // 1. 已缓存的周直接命中本地数据，避免左右来回切周时重复请求后端。
  // 2. force 只用于“应用/删除后刷新当前周”，此时必须跳过缓存回源拿最新结果。
  if (typeof normalizedWeek === 'number' && !options.force) {
    const cachedWeek = weekScheduleCache.value[normalizedWeek]
    if (cachedWeek) {
      syncLiveWeeksFromCache()
      currentWeek.value = normalizedWeek
      return
    }
  }

  // 1. 每次真实请求都分配一个递增序号。
  // 2. 只有“当前最新”的那次请求才允许修改 loading 与当前展示周。
  // 3. 旧请求如果晚回，只允许悄悄写缓存，不能把页面状态回滚。
  const requestSequence = ++weekRequestSequence
  activeWeekRequestSequence = requestSequence
  weekLoading.value = true

  try {
    const result = await getWeekSchedule(normalizedWeek)
    cacheWeekSchedules(result)

    if (result[0]?.week && weekBase.value === null) {
      weekBase.value = result[0].week
      baseMonday.value = startOfWeek(new Date())
    }

    if (requestSequence !== activeWeekRequestSequence) {
      return
    }

    if (typeof normalizedWeek === 'number') {
      currentWeek.value = normalizedWeek
    } else if (result[0]?.week) {
      currentWeek.value = result[0].week
    }
  } catch (error) {
    if (requestSequence !== activeWeekRequestSequence) {
      return
    }

    ElMessage.error(error instanceof Error ? error.message : '周日程加载失败')
  } finally {
    if (requestSequence === activeWeekRequestSequence) {
      weekLoading.value = false
    }
  }
}

async function loadTaskClassDetail(taskClassId: number) {
  taskClassDetailLoading.value = true
  try {
    expandedTaskClassDetail.value = await getTaskClassDetail(taskClassId)
  } catch (error) {
    expandedTaskClassDetail.value = null
    ElMessage.error(error instanceof Error ? error.message : '任务类详情加载失败')
  } finally {
    taskClassDetailLoading.value = false
  }
}

async function handleActivateTaskClass(taskClassId: number) {
  if (taskClassMultiSelectMode.value) {
    selectedTaskClassIds.value = selectedTaskClassIds.value.includes(taskClassId)
      ? selectedTaskClassIds.value.filter((id) => id !== taskClassId)
      : [...selectedTaskClassIds.value, taskClassId]
    return
  }

  scheduleSelectionMode.value = false
  selectedScheduleEventIds.value = []

  if (expandedTaskClassId.value === taskClassId) {
    expandedTaskClassId.value = null
    expandedTaskClassDetail.value = null
    return
  }

  expandedTaskClassId.value = taskClassId
  expandedTaskClassDetail.value = null
  await loadTaskClassDetail(taskClassId)
}

function handleToggleTaskClassMultiMode() {
  taskClassMultiSelectMode.value = !taskClassMultiSelectMode.value
  scheduleSelectionMode.value = false
  selectedScheduleEventIds.value = []

  if (taskClassMultiSelectMode.value) {
    selectedTaskClassIds.value = expandedTaskClassId.value ? [expandedTaskClassId.value] : []
    expandedTaskClassId.value = null
    expandedTaskClassDetail.value = null
    return
  }

  selectedTaskClassIds.value = []
}

async function handleDeleteTaskItem(taskItemId: number) {
  try {
    await deleteTaskClassItem(taskItemId)
    ElMessage.success('任务块已删除')
    await loadTaskClasses()
    if (expandedTaskClassId.value) {
      await loadTaskClassDetail(expandedTaskClassId.value)
    }
  } catch (error) {
    ElMessage.error(error instanceof Error ? error.message : '删除任务块失败')
  }
}

async function handleSmartPlanning() {
  const ids = effectiveSelectedTaskClassIds.value
  if (ids.length === 0) {
    ElMessage.info('请先选择任务类')
    return
  }

  smartPlanningLoading.value = true
  try {
    const plannedWeeks = ids.length === 1 ? await smartPlanning(ids[0]!) : await smartPlanningMulti(ids)
    setPreviewState(plannedWeeks, ids)

    if (plannedWeeks[0]?.week) {
      currentWeek.value = plannedWeeks[0].week
    }
    ElMessage.success(ids.length === 1 ? '已生成粗排预览' : '已生成批量粗排预览')
  } catch (error) {
    ElMessage.error(error instanceof Error ? error.message : '智能粗排失败')
  } finally {
    smartPlanningLoading.value = false
  }
}

function toggleScheduleSelectionMode() {
  scheduleSelectionMode.value = !scheduleSelectionMode.value
  selectedScheduleEventIds.value = []
}

function handleToggleScheduleEvent(eventId: number) {
  selectedScheduleEventIds.value = selectedScheduleEventIds.value.includes(eventId)
    ? selectedScheduleEventIds.value.filter((id) => id !== eventId)
    : [...selectedScheduleEventIds.value, eventId]
}

async function handleDeleteSelectedScheduleEvents() {
  const events = currentWeekData.value?.events.filter((event) => selectedScheduleEventIds.value.includes(event.id)) ?? []
  if (!events.length) {
    ElMessage.info('请先选择要解除安排的格子')
    return
  }

  deletingLoading.value = true
  try {
    await deleteScheduleEntries(events.map((event) => ({
      id: event.id,
      delete_course: event.type === 'course',
      delete_embedded_task: Boolean(event.embedded_task_info?.id),
    })))

    ElMessage.success('已完成解除安排')
    scheduleSelectionMode.value = false
    selectedScheduleEventIds.value = []
    clearPreviewState()
    await loadWeekData(currentWeek.value ?? undefined, { force: true })
    await loadTaskClasses()
    if (expandedTaskClassId.value) {
      await loadTaskClassDetail(expandedTaskClassId.value)
    }
  } catch (error) {
    ElMessage.error(error instanceof Error ? error.message : '解除安排失败')
  } finally {
    deletingLoading.value = false
  }
}

function buildApplyItemsFromPreview(weeks: ScheduleWeekData[]) {
  const items: ApplyBatchIntoScheduleItem[] = []

  for (const week of weeks) {
    for (const event of week.events) {
      if (event.status !== 'suggested') {
        continue
      }

      const startSection = (event.order - 1) * 2 + 1
      const taskItemId = event.type === 'course' && event.embedded_task_info?.id
        ? event.embedded_task_info.id
        : event.id

      items.push({
        task_item_id: taskItemId,
        week: week.week,
        day_of_week: event.day_of_week,
        start_section: startSection,
        end_section: startSection + Math.max(1, event.span || 2) - 1,
        embed_course_event_id: event.type === 'course' ? event.id : 0,
      })
    }
  }

  return items
}

// buildApplyGroupsFromPreview 负责把当前预览里的 suggested 任务按“所属任务类”重新分组。
//
// 处理步骤：
// 1. 单任务类预览直接复用现有单接口，不做额外解析。
// 2. 多任务类预览先拉各任务类详情，建立 task_item_id -> task_class_id 映射。
// 3. 再把预览中的建议项按任务类分桶，后续逐桶调用现有 applyBatchIntoSchedule。
//
// 说明：
// 1. 这里不改 preview JSON，只负责为“正式应用”准备提交载荷。
// 2. 如果发现某个 suggested 任务找不到所属任务类，直接报错，避免提交错桶导致脏数据。
async function buildApplyGroupsFromPreview(
  weeks: ScheduleWeekData[],
  taskClassIds: number[],
) {
  const items = buildApplyItemsFromPreview(weeks)
  const groupedItems = new Map<number, ApplyBatchIntoScheduleItem[]>()

  if (items.length === 0) {
    return groupedItems
  }

  if (taskClassIds.length === 1) {
    groupedItems.set(taskClassIds[0]!, items)
    return groupedItems
  }

  const ownerMap = new Map<number, number>()
  const details = await Promise.all(taskClassIds.map(async (taskClassId) => ({
    taskClassId,
    detail: await getTaskClassDetail(taskClassId),
  })))

  for (const { taskClassId, detail } of details) {
    for (const item of detail.items) {
      if (typeof item.id === 'number' && item.id > 0) {
        ownerMap.set(item.id, taskClassId)
      }
    }
  }

  for (const item of items) {
    const ownerTaskClassId = ownerMap.get(item.task_item_id)
    if (!ownerTaskClassId) {
      throw new Error(`未找到任务块 ${item.task_item_id} 对应的任务类，无法正式应用批量粗排结果`)
    }

    if (!groupedItems.has(ownerTaskClassId)) {
      groupedItems.set(ownerTaskClassId, [])
    }
    groupedItems.get(ownerTaskClassId)!.push(item)
  }

  return groupedItems
}

async function handleApplyPreview() {
  if (!previewWeeks.value?.length || previewTaskClassIds.value.length === 0) {
    ElMessage.info('当前没有可正式应用的预览结果')
    return
  }

  applyingLoading.value = true
  try {
    const groupedItems = await buildApplyGroupsFromPreview(previewWeeks.value, previewTaskClassIds.value)
    if (!groupedItems.size) {
      ElMessage.info('当前预览没有可应用的建议排程')
      return
    }

    for (const [taskClassId, items] of groupedItems) {
      await applyBatchIntoSchedule(taskClassId, items)
    }

    ElMessage.success(previewTaskClassIds.value.length > 1 ? '已正式应用批量粗排结果' : '已正式应用到日程')
    clearPreviewState()
    await loadWeekData(currentWeek.value ?? undefined, { force: true })
    await loadTaskClasses()
    if (expandedTaskClassId.value) {
      await loadTaskClassDetail(expandedTaskClassId.value)
    }
  } catch (error) {
    ElMessage.error(error instanceof Error ? error.message : '正式应用失败')
  } finally {
    applyingLoading.value = false
  }
}

async function handleCreateTaskClass(payload: Parameters<typeof createTaskClass>[0]) {
  createDialogLoading.value = true
  try {
    await createTaskClass(payload)
    ElMessage.success('任务类已创建')
    createDialogVisible.value = false
    await loadTaskClasses()
  } catch (error) {
    ElMessage.error(error instanceof Error ? error.message : '创建任务类失败')
  } finally {
    createDialogLoading.value = false
  }
}

function goPreviousWeek() {
  if (currentWeek.value === null || currentWeek.value <= MIN_SCHEDULE_WEEK) {
    return
  }
  currentWeek.value -= 1
}

function goNextWeek() {
  if (currentWeek.value === null || currentWeek.value >= MAX_SCHEDULE_WEEK) {
    return
  }
  currentWeek.value += 1
}

watch(currentWeek, async (nextWeek, previousWeek) => {
  if (nextWeek === null || nextWeek === previousWeek) {
    return
  }

  const normalizedWeek = clampWeekIntoRange(nextWeek)
  if (normalizedWeek !== nextWeek) {
    currentWeek.value = normalizedWeek
    return
  }

  if (previewWeeks.value?.some((item) => item.week === nextWeek)) {
    return
  }

  await loadWeekData(normalizedWeek)
})

watch(currentWeek, (nextWeek) => {
  schedulePreviewRuntimeState.currentWeek = nextWeek
}, { immediate: true })

watch(resolvedCurrentWeekData, (nextWeekData) => {
  // 1. 只有真正解析到“当前目标周”的数据时，才更新稳定展示态。
  // 2. 当目标周仍在请求中时，这里保持旧值不动，避免课表闪回到别的周。
  if (nextWeekData) {
    lastStableWeekData.value = nextWeekData
  }
}, { immediate: true })

onMounted(async () => {
  await Promise.all([loadTaskClasses(), loadWeekData(currentWeek.value ?? undefined)])
})
</script>

<template>
  <main class="schedule-page">
    <div class="schedule-layout">
      <aside class="dashboard-sidebar">
        <div class="dashboard-sidebar__brand">S</div>
        <nav class="dashboard-sidebar__nav">
          <button
            v-for="item in sidebarItems"
            :key="item.key"
            type="button"
            class="dashboard-sidebar__nav-item"
            :class="{ 'dashboard-sidebar__nav-item--active': item.key === activeSidebarKey }"
            @click="handleSidebarNavigate(item)"
          >
            <span>{{ item.short }}</span>
            <small>{{ item.label }}</small>
          </button>
        </nav>
        <button type="button" class="dashboard-sidebar__settings">设</button>
      </aside>

      <section class="schedule-shell">
        <header class="schedule-topbar">
          <div class="schedule-topbar__brand">
            <span class="schedule-topbar__brand-icon" aria-hidden="true">
              <svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
                <rect x="2" y="2" width="18" height="18" rx="5" fill="currentColor" />
                <path d="M7 8H15V9.6H7V8ZM7 11H15V12.6H7V11ZM7 14H12V15.6H7V14Z" fill="white" />
              </svg>
            </span>
            <strong>日程安排中心</strong>
          </div>

          <div class="schedule-topbar__meta">
            <strong>{{ currentTermLabel }}</strong>
            <span>当前日期: {{ formatHeaderDate(new Date()) }}</span>
          </div>
        </header>

        <div class="schedule-main">
          <TaskClassSidebar
            :task-classes="taskClasses"
            :loading="taskClassLoading"
            :detail-loading="taskClassDetailLoading"
            :expanded-task-class-id="expandedTaskClassId"
            :expanded-task-class-detail="expandedTaskClassDetail"
            :selected-task-class-ids="effectiveSelectedTaskClassIds"
            :task-class-multi-select-mode="taskClassMultiSelectMode"
            @activate="handleActivateTaskClass"
            @toggle-multi-mode="handleToggleTaskClassMultiMode"
            @create="createDialogVisible = true"
            @delete-item="handleDeleteTaskItem"
          />

          <section class="schedule-board-wrap">
            <div class="schedule-board__toolbar">
              <div class="schedule-board__toolbar-left">
                <button
                  v-if="showDeleteModeButton"
                  type="button"
                  class="schedule-board__toolbar-button schedule-board__toolbar-button--ghost"
                  @click="toggleScheduleSelectionMode"
                >
                  多选
                </button>

                <button
                  v-else-if="scheduleSelectionMode"
                  type="button"
                  class="schedule-board__toolbar-button schedule-board__toolbar-button--ghost"
                  @click="toggleScheduleSelectionMode"
                >
                  取消多选
                </button>

                <button
                  v-if="showSmartPlanningButton"
                  type="button"
                  class="schedule-board__toolbar-button schedule-board__toolbar-button--primary"
                  :disabled="smartPlanningLoading"
                  @click="handleSmartPlanning"
                >
                  {{ smartPlanningLoading ? '编排中…' : effectiveSelectedTaskClassIds.length > 1 ? '智能批量编排' : '智能一键编排' }}
                </button>
              </div>

              <div class="schedule-board__toolbar-right">
                <button
                  type="button"
                  class="schedule-board__toolbar-button schedule-board__toolbar-button--ghost"
                  :disabled="!canGoPreviousWeek"
                  @click="goPreviousWeek"
                >
                  上一周
                </button>
                <button
                  type="button"
                  class="schedule-board__toolbar-button schedule-board__toolbar-button--primary"
                  :disabled="!canGoNextWeek"
                  @click="goNextWeek"
                >
                  下一周
                </button>
              </div>
            </div>

            <WeekPlanningBoard
              :week-label="weekLabel"
              :week-headers="weekHeaders"
              :week-data="currentWeekData"
              :schedule-selection-mode="scheduleSelectionMode"
              :selected-schedule-event-ids="selectedScheduleEventIds"
              :preview-drag-enabled="hasPendingPreview"
              @toggle-schedule-event="handleToggleScheduleEvent"
              @move-preview-event="handleMovePreviewEvent"
            />

            <div v-if="showApplyButton || scheduleSelectionMode" class="schedule-board__footer">
              <button
                v-if="showApplyButton"
                type="button"
                class="schedule-board__footer-button schedule-board__footer-button--primary"
                :disabled="applyingLoading"
                @click="handleApplyPreview"
              >
                {{ applyingLoading ? '应用中…' : '正式应用日程' }}
              </button>

              <button
                v-if="scheduleSelectionMode"
                type="button"
                class="schedule-board__footer-button schedule-board__footer-button--danger"
                :disabled="deletingLoading || selectedScheduleEventIds.length === 0"
                @click="handleDeleteSelectedScheduleEvents"
              >
                {{ deletingLoading ? '处理中…' : '解除安排/删除课程' }}
              </button>
            </div>
          </section>
        </div>
      </section>
    </div>

    <CreateTaskClassDialog
      v-model="createDialogVisible"
      :loading="createDialogLoading"
      @submit="handleCreateTaskClass"
    />
  </main>
</template>

<style scoped>
.schedule-page {
  height: 100vh;
  padding: 10px;
  overflow: hidden;
  background: linear-gradient(180deg, #f6f9fd 0%, #eff4fb 100%);
}

.schedule-layout {
  height: calc(100vh - 20px);
  display: grid;
  grid-template-columns: 78px minmax(0, 1fr);
  gap: 8px;
  min-height: 0;
}

.dashboard-sidebar {
  height: 100%;
  border-radius: 26px;
  background: linear-gradient(180deg, #165ca8 0%, #104d8f 100%);
  padding: 16px 12px;
  display: grid;
  grid-template-rows: auto 1fr auto;
  gap: 16px;
}

.dashboard-sidebar__brand,
.dashboard-sidebar__settings {
  width: 50px;
  height: 50px;
  border: none;
  border-radius: 16px;
  background: rgba(255, 255, 255, 0.14);
  color: #fff;
  font-weight: 800;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.dashboard-sidebar__nav {
  display: grid;
  gap: 12px;
  align-content: start;
}

.dashboard-sidebar__nav-item {
  width: 54px;
  border: none;
  border-radius: 16px;
  background: transparent;
  color: rgba(255, 255, 255, 0.74);
  padding: 10px 8px;
  display: grid;
  justify-items: center;
  gap: 5px;
  cursor: pointer;
}

.dashboard-sidebar__nav-item span {
  width: 32px;
  height: 32px;
  border-radius: 11px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.08);
  font-weight: 700;
}

.dashboard-sidebar__nav-item small {
  font-size: 10px;
}

.dashboard-sidebar__nav-item--active {
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
}

.schedule-shell {
  min-width: 0;
  min-height: 0;
  border-radius: 28px;
  border: 1px solid rgba(215, 224, 237, 0.84);
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.96), rgba(248, 251, 255, 0.98));
  overflow: hidden;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
}

.schedule-topbar {
  padding: 14px 24px;
  border-bottom: 1px solid rgba(218, 227, 239, 0.92);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  min-width: 0;
}

.schedule-topbar__brand {
  display: inline-flex;
  align-items: center;
  gap: 16px;
  min-width: 0;
}

.schedule-topbar__brand-icon {
  width: 44px;
  height: 44px;
  border-radius: 14px;
  background: linear-gradient(180deg, #1b64cf 0%, #0f56b7 100%);
  color: #ffffff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.schedule-topbar__brand strong {
  color: #19263d;
  font-size: 20px;
  font-weight: 800;
  min-width: 0;
}

.schedule-topbar__meta {
  display: grid;
  justify-items: end;
  gap: 6px;
  color: #8493aa;
  min-width: 0;
  text-align: right;
}

.schedule-topbar__meta strong {
  color: #344055;
  font-size: 14px;
}

.schedule-topbar__meta span {
  font-size: 12px;
}

.schedule-main {
  min-width: 0;
  min-height: 0;
  display: grid;
  grid-template-columns: clamp(280px, 26vw, 380px) minmax(0, 1fr);
  overflow: hidden;
}

.schedule-board-wrap {
  min-width: 0;
  min-height: 0;
  padding: 18px 22px 22px;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  gap: 14px;
  overflow: hidden;
}

.schedule-board__toolbar {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  align-items: center;
  min-width: 0;
  flex-wrap: wrap;
}

.schedule-board__toolbar-left,
.schedule-board__toolbar-right {
  display: flex;
  gap: 10px;
  align-items: center;
  min-width: 0;
  flex-wrap: wrap;
}

.schedule-board__toolbar-right {
  margin-left: auto;
}

.schedule-board__toolbar-button,
.schedule-board__footer-button {
  height: 36px;
  border-radius: 10px;
  border: 1px solid transparent;
  padding: 0 18px;
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
  transition: border-color 0.16s ease, background-color 0.16s ease, color 0.16s ease;
}

.schedule-board__toolbar-button--primary,
.schedule-board__footer-button--primary {
  background: linear-gradient(180deg, #1d64d1 0%, #1157bd 100%);
  color: #ffffff;
}

.schedule-board__toolbar-button--primary:hover,
.schedule-board__footer-button--primary:hover {
  background: linear-gradient(180deg, #1757b8 0%, #0f4ea9 100%);
}

.schedule-board__toolbar-button--ghost {
  border-color: rgba(27, 96, 208, 0.22);
  background: #ffffff;
  color: #1e66d4;
}

.schedule-board__toolbar-button--ghost:hover {
  border-color: rgba(27, 96, 208, 0.38);
  background: #f2f7ff;
}

.schedule-board__footer {
  display: flex;
  justify-content: flex-end;
  gap: 12px;
}

.schedule-board__footer-button--danger {
  background: #bb3326;
  color: #ffffff;
}

.schedule-board__footer-button--danger:disabled,
.schedule-board__footer-button--primary:disabled,
.schedule-board__toolbar-button:disabled {
  opacity: 0.46;
  cursor: not-allowed;
}

@media (max-width: 1520px) {
  .schedule-main {
    grid-template-columns: clamp(260px, 24vw, 332px) minmax(0, 1fr);
  }

  .schedule-board-wrap {
    padding: 16px 18px 18px;
  }
}

@media (max-width: 1380px) {
  .schedule-main {
    grid-template-columns: 1fr;
  }

  .schedule-topbar {
    flex-wrap: wrap;
  }

  .schedule-topbar__meta {
    justify-items: start;
    text-align: left;
  }
}

@media (max-height: 900px) {
  .schedule-page {
    padding: 8px;
  }

  .schedule-layout {
    height: calc(100vh - 16px);
  }

  .schedule-topbar {
    padding: 12px 18px;
  }

  .schedule-topbar__brand {
    gap: 12px;
  }

  .schedule-topbar__brand-icon {
    width: 38px;
    height: 38px;
    border-radius: 12px;
  }

  .schedule-topbar__brand strong {
    font-size: 18px;
  }

  .schedule-board-wrap {
    padding-top: 14px;
    padding-bottom: 16px;
    gap: 10px;
  }

  .schedule-board__toolbar-button,
  .schedule-board__footer-button {
    height: 34px;
    padding: 0 14px;
  }
}

@media (max-height: 820px) {
  .schedule-topbar {
    padding-top: 10px;
    padding-bottom: 10px;
  }

  .schedule-topbar__meta {
    gap: 4px;
  }

  .schedule-board-wrap {
    padding-left: 14px;
    padding-right: 14px;
    padding-bottom: 14px;
  }
}

@media (max-width: 1180px) {
  .schedule-layout {
    grid-template-columns: 1fr;
  }

  .dashboard-sidebar {
    display: none;
  }

  .schedule-main {
    grid-template-columns: 1fr;
  }
}
</style>
