<script setup lang="ts">
import { computed, onMounted, reactive, ref } from 'vue'
import {
  Check,
  ArrowRight,
  Delete,
  DocumentChecked,
  Download,
  EditPen,
  Plus,
  Promotion,
  Refresh,
  Search,
  Upload,
} from '@element-plus/icons-vue'
import http, { apiErrorMessage } from '../api/http'
import { downloadApiFile, importExcel } from '../api/excel'
import { useAuthStore } from '../stores/auth'
import { useRouter } from 'vue-router'
import { academicTermLabel, academicTermOptionClass, defaultAcademicTermId } from '../utils/academicTerms'

const auth = useAuthStore()
const router = useRouter()
const isStudent = computed(() => auth.user?.roles.includes('Student') &&
  !auth.user.roles.some((role) =>
    ['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin', 'Teacher'].includes(role)))
const terms = ref<any[]>([])
const tasks = ref<any[]>([])
const selectedTask = ref<any | null>(null)
const detail = ref<any | null>(null)
const transcript = ref<any>({ student: null, records: [] })
const loading = ref(false)
const detailLoading = ref(false)
const createDialog = ref(false)
const returnDialog = ref(false)
const termId = ref<string | undefined>()
const status = ref<string | undefined>()
const taskKeyword = ref('')
const taskPage = ref(1)
const taskPageSize = 12
const taskTotal = ref(0)
const recordKeyword = ref('')
const recordPage = ref(1)
const recordPageSize = 50
const experimentGradeItemId = ref('')
const createForm = reactive({
  regularWeight: 30,
  finalWeight: 70,
  items: [] as { name: string; weight: number }[],
})
const newItemName = ref('')
const newItemWeight = ref(0)
const returnComment = ref('')

const presetItems = ['实验', '实习', '课程设计', '作业', '课堂表现', '期中']

const statusLabels: Record<string, string> = {
  Draft: '录入中',
  Submitted: '待审核',
  Approved: '审核通过',
  Published: '已发布',
  Returned: '已退回',
}
const statusMeta: Record<string, { icon: string; description: string }> = {
  Draft: { icon: '📝', description: '教师正在录入成绩' },
  Submitted: { icon: '📤', description: '已提交，等待开课学院审核' },
  Approved: { icon: '✅', description: '学院审核通过，待校级发布' },
  Published: { icon: '📢', description: '成绩已向学生发布' },
  Returned: { icon: '↩️', description: '学院退回，需教师修改后重新提交' },
}
const examStatusLabels: Record<string, string> = {
  Normal: '正常',
  Absent: '缺考',
  Deferred: '缓考',
  Exempt: '免修',
}
const itemsWeight = computed(() =>
  createForm.items.reduce((sum, item) => sum + item.weight, 0))
const weightSum = computed(() =>
  createForm.regularWeight + createForm.finalWeight + itemsWeight.value)
const completedCount = computed(() =>
  selectedTask.value?.sheet?.completedCount ?? 0)
const averageScore = computed(() => {
  const scores = (detail.value?.records ?? [])
    .map((record: any) => Number(record.totalScore))
    .filter((value: number) => Number.isFinite(value))
  if (!scores.length) return '—'
  return (scores.reduce((sum: number, value: number) => sum + value, 0) / scores.length)
    .toFixed(1)
})
const passRate = computed(() => {
  const completed = Number(selectedTask.value?.sheet?.completedCount ?? 0)
  if (!completed) return '—'
  return `${Math.round(Number(selectedTask.value?.sheet?.passedCount ?? 0) / completed * 100)}%`
})
const transcriptGpa = computed(() => {
  const numeric = transcript.value.records.filter((record: any) => record.gradePoint != null)
  const credits = numeric.reduce((sum: number, record: any) => sum + Number(record.credits), 0)
  if (!credits) return '—'
  const points = numeric.reduce(
    (sum: number, record: any) => sum + Number(record.gradePoint) * Number(record.credits), 0,
  )
  return (points / credits).toFixed(2)
})
const earnedCredits = computed(() =>
  transcript.value.records
    .filter((record: any) => Number(record.totalScore) >= 60)
    .reduce((sum: number, record: any) => sum + Number(record.credits), 0))

