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

const auth = useAuthStore()
const isStudent = computed(() => auth.user?.roles.includes('Student') ?? false)
const loading = ref(false)
const terms = ref<any[]>([])
const termId = ref('')
const projects = ref<any[]>([])
const options = reactive({
  tasks: [] as any[],
  scheduleEntries: [] as any[],
  classrooms: [] as any[],
  periods: [] as any[],
})
const modeFilter = ref('')
const statusFilter = ref('')

const projectDialog = ref(false)
const editingProjectId = ref('')
const projectForm = reactive({
  teachingTaskIds: [] as string[],
  scheduleEntryId: '',
  code: '',
  name: '',
  arrangementMode: 'Centralized',
  description: '',
  requirements: '',
  dates: [] as string[],
})

const sessionDialog = ref(false)
const selectedProject = ref<any>(null)
const sessionForm = reactive({
  classroomId: '',
  sessionDate: '',
  startPeriod: undefined as number | undefined,
  periodCount: 2,
  capacity: 30,
  notes: '',
})

const batchSessionDialog = ref(false)
const batchSessionProjectIds = ref<string[]>([])
const batchSessionRows = ref<any[]>([])

const participantsDialog = ref(false)
const participantSession = ref<any>(null)
const participants = ref<any[]>([])
const participantsLoading = ref(false)

const selectedTask = computed(() =>
  options.tasks.find((task) => task.id === projectForm.teachingTaskIds[0]),
)
const selectedScheduleEntry = computed(() =>
  options.scheduleEntries.find((entry) => entry.id === projectForm.scheduleEntryId),
)
const batchProjectOptions = computed(() =>
  projects.value.filter((project) =>
    project.status !== 'Closed' && project.arrangementMode === 'SelfScheduled'),
)
const activePeriods = computed(() =>
  options.periods.filter((period) =>
    !selectedProject.value
      || period.academicTermId === selectedProject.value.academicTermId,
  ),
)

const modeMeta: Record<string, { label: string; note: string }> = {
  Centralized: {
    label: '集中安排',
    note: '像课程一样统一到场，无需学生预约',
  },
  SelfScheduled: {
    label: '自行安排',
    note: '规定实验项目，学生从开放场次中预约',
  },
}

const statusLabels: Record<string, string> = {
  Draft: '草稿',
  Published: '进行中',
  Closed: '已关闭',
}

function resetProjectForm() {
  editingProjectId.value = ''
  Object.assign(projectForm, {
    teachingTaskIds: [],
    scheduleEntryId: '',
    code: '',
    name: '',
    arrangementMode: 'Centralized',
    description: '',
    requirements: '',
    dates: [],
  })
}

function onTaskChange() {
  const task = selectedTask.value
  if (!task) return
  projectForm.dates = [task.termStartDate, task.termEndDate]
}

function onScheduleEntryChange() {
  const entry = selectedScheduleEntry.value
  if (!entry) return
  projectForm.teachingTaskIds = [entry.teachingTaskId]
  onTaskChange()
}

function openCreateProject() {
  resetProjectForm()
  projectDialog.value = true
}

function openEditProject(project: any) {
  editingProjectId.value = project.id
  Object.assign(projectForm, {
    teachingTaskIds: [project.teachingTaskId],
    scheduleEntryId: project.scheduleEntryId ?? '',
    code: project.code,
    name: project.name,
    arrangementMode: project.arrangementMode,
    description: project.description ?? '',
    requirements: project.requirements ?? '',
    dates: [project.startDate, project.endDate],
  })
  projectDialog.value = true
}

async function saveProject() {
  if (!projectForm.teachingTaskIds.length ||
    (projectForm.arrangementMode === 'Centralized' && !projectForm.scheduleEntryId) ||
    !projectForm.code.trim()
    || !projectForm.name.trim() || projectForm.dates.length !== 2) {
    ElMessage.warning(projectForm.arrangementMode === 'Centralized'
      ? '请选择课表实验课，并填写项目编码、名称和开放日期'
      : '请填写教学任务、项目编码、名称和开放日期')
    return
  }
  const payload = {
    teachingTaskId: projectForm.teachingTaskIds[0],
    scheduleEntryId: projectForm.scheduleEntryId || null,
    code: projectForm.code,
    name: projectForm.name,
    arrangementMode: projectForm.arrangementMode,
    description: projectForm.description || null,
    requirements: projectForm.requirements || null,
    startDate: projectForm.dates[0],
    endDate: projectForm.dates[1],
  }
  try {
    if (editingProjectId.value) {
      await http.put(`/experiments/${editingProjectId.value}`, payload)
      ElMessage.success('实验项目已更新')
    } else if (projectForm.arrangementMode === 'Centralized') {
      await http.post('/experiments', payload)
      ElMessage.success('实验项目已绑定课表实验课')
    } else {
      await http.post('/experiments/batch', {
        ...payload,
        teachingTaskId: undefined,
        teachingTaskIds: projectForm.teachingTaskIds,
      })
      ElMessage.success(`已为 ${projectForm.teachingTaskIds.length} 个教学任务创建实验项目`)
    }
    projectDialog.value = false
    await load()
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  }
}

function openBatchSession() {
  batchSessionProjectIds.value = []
  batchSessionRows.value = []
  batchSessionDialog.value = true
}

function syncBatchSessionRows() {
  const existing = new Map(batchSessionRows.value.map((row) => [row.projectId, row]))
  batchSessionRows.value = batchSessionProjectIds.value.map((projectId) => {
    const current = existing.get(projectId)
    if (current) return current
    const project = projects.value.find((item) => item.id === projectId)
    const firstPeriod = options.periods.find((item) =>
      item.academicTermId === project?.academicTermId,
    )
    return {
      projectId,
      classroomId: '',
      sessionDate: project?.startDate ?? '',
      startPeriod: firstPeriod?.periodNumber,
      periodCount: 2,
      capacity: 30,
      notes: '',
    }
  })
}

function applyFirstBatchTime() {
  const first = batchSessionRows.value[0]
  if (!first) return
  batchSessionRows.value.slice(1).forEach((row) => {
    row.sessionDate = first.sessionDate
    row.startPeriod = first.startPeriod
    row.periodCount = first.periodCount
  })
  ElMessage.success('已套用首行的日期和节次，请分别选择不冲突的实验室')
}

function batchProject(projectId: string) {
  return projects.value.find((project) => project.id === projectId)
}

function periodsForProject(projectId: string) {
  const project = batchProject(projectId)
  return options.periods.filter((period) =>
    period.academicTermId === project?.academicTermId,
  )
}

async function saveBatchSessions() {
  if (!batchSessionRows.value.length) {
    ElMessage.warning('请至少选择一个实验项目')
    return
  }
  if (batchSessionRows.value.some((row) =>
    !row.classroomId || !row.sessionDate || !row.startPeriod || !row.periodCount,
  )) {
    ElMessage.warning('请完整填写每个项目的日期、节次、实验室和容量')
    return
  }
  try {
    await http.post('/experiments/sessions/batch', {
      items: batchSessionRows.value,
    })
    batchSessionDialog.value = false
    ElMessage.success(`已批量安排 ${batchSessionRows.value.length} 个实验场次`)
    await load()
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  }
}

function openSession(project: any) {
  selectedProject.value = project
  const firstPeriod = options.periods.find((item) =>
    item.academicTermId === project.academicTermId,
  )
  Object.assign(sessionForm, {
    classroomId: '',
    sessionDate: project.startDate,
    startPeriod: firstPeriod?.periodNumber,
    periodCount: 2,
    capacity: 30,
    notes: '',
  })
  sessionDialog.value = true
}

async function saveSession() {
  if (!selectedProject.value || !sessionForm.classroomId
    || !sessionForm.sessionDate || !sessionForm.startPeriod) {
    ElMessage.warning('请选择实验日期、节次和教室')
    return
  }
  try {
    await http.post(`/experiments/${selectedProject.value.id}/sessions`, sessionForm)
    sessionDialog.value = false
    ElMessage.success('实验场次已安排')
    await load()
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  }
}

async function publishProject(project: any) {
  try {
    await ElMessageBox.confirm(
      project.arrangementMode === 'Centralized'
        ? '发布后，教学任务名单中的学生将直接看到全部集中实验场次。'
        : '发布后，教学任务名单中的学生可以立即预约开放场次。',
      `发布“${project.name}”`,
      { confirmButtonText: '确认发布', cancelButtonText: '暂不发布', type: 'warning' },
    )
    await http.post(`/experiments/${project.id}/publish`)
    ElMessage.success('实验项目已发布')
    await load()
  } catch (error: any) {
    if (error === 'cancel' || error === 'close') return
    ElMessage.error(apiErrorMessage(error))
  }
}

async function closeProject(project: any) {
  try {
    await ElMessageBox.confirm(
      '关闭后保留已有安排和预约记录，但学生不能再预约新场次。',
      `关闭“${project.name}”`,
      { confirmButtonText: '确认关闭', cancelButtonText: '取消', type: 'warning' },
    )
    await http.post(`/experiments/${project.id}/close`)
    ElMessage.success('实验项目已关闭')
    await load()
  } catch (error: any) {
    if (error === 'cancel' || error === 'close') return
    ElMessage.error(apiErrorMessage(error))
  }
}

async function deleteProject(project: any) {
  try {
    await ElMessageBox.confirm(
      '草稿项目及其未发布场次将一并删除。',
      `删除“${project.name}”`,
      { confirmButtonText: '删除', cancelButtonText: '取消', type: 'error' },
    )
    await http.delete(`/experiments/${project.id}`)
    ElMessage.success('实验项目已删除')
    await load()
  } catch (error: any) {
    if (error === 'cancel' || error === 'close') return
    ElMessage.error(apiErrorMessage(error))
  }
}

async function cancelSession(project: any, session: any) {
  try {
    await ElMessageBox.confirm(
      project.status === 'Draft'
        ? '该未发布场次将直接删除。'
        : '该场次将被取消；已有学生预约也会取消并收到通知。',
      `${project.status === 'Draft' ? '删除' : '取消'} ${formatSessionTime(session)}`,
      { confirmButtonText: '确认', cancelButtonText: '返回', type: 'warning' },
    )
    await http.delete(`/experiments/sessions/${session.id}`)
    ElMessage.success(project.status === 'Draft' ? '场次已删除' : '场次已取消')
    await load()
  } catch (error: any) {
    if (error === 'cancel' || error === 'close') return
    ElMessage.error(apiErrorMessage(error))
  }
}

async function showParticipants(project: any, session: any) {
  participantSession.value = { ...session, project }
  participantsDialog.value = true
  participantsLoading.value = true
  try {
    participants.value = (
      await http.get(`/experiments/sessions/${session.id}/participants`)
    ).data
  } catch (error) {
    participants.value = []
    ElMessage.error(apiErrorMessage(error))
  } finally {
    participantsLoading.value = false
  }
}