function addItem() {
  const name = newItemName.value.trim() || '自定义分项'
  if (createForm.items.some(item => item.name === name)) {
    ElMessage.warning('分项名称不能重复。')
    return
  }
  createForm.items.push({ name, weight: newItemWeight.value || 0 })
  newItemName.value = ''
  newItemWeight.value = 0
}

function addPresetItem(name: string) {
  if (createForm.items.some(item => item.name === name)) {
    ElMessage.warning('该分项已存在。')
    return
  }
  createForm.items.push({ name, weight: 0 })
}

function removeItem(index: number) {
  createForm.items.splice(index, 1)
}

function openStatistics(record: any) {
  router.push({ name: 'course-grade-statistics', params: { sheetId: record.gradeSheetId } })
}

async function load(resetPage = false) {
  loading.value = true
  try {
    if (isStudent.value) {
      transcript.value = (await http.get('/grades/student/transcript', {
        params: { academicTermId: termId.value },
      })).data
      return
    }
    if (resetPage) taskPage.value = 1
    const response = (await http.get('/grades/sheets', {
      params: {
        academicTermId: termId.value,
        status: status.value,
        keyword: taskKeyword.value.trim() || undefined,
        page: taskPage.value,
        pageSize: taskPageSize,
      },
    })).data
    tasks.value = response.items
    taskTotal.value = response.total
    const preferred = tasks.value.find((item) => item.id === selectedTask.value?.id)
    if (preferred) {
      selectedTask.value = preferred
      if (preferred.sheet) await loadDetail()
    } else {
      selectedTask.value = null
      detail.value = null
    }
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  } finally {
    loading.value = false
  }
}

async function selectTask(task: any) {
  selectedTask.value = task
  recordPage.value = 1
  recordKeyword.value = ''
  detail.value = null
  if (!task.sheet) return
  await loadDetail()
}

async function loadDetail() {
  if (!selectedTask.value?.sheet) return
  detailLoading.value = true
  try {
    const response = (await http.get(`/grades/sheets/${selectedTask.value.sheet.id}`, {
      params: {
        recordPage: recordPage.value,
        recordPageSize,
        studentKeyword: recordKeyword.value.trim() || undefined,
      },
    })).data
    detail.value = response.sheet
    detail.value.canEdit = response.canEdit
    detail.value.canReview = response.canReview
    detail.value.canPublish = response.canPublish
    if (!detail.value.items?.some((item: any) => item.id === experimentGradeItemId.value)) {
      experimentGradeItemId.value =
        detail.value.items?.find((item: any) => item.name.includes('实验'))?.id
        ?? detail.value.items?.[0]?.id
        ?? ''
    }
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  } finally {
    detailLoading.value = false
  }
}

async function changeTaskPage(page: number) {
  taskPage.value = page
  await load()
}

async function searchRecords() {
  recordPage.value = 1
  await loadDetail()
}

function openCreate() {
  createForm.regularWeight = 30
  createForm.finalWeight = 70
  createForm.items = []
  newItemName.value = ''
  newItemWeight.value = 0
  createDialog.value = true
}

async function createSheet() {
  if (weightSum.value !== 100) {
    ElMessage.warning(`平时、期末与所有分项的比例必须合计 100%（当前 ${weightSum.value}%）。`)
    return
  }
  try {
    await http.post('/grades/sheets', {
      teachingTaskId: selectedTask.value.id,
      regularWeight: createForm.regularWeight,
      finalWeight: createForm.finalWeight,
      items: createForm.items.map(item => ({ name: item.name, weight: item.weight })),
    })
    createDialog.value = false
    ElMessage.success('成绩登记册已建立')
    await load()
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  }
}

async function saveRecords() {
  try {
    await http.put(`/grades/sheets/${detail.value.id}/records`, {
      records: detail.value.records.map((record: any) => ({
        id: record.id,
        regularScore: record.regularScore,
        finalScore: record.finalScore,
        itemScores: record.itemScores?.map((itemScore: any) => ({
          gradeItemId: itemScore.gradeItemId,
          score: itemScore.score,
        })),
        examStatus: record.examStatus,
        notes: record.notes,
      })),
    })
    ElMessage.success('成绩已保存并重新计算总评')
    await loadDetail()
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  }
}