async function book(project: any, session: any) {
  try {
    await ElMessageBox.confirm(
      `${formatSessionTime(session)} · ${session.campusName} ${session.buildingName} ${session.classroomName}`,
      `预约“${project.name}”`,
      { confirmButtonText: '确认预约', cancelButtonText: '再看看', type: 'info' },
    )
    await http.post(`/experiments/sessions/${session.id}/book`)
    ElMessage.success('实验场次已预约')
    await load()
  } catch (error: any) {
    if (error === 'cancel' || error === 'close') return
    ElMessage.error(apiErrorMessage(error))
  }
}

async function cancelBooking(project: any) {
  if (!project.myBooking) return
  try {
    await ElMessageBox.confirm(
      '取消后名额会立即释放，您可以重新选择该项目的其他场次。',
      `取消“${project.name}”预约`,
      { confirmButtonText: '取消预约', cancelButtonText: '保留预约', type: 'warning' },
    )
    await http.delete(`/experiments/bookings/${project.myBooking.id}`)
    ElMessage.success('预约已取消')
    await load()
  } catch (error: any) {
    if (error === 'cancel' || error === 'close') return
    ElMessage.error(apiErrorMessage(error))
  }
}

function formatSessionTime(session: any) {
  return `${session.sessionDate} · 第 ${session.startPeriod}–${session.startPeriod + session.periodCount - 1} 节`
}

function selectedSession(project: any) {
  return project.sessions.find((session: any) =>
    session.id === project.myBooking?.experimentSessionId,
  )
}

function sessionTagType(session: any) {
  const remaining = session.capacity - session.reservedCount
  if (remaining <= 0) return 'danger'
  if (remaining <= Math.max(2, Math.floor(session.capacity * 0.2))) return 'warning'
  return 'success'
}

async function loadOptions() {
  if (isStudent.value) return
  try {
    const { data } = await http.get('/experiments/options', {
      params: { academicTermId: termId.value || undefined },
    })
    options.tasks = data.tasks
    options.scheduleEntries = data.scheduleEntries
    options.classrooms = data.classrooms
    options.periods = data.periods
  } catch (error) {
    options.tasks = []
    options.classrooms = []
    options.periods = []
    ElMessage.error(apiErrorMessage(error))
  }
}

async function load() {
  loading.value = true
  try {
    if (isStudent.value) {
      projects.value = (await http.get('/experiments/student', {
        params: { academicTermId: termId.value || undefined },
      })).data
    } else {
      projects.value = (await http.get('/experiments/management', {
        params: {
          academicTermId: termId.value || undefined,
          arrangementMode: modeFilter.value || undefined,
          status: statusFilter.value || undefined,
        },
      })).data
    }
  } catch (error) {
    projects.value = []
    ElMessage.error(apiErrorMessage(error))
  } finally {
    loading.value = false
  }
}

async function changeTerm() {
  await loadOptions()
  await load()
}

onMounted(async () => {
  try {
    terms.value = (await http.get('/base-data/terms')).data
    termId.value = defaultAcademicTermId(terms.value) ?? ''
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  }
  await loadOptions()
  await load()
})
</script>

<template>
  <div class="page-stack experiment-page">
    <section class="page-intro experiment-intro">
      <div>
        <span class="section-kicker">LABORATORY OPERATIONS</span>
        <h2>{{ isStudent ? '我的实验' : '实验管理' }}</h2>
        <p v-if="isStudent">查看统一安排的实验课次，或为规定实验项目选择适合自己的开放时段。</p>
        <p v-else>集中实验直接绑定已发布课表；仅自主预约实验需要在此开放场次。</p>
      </div>
      <div class="intro-actions">
        <el-button v-if="!isStudent" :icon="Calendar" @click="openBatchSession">
          批量开放场次
        </el-button>
        <el-button v-if="!isStudent" type="primary" :icon="Plus" @click="openCreateProject">
          批量设置实验任务
        </el-button>
        <el-button :icon="Refresh" @click="load">刷新</el-button>
      </div>
    </section>

    <section class="lab-rail" aria-label="实验安排方式说明">
      <article class="rail-lane centralized">
        <span class="rail-code">FIXED / 集中</span>
        <b>统一课次</b>
        <p>面向整个教学任务名单，时间和实验室固定，像上课一样按安排到场。</p>
      </article>
      <div class="rail-switch" aria-hidden="true"><i /><i /></div>
      <article class="rail-lane flexible">
        <span class="rail-code">OPEN / 自主</span>
        <b>开放预约</b>
        <p>实验内容保持一致，学生在容量允许的场次中选择时间，每个项目限约一次。</p>
      </article>
    </section>

    <section class="experiment-toolbar">
      <el-select v-model="termId" clearable placeholder="全部学期" @change="changeTerm">
        <el-option
          v-for="term in terms"
          :key="term.id"
          :label="academicTermLabel(term)"
          :value="term.id"
          :class="academicTermOptionClass(term)"
        />
      </el-select>
      <template v-if="!isStudent">
        <el-select v-model="modeFilter" clearable placeholder="全部安排方式" @change="load">
          <el-option label="集中安排" value="Centralized" />
          <el-option label="自行安排" value="SelfScheduled" />
        </el-select>
        <el-select v-model="statusFilter" clearable placeholder="全部状态" @change="load">
          <el-option label="草稿" value="Draft" />
          <el-option label="进行中" value="Published" />
          <el-option label="已关闭" value="Closed" />
        </el-select>
      </template>
      <span class="result-note">共 {{ projects.length }} 个实验项目</span>
    </section>

    <section v-loading="loading" class="project-list">
      <article
        v-for="project in projects"
        :key="project.id"
        class="project-card"
        :class="[
          project.arrangementMode === 'Centralized' ? 'is-centralized' : 'is-flexible',
          `is-${project.status.toLowerCase()}`,
        ]"
      >
        <header class="project-head">
          <div class="project-identity">
            <span class="project-code">{{ project.courseCode }} · {{ project.code }}</span>
            <h3>{{ project.name }}</h3>
            <p>{{ project.courseName }} · {{ project.taskNumber }}</p>
          </div>
          <div class="project-tags">
            <el-tag
              :type="project.arrangementMode === 'Centralized' ? 'primary' : 'success'"
              effect="plain"
            >
              {{ modeMeta[project.arrangementMode].label }}
            </el-tag>
            <el-tag
              :type="project.status === 'Published' ? 'success' : project.status === 'Closed' ? 'info' : 'warning'"
            >
              {{ statusLabels[project.status] }}
            </el-tag>
          </div>
        </header>

        <div class="project-context">
          <span><b>开放日期</b>{{ project.startDate }} — {{ project.endDate }}</span>
          <span><b>任课教师</b>{{ project.teacherNames.join('、') || '待定' }}</span>
          <span v-if="!isStudent"><b>面向班级</b>{{ project.classNames.join('、') || '选课学生' }}</span>
        </div>

        <p v-if="project.description" class="project-description">{{ project.description }}</p>
        <div v-if="project.requirements" class="requirement-strip">
          <b>实验要求</b>
          <span>{{ project.requirements }}</span>
        </div>

        <div class="session-board">
          <div class="session-board-head">
            <div>
              <span>{{ project.arrangementMode === 'Centralized' ? '统一实验课次' : '可选实验场次' }}</span>
              <b>{{ modeMeta[project.arrangementMode].note }}</b>
            </div>
            <el-button
              v-if="!isStudent && project.status !== 'Closed' && project.arrangementMode === 'SelfScheduled'"
              size="small"
              :icon="Calendar"
              @click="openSession(project)"
            >
              安排场次
            </el-button>
          </div>

          <div v-if="project.arrangementMode === 'Centralized' && project.scheduleEntry" class="session-grid">
            <article class="session-ticket">
              <div class="ticket-date"><strong>课表</strong><span>固定</span></div>
              <div class="ticket-body">
                <b>星期 {{ project.scheduleEntry.dayOfWeek }} · 第 {{ project.scheduleEntry.startPeriod }}–{{ project.scheduleEntry.startPeriod + project.scheduleEntry.periodCount - 1 }} 节</b>
                <span>第 {{ project.scheduleEntry.startWeek }}–{{ project.scheduleEntry.endWeek }} 周 · {{ project.scheduleEntry.campusName }} · {{ project.scheduleEntry.buildingName }} {{ project.scheduleEntry.classroomName }}</span>
              </div>
              <div class="ticket-action"><el-tag type="primary" size="small" effect="plain">课表已安排</el-tag></div>
            </article>
          </div>
          <div v-else-if="project.sessions.length" class="session-grid">
            <article
              v-for="session in project.sessions"
              :key="session.id"
              class="session-ticket"
              :class="{
                'is-selected': project.myBooking?.experimentSessionId === session.id,
                'is-cancelled': session.status === 'Cancelled',
              }"
            >
              <div class="ticket-date">
                <strong>{{ session.sessionDate.slice(8, 10) }}</strong>
                <span>{{ session.sessionDate.slice(5, 7) }} 月</span>
              </div>
              <div class="ticket-body">
                <b>第 {{ session.startPeriod }}–{{ session.startPeriod + session.periodCount - 1 }} 节</b>
                <span>{{ session.campusName }} · {{ session.buildingName }} {{ session.classroomName }}</span>
                <small v-if="session.notes">{{ session.notes }}</small>
              </div>
              <div class="ticket-action">
                <template v-if="project.arrangementMode === 'SelfScheduled'">
                  <el-tag
                    v-if="session.status !== 'Cancelled'"
                    :type="sessionTagType(session)"
                    size="small"
                    effect="plain"
                  >
                    余 {{ session.capacity - session.reservedCount }} / {{ session.capacity }}
                  </el-tag>
                  <el-tag v-else type="info" size="small">已取消</el-tag>
                </template>
                <el-tag v-else type="primary" size="small" effect="plain">
                  统一到场
                </el-tag>

                <template v-if="isStudent && project.arrangementMode === 'SelfScheduled'">
                  <el-button
                    v-if="project.myBooking?.experimentSessionId === session.id"
                    size="small"
                    type="success"
                    :icon="Check"
                    disabled
                  >
                    已预约
                  </el-button>
                  <el-button
                    v-else
                    size="small"
                    type="primary"
                    plain
                    :disabled="project.status !== 'Published' || !!project.myBooking || session.remainingCount <= 0"
                    @click="book(project, session)"
                  >
                    预约此场
                  </el-button>
                </template>
                <template v-if="!isStudent">
                  <el-button
                    size="small"
                    text
                    :icon="User"
                    @click="showParticipants(project, session)"
                  >
                    {{ project.arrangementMode === 'Centralized' ? '应到名单' : '预约名单' }}
                  </el-button>
                  <el-button
                    v-if="session.status !== 'Cancelled'"
                    size="small"
                    text
                    type="danger"
                    @click="cancelSession(project, session)"
                  >
                    {{ project.status === 'Draft' ? '删除' : '取消' }}
                  </el-button>
                </template>
              </div>
            </article>
          </div>
          <el-empty
            v-else
            :image-size="58"
            :description="project.arrangementMode === 'Centralized' ? '尚未绑定已发布课表中的实验课' : (project.status === 'Draft' ? '尚未开放场次，安排后才能发布' : '暂无有效实验场次')"
          />
        </div>

        <footer v-if="!isStudent" class="project-actions">
          <template v-if="project.status === 'Draft'">
            <el-button size="small" @click="openEditProject(project)">编辑项目</el-button>
            <el-button size="small" type="danger" plain @click="deleteProject(project)">删除</el-button>
            <el-button
              size="small"
              type="primary"
              :disabled="project.arrangementMode === 'Centralized'
                ? !project.scheduleEntry
                : !project.sessions.some((item: any) => item.status !== 'Cancelled')"
              @click="publishProject(project)"
            >
              发布给学生
            </el-button>
          </template>
          <el-button
            v-else-if="project.status === 'Published'"
            size="small"
            @click="closeProject(project)"
          >
            关闭项目
          </el-button>
        </footer>
        <footer
          v-else-if="project.arrangementMode === 'SelfScheduled' && project.myBooking"
          class="student-booking-summary"
        >
          <div>
            <Tickets />
            <span>
              <b>当前预约</b>
              {{ formatSessionTime(selectedSession(project)) }}
            </span>
          </div>
          <el-button
            v-if="project.status === 'Published'"
            size="small"
            type="danger"
            plain
            @click="cancelBooking(project)"
          >
            取消并重选
          </el-button>
        </footer>
      </article>

      <el-empty
        v-if="!loading && !projects.length"
        :description="isStudent ? '当前学期没有已发布的实验项目' : '当前筛选条件下暂无实验项目'"
      >
        <el-button v-if="!isStudent" type="primary" @click="openCreateProject">
          新建第一个实验项目
        </el-button>
      </el-empty>
    </section>

    <el-dialog
      v-model="projectDialog"
      :title="editingProjectId ? '编辑实验项目' : '批量设置实验任务'"
      width="720px"
      top="5vh"
    >
      <el-form label-position="top" class="experiment-form">
        <div class="form-section">
          <header><span>PROJECT</span><b>规定实验项目</b></header>
          <el-form-item v-if="projectForm.arrangementMode === 'Centralized'" label="已发布课表中的实验课" required>
            <el-select v-model="projectForm.scheduleEntryId" filterable :disabled="!!editingProjectId" placeholder="选择已安排实验室的实验课" @change="onScheduleEntryChange">
              <el-option
                v-for="entry in options.scheduleEntries"
                :key="entry.id"
                :label="`${entry.courseCode} · ${entry.courseName} · ${entry.taskNumber} · 星期 ${entry.dayOfWeek} 第 ${entry.startPeriod}–${entry.startPeriod + entry.periodCount - 1} 节 · ${entry.campusName} ${entry.buildingName} ${entry.classroomName}`"
                :value="entry.id"
              />
            </el-select>
            <small class="form-help">集中实验复用课表的时间和实验室，不会再生成独立实验场次。</small>
          </el-form-item>
          <el-form-item v-else :label="editingProjectId ? '所属教学任务' : '适用教学任务（可多选）'" required>
            <el-select
              v-model="projectForm.teachingTaskIds"
              filterable
              :multiple="!editingProjectId"
              collapse-tags
              collapse-tags-tooltip
              :disabled="!!editingProjectId"
              placeholder="选择同一学期、同一课程的已发布教学任务"
              @change="onTaskChange"
            >
              <el-option
                v-for="task in options.tasks"
                :key="task.id"
                :label="`${task.courseCode} · ${task.courseName} · ${task.taskNumber}`"
                :value="task.id"
                :disabled="!!selectedTask
                  && (task.academicTermId !== selectedTask.academicTermId
                    || task.courseId !== selectedTask.courseId)"
              />
            </el-select>
            <small v-if="!editingProjectId" class="form-help">
              已选 {{ projectForm.teachingTaskIds.length }} 个；实验编码、名称、内容和开放日期将一次应用到这些教学任务。
            </small>
          </el-form-item>
          <div class="form-grid two">
            <el-form-item label="项目编码" required>
              <el-input v-model="projectForm.code" maxlength="40" placeholder="如 LAB-01" />
            </el-form-item>
            <el-form-item label="项目名称" required>
              <el-input v-model="projectForm.name" maxlength="120" placeholder="如 数据库事务与并发实验" />
            </el-form-item>
          </div>
        </div>

        <div class="form-section">
          <header><span>ROUTE</span><b>选择运行轨道</b></header>
           <el-radio-group v-model="projectForm.arrangementMode" class="mode-choice" :disabled="!!editingProjectId">
            <el-radio-button value="Centralized">
              <b>集中安排</b><small>复用已发布课表的实验课</small>
            </el-radio-button>
            <el-radio-button value="SelfScheduled">
              <b>自行安排</b><small>开放多个场次，学生自主预约</small>
            </el-radio-button>
          </el-radio-group>
          <el-form-item label="项目开放日期" required>
            <el-date-picker
              v-model="projectForm.dates"
              type="daterange"
              value-format="YYYY-MM-DD"
              range-separator="至"
              start-placeholder="开始日期"
              end-placeholder="结束日期"
            />
          </el-form-item>
        </div>

        <div class="form-section">
          <header><span>BRIEF</span><b>说明学生要完成什么</b></header>
          <el-form-item label="实验内容">
            <el-input
              v-model="projectForm.description"
              type="textarea"
              :rows="3"
              maxlength="1000"
              show-word-limit
              placeholder="说明实验目标、任务和成果"
            />
          </el-form-item>
          <el-form-item label="到场与准备要求">
            <el-input
              v-model="projectForm.requirements"
              type="textarea"
              :rows="2"
              maxlength="1000"
              show-word-limit
              placeholder="如携带材料、安装软件、分组要求"
            />
          </el-form-item>
        </div>
      </el-form>
      <template #footer>
        <el-button @click="projectDialog = false">取消</el-button>
        <el-button type="primary" @click="saveProject">
          {{ editingProjectId
            ? '保存项目'
            : projectForm.teachingTaskIds.length
              ? `创建 ${projectForm.teachingTaskIds.length} 个项目`
              : '创建项目' }}
        </el-button>
      </template>
    </el-dialog>

    <el-dialog
      v-model="sessionDialog"
      :title="`安排场次 · ${selectedProject?.name ?? ''}`"
      width="650px"
    >
      <el-form label-position="top" class="experiment-form">
        <el-alert
          :title="selectedProject?.arrangementMode === 'Centralized'
            ? '系统会检查实验室、任课教师和行政班的课表冲突。'
            : '系统会检查实验室占用；学生预约时再检查个人课表和其他实验。'"
          type="info"
          :closable="false"
          show-icon
        />
        <div class="form-grid two">
          <el-form-item label="实验日期" required>
            <el-date-picker
              v-model="sessionForm.sessionDate"
              type="date"
              value-format="YYYY-MM-DD"
              placeholder="选择日期"
            />
          </el-form-item>
          <el-form-item label="起始节次" required>
            <el-select v-model="sessionForm.startPeriod" placeholder="选择节次">
              <el-option
                v-for="period in activePeriods"
                :key="period.periodNumber"
                :label="period.startsAt
                  ? `${period.name} · ${period.startsAt}–${period.endsAt}`
                  : period.name"
                :value="period.periodNumber"
              />
            </el-select>
          </el-form-item>
          <el-form-item label="连续节数" required>
            <el-input-number v-model="sessionForm.periodCount" :min="1" :max="12" />
          </el-form-item>
          <el-form-item label="场次容量" required>
            <el-input-number v-model="sessionForm.capacity" :min="1" :max="10000" />
          </el-form-item>
        </div>
        <el-form-item label="实验室" required>
          <el-select v-model="sessionForm.classroomId" filterable placeholder="选择实验室或教学场所">
            <el-option
              v-for="room in options.classrooms.filter((item: any) => (Number(item.teachingVenueNature) & (2 | 4 | 8 | 16)) !== 0)"
              :key="room.id"
              :label="`${room.campusName} · ${room.buildingName} ${room.name} · ${room.capacity} 人`"
              :value="room.id"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="场次备注">
          <el-input
            v-model="sessionForm.notes"
            maxlength="500"
            placeholder="如分组、设备或材料说明"
          />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="sessionDialog = false">取消</el-button>
        <el-button type="primary" @click="saveSession">保存场次</el-button>
      </template>
    </el-dialog>

    <el-dialog
      v-model="batchSessionDialog"
      title="批量安排实验场次"
      width="min(1180px, 94vw)"
      top="4vh"
    >
      <el-alert
        title="一次提交整批排课；系统会逐条检查管理范围、开放日期、实验室、课表、教师和班级冲突，任何一条失败都不会写入本批次。"
        type="info"
        :closable="false"
        show-icon
      />
      <el-form label-position="top" class="batch-session-form">
        <el-form-item label="选择实验项目" required>
          <el-select
            v-model="batchSessionProjectIds"
            multiple
            filterable
            collapse-tags
            collapse-tags-tooltip
            placeholder="选择需要一起排课的实验项目"
            @change="syncBatchSessionRows"
          >
            <el-option
              v-for="project in batchProjectOptions"
              :key="project.id"
              :label="`${project.courseCode} · ${project.name} · ${project.taskNumber}`"
              :value="project.id"
            />
          </el-select>
        </el-form-item>
        <div v-if="batchSessionRows.length" class="batch-session-tools">
          <span>共 {{ batchSessionRows.length }} 条排课</span>
          <el-button size="small" @click="applyFirstBatchTime">套用首行日期与节次</el-button>
        </div>
        <div class="batch-session-table">
          <article v-for="(row, index) in batchSessionRows" :key="row.projectId" class="batch-session-row">
            <div class="batch-project-cell">
              <span>{{ index + 1 }}</span>
              <div>
                <b>{{ batchProject(row.projectId)?.name }}</b>
                <small>{{ batchProject(row.projectId)?.taskNumber }} · {{ batchProject(row.projectId)?.classNames.join('、') || '选课学生' }}</small>
              </div>
            </div>
            <el-date-picker
              v-model="row.sessionDate"
              type="date"
              value-format="YYYY-MM-DD"
              placeholder="实验日期"
            />
            <el-select v-model="row.startPeriod" placeholder="起始节次">
              <el-option
                v-for="period in periodsForProject(row.projectId)"
                :key="period.periodNumber"
                :label="period.name"
                :value="period.periodNumber"
              />
            </el-select>
            <el-input-number v-model="row.periodCount" :min="1" :max="12" controls-position="right" />
            <el-select v-model="row.classroomId" filterable placeholder="实验室">
              <el-option
                v-for="room in options.classrooms.filter((item: any) => (Number(item.teachingVenueNature) & (2 | 4 | 8 | 16)) !== 0)"
                :key="room.id"
                :label="`${room.campusName} · ${room.buildingName} ${room.name} · ${room.capacity} 人`"
                :value="room.id"
              />
            </el-select>
            <el-input-number v-model="row.capacity" :min="1" :max="10000" controls-position="right" />
          </article>
        </div>
        <el-empty v-if="!batchSessionRows.length" :image-size="60" description="选择实验项目后，在同一张表中完成排课" />
      </el-form>
      <template #footer>
        <el-button @click="batchSessionDialog = false">取消</el-button>
        <el-button type="primary" :disabled="!batchSessionRows.length" @click="saveBatchSessions">
          提交整批排课
        </el-button>
      </template>
    </el-dialog>

    <el-dialog
      v-model="participantsDialog"
      :title="`${participantSession?.project?.arrangementMode === 'Centralized' ? '应到名单' : '预约名单'} · ${participantSession ? formatSessionTime(participantSession) : ''}`"
      width="680px"
    >
      <el-table v-loading="participantsLoading" :data="participants" max-height="480">
        <el-table-column prop="studentNumber" label="学号" min-width="130" />
        <el-table-column prop="name" label="姓名" min-width="100" />
        <el-table-column prop="className" label="行政班" min-width="160" />
        <el-table-column label="参与方式" width="110">
          <template #default="{ row }">
            <el-tag
              :type="row.participationType === 'Centralized' ? 'primary' : 'success'"
              size="small"
              effect="plain"
            >
              {{ row.participationType === 'Centralized' ? '统一安排' : '学生预约' }}
            </el-tag>
          </template>
        </el-table-column>
      </el-table>
      <el-empty v-if="!participantsLoading && !participants.length" description="当前没有学生名单" />
    </el-dialog>
  </div>