async function importExperimentScores() {
  if (!experimentGradeItemId.value) {
    ElMessage.warning('请先选择用于承接实验成绩的课程成绩项。')
    return
  }

  try {
    const result = (await http.post(
      `/grades/sheets/${detail.value.id}/import-experiment-scores`,
      { gradeItemId: experimentGradeItemId.value },
    )).data
    const missingHint = result.missingCount > 0
      ? `；${result.missingCount} 名学生因实验成绩尚未完整发布而保持空白`
      : ''
    ElMessage.success(
      `已从 ${result.publishedProjectCount} 个实验项目导入 ${result.importedCount} 名学生的成绩快照${missingHint}`,
    )
    await loadDetail()
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  }
}

async function submitSheet() {
  try {
    await ElMessageBox.confirm(
      '提交后成绩将锁定并进入学院审核，确认所有学生成绩已经核对？',
      '提交成绩',
      { type: 'warning', confirmButtonText: '确认提交', cancelButtonText: '继续检查' },
    )
    await http.post(`/grades/sheets/${detail.value.id}/submit`)
    ElMessage.success('成绩单已提交审核')
    await load()
  } catch (error: any) {
    if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error))
  }
}

const importFileInput = ref<HTMLInputElement>()

function downloadTemplate() {
  downloadApiFile(
    `/grades/sheets/${detail.value.id}/template`,
    '成绩导入模板.xlsx',
  )
}

function chooseImportFile() {
  importFileInput.value?.click()
}

async function handleImport(event: Event) {
  const input = event.target as HTMLInputElement
  const file = input.files?.[0]
  if (!file) return
  try {
    const result = await importExcel(
      `/grades/sheets/${detail.value.id}/import`,
      file,
    )
    ElMessage.success(`导入完成：已更新 ${result.data.updated} 条成绩记录`)
    await loadDetail()
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  } finally {
    input.value = ''
  }
}

async function approveSheet() {
  try {
    await http.post(`/grades/sheets/${detail.value.id}/approve`)
    ElMessage.success('成绩单已通过审核')
    await load()
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  }
}

function openReturn() {
  returnComment.value = ''
  returnDialog.value = true
}

async function returnSheet() {
  if (!returnComment.value.trim()) {
    ElMessage.warning('请填写需要教师修改的具体内容。')
    return
  }
  try {
    await http.post(`/grades/sheets/${detail.value.id}/return`, {
      comment: returnComment.value,
    })
    returnDialog.value = false
    ElMessage.success('成绩单已退回教师修改')
    await load()
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  }
}

async function publishSheet() {
  try {
    await ElMessageBox.confirm(
      '发布后学生即可查看成绩，当前版本将锁定。确定发布？',
      '发布成绩',
      { type: 'warning', confirmButtonText: '确认发布', cancelButtonText: '取消' },
    )
    await http.post(`/grades/sheets/${detail.value.id}/publish`)
    ElMessage.success('成绩已向学生发布')
    await load()
  } catch (error: any) {
    if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error))
  }
}

function scoreClass(score: number | null) {
  if (score == null) return 'pending'
  if (score < 60) return 'failed'
  if (score >= 90) return 'excellent'
  return ''
}

function isFailingScore(score: number | string | null | undefined) {
  return score != null && score !== '' && Number(score) < 60
}

function itemScore(record: any, gradeItemId: string) {
  return record.itemScores?.find((itemScore: any) => itemScore.gradeItemId === gradeItemId)
}

/** Client-side preview calculation — mirrors backend GradeCalculator.CalculateTotal */
function calcPreviewTotal(record: any): number | null {
  if (!detail.value) return null
  if (record.examStatus !== 'Normal') return null

  const { regularWeight, finalWeight, items } = detail.value
  const itemWeightById: Record<string, number> = {}
  for (const item of items ?? []) itemWeightById[item.id] = item.weight

  // Check required scores are present
  if (regularWeight > 0 && (record.regularScore == null || record.regularScore === '')) return null
  if (finalWeight > 0 && (record.finalScore == null || record.finalScore === '')) return null
  for (const itemScore of record.itemScores ?? []) {
    const weight = itemWeightById[itemScore.gradeItemId]
    if (weight > 0 && (itemScore.score == null || itemScore.score === '')) return null
  }

  let total = (Number(record.regularScore) || 0) * regularWeight / 100
  for (const itemScore of record.itemScores ?? []) {
    const weight = itemWeightById[itemScore.gradeItemId] || 0
    total += (Number(itemScore.score) || 0) * weight / 100
  }
  total += (Number(record.finalScore) || 0) * finalWeight / 100
  return Math.round(total * 10) / 10
}

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

<template>
  <div class="page-stack grade-page">
    <section class="page-intro">
      <div>
        <span class="section-kicker">ACADEMIC RECORD</span>
        <h2>{{ isStudent ? '学业成绩单' : '成绩管理' }}</h2>
        <p v-if="isStudent">查看学校已正式发布的课程成绩、学分与绩点。</p>
        <p v-else>从教师登记、学院复核到校级发布，支持自定义平时、实验、实习等分项。</p>
      </div>
      <el-button :icon="Refresh" @click="load()">刷新</el-button>
    </section>

    <template v-if="isStudent">
      <section class="transcript-identity">
        <div>
          <span>STUDENT TRANSCRIPT</span>
          <h3>{{ transcript.student?.name ?? '学生' }}</h3>
          <p>{{ transcript.student?.studentNumber }} · {{ transcript.student?.majorName }} · {{ transcript.student?.className }}</p>
        </div>
        <label>
          <span>查看学期</span>
          <el-select v-model="termId" clearable placeholder="全部学期" @change="load()">
            <el-option v-for="term in terms" :key="term.id" :label="academicTermLabel(term)" :value="term.id" :class="academicTermOptionClass(term)" />
          </el-select>
        </label>
      </section>

      <section class="transcript-ribbon">
        <div><span>已发布课程</span><b>{{ transcript.records.length }}</b><small>门</small></div>
        <div><span>获得学分</span><b>{{ earnedCredits }}</b><small>分</small></div>
        <div><span>加权平均绩点</span><b>{{ transcriptGpa }}</b><small>GPA</small></div>
      </section>

      <section class="transcript-list" v-loading="loading">
        <article
          v-for="record in transcript.records"
          :key="record.id"
          class="transcript-course-card"
          role="button"
          tabindex="0"
          :aria-label="`查看${record.courseName}成绩分析`"
          @click="openStatistics(record)"
          @keydown.enter="openStatistics(record)"
          @keydown.space.prevent="openStatistics(record)"
        >
          <div class="transcript-course">
            <span>{{ record.termName }}</span>
            <h3>{{ record.courseName }}</h3>
            <p>{{ record.courseCode }} · {{ record.taskNumber }} · {{ record.credits }} 学分</p>
          </div>
          <div class="grade-scale">
            <i v-for="tick in 11" :key="tick" />
            <span
              v-if="record.totalScore != null"
              :class="scoreClass(record.totalScore)"
              :style="{ left: `${record.totalScore}%` }"
            >{{ record.totalScore }}</span>
          </div>
          <div class="transcript-result" :class="{ 'is-failed': isFailingScore(record.totalScore) }">
            <span>{{ examStatusLabels[record.examStatus] }}</span>
            <b v-if="record.totalScore != null">{{ record.totalScore }}</b>
            <b v-else>—</b>
            <small>绩点 {{ record.gradePoint ?? '—' }}</small>
            <span class="card-entry">查看成绩分析 <el-icon><ArrowRight /></el-icon></span>
          </div>
        </article>
        <el-empty v-if="!transcript.records.length" description="当前范围内暂无已发布成绩" />
      </section>
    </template>

    <template v-else>
      <section class="grade-toolbar">
        <el-input
          v-model="taskKeyword"
          clearable
          :prefix-icon="Search"
          placeholder="搜索课程、课程号或教学班号"
          @keyup.enter="load(true)"
          @clear="load(true)"
        />
        <el-select v-model="termId" clearable placeholder="全部学期" @change="load(true)">
          <el-option v-for="term in terms" :key="term.id" :label="academicTermLabel(term)" :value="term.id" :class="academicTermOptionClass(term)" />
        </el-select>
        <el-select v-model="status" clearable placeholder="全部状态" @change="load(true)">
          <el-option v-for="(label, value) in statusLabels" :key="value" :label="label" :value="value" />
        </el-select>
        <el-button :icon="Search" @click="load(true)">查询</el-button>
        <span>共 {{ taskTotal }} 个可管理教学班</span>
      </section>

      <section class="grade-workspace" v-loading="loading">
        <aside class="grade-sheet-list">
          <button
            v-for="task in tasks"
            :key="task.id"
            type="button"
            :class="{ active: selectedTask?.id === task.id }"
            @click="selectTask(task)"
          >
            <span>{{ task.courseCode }} · {{ task.taskNumber }}</span>
            <b>{{ task.courseName }}</b>
            <small>{{ task.teacherNames.join('、') }} · {{ task.classNames.join('、') }}</small>
            <i :class="task.sheet?.status?.toLowerCase()">
              {{ task.sheet ? statusLabels[task.sheet.status] : '未建册' }}
            </i>
          </button>
          <el-empty v-if="!tasks.length" description="没有可管理的教学班" />
          <el-pagination
            v-if="taskTotal > taskPageSize"
            class="grade-task-pagination"
            small
            layout="prev, pager, next"
            :current-page="taskPage"
            :page-size="taskPageSize"
            :total="taskTotal"
            @current-change="changeTaskPage"
          />
        </aside>

        <main class="grade-register" v-loading="detailLoading">
          <el-empty v-if="!selectedTask" description="从左侧选择一个教学班，进入对应成绩册" />
          <section v-else-if="!selectedTask.sheet" class="grade-create-empty">
            <el-icon><DocumentChecked /></el-icon>
            <h3>{{ selectedTask.courseName }}</h3>
            <p>{{ selectedTask.taskNumber }} 尚未建立成绩登记册。系统会从有效选课名单生成学生行。</p>
            <el-button type="primary" :icon="Plus" @click="openCreate">建立成绩登记册</el-button>
          </section>
          <template v-else-if="detail">
            <header class="grade-register-head">
              <div>
                <span>{{ detail.courseCode }} · {{ detail.taskNumber }}</span>
                <h3>{{ detail.courseName }}成绩登记册</h3>
                <p>{{ detail.termName }} · {{ detail.teacherNames.join('、') }} · {{ detail.classNames.join('、') }}</p>
                <p v-if="detail.courseCollegeName" class="college-meta">开课学院：{{ detail.courseCollegeName }}</p>
              </div>
              <div class="status-badge-group">
                <i :class="detail.status.toLowerCase()">{{ statusLabels[detail.status] }}</i>
                <small v-if="detail.status === 'Submitted' && detail.needsCollegeReview">
                  需 {{ detail.needsCollegeReview }} 审核
                </small>
              </div>
            </header>

            <section class="grade-register-ruler">
              <div><span>学生数</span><b>{{ detail.recordTotal }}</b></div>
              <div><span>已完成</span><b>{{ completedCount }}</b></div>
              <div><span>本页平均</span><b>{{ averageScore }}</b></div>
              <div><span>及格率</span><b>{{ passRate }}</b></div>
              <p>
                平时 {{ detail.regularWeight }}%
                <template v-if="detail.items?.length">{{ detail.items.map((item: any) => ` · ${item.name} ${item.weight}%`).join('') }}</template>
                · 期末 {{ detail.finalWeight }}%
              </p>
            </section>

            <el-alert
              v-if="detail.status === 'Returned'"
              :title="`审核退回：${detail.reviewComment}`"
              type="warning"
              :closable="false"
              show-icon
            />

            <section class="grade-record-toolbar">
              <el-input
                v-model="recordKeyword"
                clearable
                :prefix-icon="Search"
                placeholder="搜索姓名、学号或行政班"
                @keyup.enter="searchRecords"
                @clear="searchRecords"
              />
              <el-button :icon="Search" @click="searchRecords">查找学生</el-button>
              <span>每页最多显示 {{ recordPageSize }} 人，当前第 {{ detail.recordPage }} 页</span>
            </section>

            <div class="grade-table-scroll">
              <el-table :data="detail.records" class="data-table grade-entry-table">
                <el-table-column label="学生" fixed min-width="165">
                  <template #default="{ row }">
                    <div class="record-name">
                      <b>{{ row.name }}</b>
                      <span>{{ row.studentNumber }} · {{ row.className }}</span>
                    </div>
                  </template>
                </el-table-column>
                <el-table-column :label="`平时 ${detail.regularWeight}%`" width="110">
                  <template #default="{ row }">
                    <div class="score-cell" :class="{ 'is-failed': isFailingScore(row.regularScore) }">
                      <el-input-number
                        v-if="detail.canEdit && row.examStatus === 'Normal'"
                        v-model="row.regularScore"
                        :min="0" :max="100" :controls="false" size="small"
                      />
                      <span v-else>{{ row.regularScore ?? '—' }}</span>
                    </div>
                  </template>
                </el-table-column>
                <el-table-column
                  v-for="item in detail.items"
                  :key="item.id"
                  :label="`${item.name} ${item.weight}%${item.sourceType === 'ExperimentSummary' ? ' · 实验快照' : ''}`"
                  :width="item.sourceType === 'ExperimentSummary' ? 150 : 110"
                >
                  <template #default="{ row }">
                    <div class="score-cell" :class="{ 'is-failed': isFailingScore(itemScore(row, item.id)?.score) }">
                      <el-input-number
                        v-if="detail.canEdit && row.examStatus === 'Normal' && item.sourceType !== 'ExperimentSummary'"
                        :model-value="itemScore(row, item.id)?.score"
                        @update:model-value="(val: number | undefined) => { const found = itemScore(row, item.id); if (found) found.score = val }"
                        :min="0" :max="100" :controls="false" size="small"
                      />
                      <span v-else class="item-score-readonly">
                        {{ itemScore(row, item.id)?.score ?? '—' }}
                        <small v-if="item.sourceType === 'ExperimentSummary'">已锁定</small>
                      </span>
                    </div>
                  </template>
                </el-table-column>
                <el-table-column :label="`期末 ${detail.finalWeight}%`" width="110">
                  <template #default="{ row }">
                    <div class="score-cell" :class="{ 'is-failed': isFailingScore(row.finalScore) }">
                      <el-input-number
                        v-if="detail.canEdit && row.examStatus === 'Normal'"
                        v-model="row.finalScore"
                        :min="0" :max="100" :controls="false" size="small"
                      />
                      <span v-else>{{ row.finalScore ?? '—' }}</span>
                    </div>
                  </template>
                </el-table-column>
                <el-table-column label="总评" width="90">
                  <template #default="{ row }">
                    <div
                      class="score-cell"
                      :class="{ 'is-failed': isFailingScore(detail.canEdit && row.examStatus === 'Normal' ? calcPreviewTotal(row) : row.totalScore) }"
                    >
                      <template v-if="detail.canEdit && row.examStatus === 'Normal'">
                        <b class="total-score preview" :class="scoreClass(calcPreviewTotal(row))">
                          {{ calcPreviewTotal(row) ?? '—' }}
                        </b>
                      </template>
                      <b v-else class="total-score" :class="scoreClass(row.totalScore)">{{ row.totalScore ?? '—' }}</b>
                    </div>
                  </template>
                </el-table-column>
                <el-table-column label="考试状态" width="115">
                  <template #default="{ row }">
                    <el-select v-if="detail.canEdit" v-model="row.examStatus" size="small">
                      <el-option v-for="(label, value) in examStatusLabels" :key="value" :label="label" :value="value" />
                    </el-select>
                    <span v-else>{{ examStatusLabels[row.examStatus] }}</span>
                  </template>
                </el-table-column>
                <el-table-column label="备注" min-width="140">
                  <template #default="{ row }">
                    <el-input v-if="detail.canEdit" v-model="row.notes" maxlength="300" size="small" />
                    <span v-else>{{ row.notes || '—' }}</span>
                  </template>
                </el-table-column>
              </el-table>
            </div>
            <el-pagination
              v-if="detail.recordTotal > recordPageSize"
              class="grade-record-pagination"
              background
              layout="total, prev, pager, next"
              :current-page="recordPage"
              :page-size="recordPageSize"
              :total="detail.recordTotal"
              @current-change="(page: number) => { recordPage = page; loadDetail() }"
            />

            <input ref="importFileInput" type="file" accept=".xlsx" style="display:none" @change="handleImport" />

            <footer class="grade-actions">
              <div class="workflow-status">
                <span>{{ statusMeta[detail.status]?.icon }} {{ statusLabels[detail.status] }}</span>
                <small>{{ statusMeta[detail.status]?.description }}</small>
                <small v-if="detail.needsCollegeReview && (detail.status === 'Submitted' || detail.status === 'Approved')" class="college-hint">
                  开课学院：{{ detail.needsCollegeReview }}
                </small>
              </div>
              <div class="grade-actions-buttons">
                <div v-if="detail.canEdit && detail.items?.length" class="experiment-import">
                  <el-select
                    v-model="experimentGradeItemId"
                    placeholder="选择课程成绩项"
                    aria-label="实验成绩导入目标"
                  >
                    <el-option
                      v-for="item in detail.items"
                      :key="item.id"
                      :label="`${item.name} ${item.weight}%`"
                      :value="item.id"
                    />
                  </el-select>
                  <el-button :icon="Refresh" @click="importExperimentScores">导入实验成绩</el-button>
                </div>
                <el-button v-if="detail.canEdit" :icon="Download" @click="downloadTemplate">下载模板</el-button>
                <el-button v-if="detail.canEdit" :icon="Upload" @click="chooseImportFile">Excel导入</el-button>
                <el-button v-if="detail.canEdit" :icon="EditPen" @click="saveRecords">保存成绩</el-button>
                <el-button v-if="detail.canEdit" type="primary" :icon="Promotion" @click="submitSheet">提交审核</el-button>
                <el-button v-if="detail.canReview" type="danger" plain @click="openReturn">退回修改</el-button>
                <el-button v-if="detail.canReview" type="success" :icon="Check" @click="approveSheet">审核通过</el-button>
                <el-button v-if="detail.canPublish" type="primary" :icon="Promotion" @click="publishSheet">发布成绩</el-button>
              </div>
            </footer>
          </template>
        </main>
      </section>
    </template>

    <el-dialog v-model="createDialog" title="建立成绩登记册" width="620px">
      <el-alert title="平时、期末与所有分项的比例必须合计 100%" type="info" :closable="false" />
      <el-form label-position="top" class="grade-weight-form">
        <div class="form-grid three">
          <el-form-item label="平时成绩 %">
            <el-input-number v-model="createForm.regularWeight" :min="0" :max="100" />
          </el-form-item>
          <el-form-item label="期末成绩 %">
            <el-input-number v-model="createForm.finalWeight" :min="0" :max="100" />
          </el-form-item>
          <el-form-item label="当前合计">
            <div class="weight-sum" :class="{ invalid: weightSum !== 100 }">
              <b>{{ weightSum }}%</b>
              <span v-if="weightSum !== 100">需为 100%</span>
              <span v-else class="valid">✓</span>
            </div>
          </el-form-item>
        </div>

        <el-divider>成绩分项（实验、实习、课程设计等）</el-divider>

        <div class="preset-items">
          <span>快速添加：</span>
          <el-button
            v-for="name in presetItems" :key="name"
            size="small" @click="addPresetItem(name)"
          >{{ name }}</el-button>
        </div>

        <div v-if="createForm.items.length" class="item-list">
          <div v-for="(item, index) in createForm.items" :key="index" class="item-row">
            <span>{{ item.name }}</span>
            <el-input-number v-model="item.weight" :min="0" :max="100" size="small" />
            <span>%</span>
            <el-button :icon="Delete" link type="danger" @click="removeItem(index)" />
          </div>
        </div>

        <div class="add-item-row">
          <el-input v-model="newItemName" placeholder="自定义名称" size="small" style="width:160px" />
          <el-input-number v-model="newItemWeight" :min="0" :max="100" size="small" />
          <span>%</span>
          <el-button size="small" :icon="Plus" @click="addItem">添加</el-button>
        </div>
      </el-form>
      <template #footer>
        <el-button @click="createDialog = false">取消</el-button>
        <el-button type="primary" :disabled="weightSum !== 100" @click="createSheet">建立登记册</el-button>
      </template>
    </el-dialog>

    <el-dialog v-model="returnDialog" title="退回成绩单" width="560px">
      <el-form label-position="top">
        <el-form-item label="修改意见" required>
          <el-input v-model="returnComment" type="textarea" :rows="4" maxlength="500" show-word-limit />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="returnDialog = false">取消</el-button>
        <el-button type="danger" @click="returnSheet">确认退回</el-button>
      </template>
    </el-dialog>
  </div>