</template>

<style scoped>
.experiment-page { --lab-blue: #245d82; --lab-teal: #168276; --lab-ink: #173041; }
.experiment-intro { align-items: flex-start; }
.intro-actions { display: flex; gap: 8px; align-items: center; }
.lab-rail {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 78px minmax(0, 1fr);
  gap: 0;
  margin-bottom: 16px;
  border: 1px solid #ccdce5;
  background: #f7fafc;
}
.rail-lane { position: relative; padding: 17px 20px 18px 24px; overflow: hidden; }
.rail-lane::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 5px;
  background: var(--lab-blue);
}
.rail-lane.flexible::before { background: var(--lab-teal); }
.rail-lane b { display: block; margin: 4px 0; color: var(--lab-ink); font-size: 15px; }
.rail-lane p { margin: 0; color: var(--muted); font-size: 12px; line-height: 1.65; }
.rail-code { color: #607987; font: 700 10px/1.2 Consolas, monospace; letter-spacing: .08em; }
.rail-switch {
  position: relative;
  display: grid;
  place-content: center;
  gap: 8px;
  background: linear-gradient(90deg, transparent 49%, #b9cbd5 49%, #b9cbd5 51%, transparent 51%);
}
.rail-switch i { width: 30px; height: 7px; border: 2px solid #7794a5; border-radius: 999px; background: #fff; }
.experiment-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-bottom: 16px; }
.experiment-toolbar .el-select { width: 210px; }
.result-note { margin-left: auto; color: var(--muted); font-size: 12px; }
.project-list { display: grid; gap: 14px; min-height: 160px; }
.project-card {
  overflow: hidden;
  border: 1px solid #dce5ea;
  border-top: 4px solid var(--lab-blue);
  background: #fff;
  box-shadow: 0 7px 22px rgb(35 67 85 / 5%);
}
.project-card.is-flexible { border-top-color: var(--lab-teal); }
.project-card.is-closed { opacity: .82; }
.project-head { display: flex; justify-content: space-between; gap: 16px; padding: 18px 20px 10px; }
.project-code { color: #667f8d; font: 700 10px/1.2 Consolas, monospace; letter-spacing: .06em; text-transform: uppercase; }
.project-identity h3 { margin: 5px 0 4px; color: var(--lab-ink); font-size: 18px; }
.project-identity p { margin: 0; color: var(--muted); font-size: 12px; }
.project-tags { display: flex; gap: 6px; align-items: flex-start; flex-shrink: 0; }
.project-context { display: flex; flex-wrap: wrap; gap: 7px 22px; padding: 0 20px 12px; color: #5c7481; font-size: 12px; }
.project-context span { display: inline-flex; gap: 7px; }
.project-context b { color: #304b5a; font-weight: 600; }
.project-description { margin: 0; padding: 10px 20px; border-top: 1px solid #edf1f3; color: #415b69; font-size: 13px; line-height: 1.7; }
.requirement-strip {
  display: grid;
  grid-template-columns: 74px 1fr;
  gap: 10px;
  margin: 0 20px 14px;
  padding: 10px 12px;
  border-left: 3px solid #e3a13c;
  background: #fff9ef;
  color: #6d5b3e;
  font-size: 12px;
  line-height: 1.6;
}
.session-board { margin: 0 20px 16px; border: 1px solid #dfe8ec; background: #f8fafb; }
.session-board-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 11px 13px; border-bottom: 1px solid #dfe8ec; }
.session-board-head div { display: grid; gap: 2px; }
.session-board-head span { color: #617986; font: 700 10px/1.2 Consolas, monospace; letter-spacing: .06em; text-transform: uppercase; }
.session-board-head b { color: #304d5d; font-size: 11px; font-weight: 500; }
.session-grid { display: grid; gap: 8px; padding: 10px; }
.session-ticket {
  display: grid;
  grid-template-columns: 58px minmax(0, 1fr) auto;
  gap: 12px;
  align-items: center;
  min-height: 74px;
  border: 1px solid #dce5e9;
  background: #fff;
  transition: border-color .18s ease, box-shadow .18s ease;
}
.session-ticket:hover { border-color: #9fb7c4; box-shadow: 0 5px 14px rgb(40 76 96 / 7%); }
.session-ticket.is-selected { border-color: var(--lab-teal); box-shadow: inset 4px 0 0 var(--lab-teal); }
.session-ticket.is-cancelled { opacity: .58; }
.ticket-date { display: grid; place-content: center; align-self: stretch; border-right: 1px dashed #cddbe2; background: #f0f5f7; text-align: center; }
.ticket-date strong { color: var(--lab-ink); font: 700 22px/1 Consolas, monospace; }
.ticket-date span { margin-top: 4px; color: #657d89; font-size: 10px; }
.ticket-body { display: grid; gap: 3px; min-width: 0; }
.ticket-body b { color: #243f4f; font-size: 13px; }
.ticket-body span { color: #5c7481; font-size: 12px; }
.ticket-body small { overflow: hidden; color: #81929b; font-size: 10px; text-overflow: ellipsis; white-space: nowrap; }
.ticket-action { display: flex; align-items: center; justify-content: flex-end; gap: 5px; padding-right: 10px; }
.project-actions { display: flex; justify-content: flex-end; gap: 8px; padding: 12px 20px; border-top: 1px solid #e5ecef; background: #fbfcfd; }
.student-booking-summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 20px;
  border-top: 1px solid #cce3de;
  background: #f0faf7;
}
.student-booking-summary > div { display: flex; align-items: center; gap: 10px; color: var(--lab-teal); }
.student-booking-summary svg { width: 22px; }
.student-booking-summary span { display: grid; gap: 2px; color: #365f5b; font-size: 12px; }
.student-booking-summary b { font-size: 10px; text-transform: uppercase; letter-spacing: .06em; }
.experiment-form { display: grid; gap: 13px; }
.form-help { display: block; margin-top: 7px; color: var(--muted); line-height: 1.5; }
.form-section { padding: 14px 15px 1px; border: 1px solid var(--line); background: #fbfcfd; }
.form-section > header { display: flex; align-items: baseline; gap: 9px; margin-bottom: 13px; }
.form-section > header span { color: var(--lab-teal); font: 700 10px/1 Consolas, monospace; letter-spacing: .08em; }
.form-section > header b { color: var(--lab-ink); font-size: 14px; }
.mode-choice { display: grid; grid-template-columns: 1fr 1fr; margin-bottom: 16px; }
.mode-choice :deep(.el-radio-button__inner) { display: grid; gap: 5px; width: 100%; padding: 13px; }
.mode-choice b { font-size: 13px; }
.mode-choice small { font-size: 10px; font-weight: 400; }
.batch-session-form { display: grid; gap: 12px; margin-top: 16px; }
.batch-session-tools { display: flex; align-items: center; justify-content: space-between; color: var(--muted); font-size: 12px; }
.batch-session-table { display: grid; gap: 8px; overflow-x: auto; padding-bottom: 4px; }
.batch-session-row {
  display: grid;
  grid-template-columns: minmax(230px, 1.4fr) 150px 120px 110px minmax(230px, 1.3fr) 110px;
  gap: 8px;
  align-items: center;
  min-width: 1000px;
  padding: 10px;
  border: 1px solid #dce5e9;
  background: #f9fbfc;
}
.batch-project-cell { display: flex; align-items: center; gap: 10px; min-width: 0; }
.batch-project-cell > span { display: grid; width: 25px; height: 25px; place-items: center; border-radius: 50%; background: #e5f0f5; color: var(--lab-blue); font: 700 11px/1 Consolas, monospace; }
.batch-project-cell > div { display: grid; gap: 3px; min-width: 0; }
.batch-project-cell b, .batch-project-cell small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.batch-project-cell b { color: var(--lab-ink); font-size: 12px; }
.batch-project-cell small { color: var(--muted); font-size: 10px; }
@media (prefers-reduced-motion: reduce) {
  .session-ticket { transition: none; }
}
@media (max-width: 760px) {
  .lab-rail { grid-template-columns: 1fr; }
  .rail-switch { display: none; }
  .rail-lane { border-bottom: 1px solid #d5e1e7; }
  .experiment-toolbar .el-select { width: 100%; }
  .result-note { width: 100%; margin-left: 0; }
  .project-head { align-items: flex-start; padding: 15px 14px 9px; }
  .project-context, .project-description { padding-inline: 14px; }
  .requirement-strip, .session-board { margin-inline: 14px; }
  .session-ticket { grid-template-columns: 54px minmax(0, 1fr); }
  .ticket-action { grid-column: 1 / -1; justify-content: flex-start; padding: 0 10px 10px; }
  .project-actions, .student-booking-summary { padding-inline: 14px; }
  .mode-choice { grid-template-columns: 1fr; }
  .intro-actions { flex-wrap: wrap; justify-content: flex-end; }
}
</style>