</template>

<style scoped>
.grade-weight-form { margin-top: 16px; }
.weight-sum { display: flex; align-items: baseline; gap: 8px; padding: 12px; border-radius: 6px; background: #f5f7fa; }
.weight-sum b { font: 700 22px/1 Consolas, monospace; color: var(--indigo); }
.weight-sum.invalid b { color: #b34e48; }
.weight-sum span { font-size: 11px; color: var(--muted); }
.weight-sum .valid { color: #2d8975; font-weight: 700; }
.preset-items { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-bottom: 12px; }
.preset-items > span { font-size: 11px; color: var(--muted); }
.item-list { display: grid; gap: 6px; margin-bottom: 12px; }
.item-row { display: flex; align-items: center; gap: 8px; padding: 6px 10px; background: #eef5f0; border-radius: 4px; }
.item-row > span:first-child { flex: 1; font-size: 13px; font-weight: 650; }
.add-item-row { display: flex; align-items: center; gap: 8px; }

.score-cell {
  min-height: 30px;
  margin: -5px;
  padding: 5px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid transparent;
  border-radius: 4px;
}
.score-cell.is-failed {
  color: #b42318;
  background: #fdecec;
  border-color: #f4b8b2;
  font-weight: 700;
}
.score-cell.is-failed :deep(input) { color: #b42318; font-weight: 700; }
.transcript-result.is-failed {
  color: #b42318;
  background: #fdecec;
  border: 1px solid #f4b8b2;
  border-radius: 4px;
  padding: 8px;
}
.transcript-result.is-failed b { color: #b42318; }
.transcript-course-card { cursor: pointer; transition: transform .18s ease, border-color .18s ease, box-shadow .18s ease; }
.transcript-course-card:hover { transform: translateY(-2px); border-color: #91a8c8; box-shadow: 0 10px 24px rgb(31 66 112 / 9%); }
.transcript-course-card:focus-visible { outline: 2px solid #315a9b; outline-offset: 3px; }
.card-entry { display: inline-flex !important; align-items: center; justify-content: flex-end; gap: 4px; margin-top: 8px; color: #315a9b !important; font-size: 10px !important; font-weight: 700; }

/* Preview total score */
.total-score.preview {
  text-decoration: underline dashed;
  text-underline-offset: 3px;
  text-decoration-color: var(--muted);
  cursor: help;
}

/* College review */
.college-meta { color: var(--muted); font-size: 12px; margin-top: 2px; }
.status-badge-group { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; }
.status-badge-group small { font-size: 11px; color: var(--muted); }
.workflow-status { display: flex; flex-direction: column; gap: 2px; }
.workflow-status span { font-weight: 650; font-size: 13px; }
.workflow-status small { color: var(--muted); font-size: 11px; }
.workflow-status .college-hint { color: #6b4e16; font-weight: 600; }
.grade-actions { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 8px; padding: 12px 16px; border-top: 1px solid #e4e7ed; background: #fafbfc; }
.grade-actions-buttons { display: flex; gap: 8px; flex-wrap: wrap; }
.experiment-import { display: flex; gap: 6px; align-items: center; padding-right: 8px; border-right: 1px solid var(--line); }
.experiment-import .el-select { width: 165px; }
.item-score-readonly { display: inline-flex; flex-direction: column; align-items: center; line-height: 1.1; }
.item-score-readonly small { margin-top: 3px; color: #2d8975; font-size: 10px; font-weight: 650; }
.grade-toolbar > .el-input { width: min(320px, 100%); }
.grade-task-pagination { justify-content: center; padding: 14px 8px; border-top: 1px solid var(--line); }
.grade-record-toolbar {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 12px 16px; border-bottom: 1px solid var(--line); background: #f7f9fb;
}
.grade-record-toolbar .el-input { width: min(300px, 100%); }
.grade-record-toolbar > span { margin-left: auto; color: var(--muted); font-size: 11px; }
.grade-record-pagination { justify-content: flex-end; padding: 14px 16px; border-top: 1px solid var(--line); }
@media (max-width: 760px) {
  .grade-record-toolbar > span { width: 100%; margin-left: 0; }
  .grade-record-pagination { justify-content: center; }
  .experiment-import { width: 100%; padding-right: 0; padding-bottom: 8px; border-right: 0; border-bottom: 1px solid var(--line); }
  .experiment-import .el-select { flex: 1; width: auto; }
}
</style>
