<script setup lang="ts">
import { computed, ref } from 'vue'

import MdKatex from '@vscode/markdown-it-katex'
import { ElMessage, ElMessageBox } from 'element-plus'
import MarkdownIt from 'markdown-it'

import { uploadImgToPath } from '#/api/global'
import { taskMgrService } from '#/api/service/question-bank/task-mgr'
import ImageAnnotator from '#/components/image-annotator/index.vue'
import PaperQuestionItem from '#/components/paper-question-item/index.vue'

import 'katex/dist/katex.min.css'

const props = defineProps({
  resourceId: {
    type: String,
    default: '',
  },
  classId: {
    type: String,
    default: '',
  },
  students: {
    type: Array,
    default: () => [],
  },
})
const emit = defineEmits(['refresh'])
const studentDialogVisible = ref(false)
const paperRecordDetail = ref<any>({})
const aiReasonMarkdown = new MarkdownIt({
  breaks: true,
  html: false,
  linkify: true,
})
aiReasonMarkdown.use(MdKatex)

function formatDuration(val: any) {
  const ms = Number(val || 0)
  const total = Math.max(0, Math.floor(ms / 1000))
  const hh = String(Math.floor(total / 3600)).padStart(2, '0')
  const mm = String(Math.floor((total % 3600) / 60)).padStart(2, '0')
  const ss = String(total % 60).padStart(2, '0')
  return `${hh}:${mm}:${ss}`
}

type StudentPerformanceLevel =
  | 'unsubmitted'
  | 'ungraded'
  | 'excellent'
  | 'good'
  | 'pass'
  | 'fail'
  | 'default'

function resolvePerformanceLevel(item: any): StudentPerformanceLevel {
  if (item.status === 0) return 'unsubmitted'
  if (item.gradingStatus === 0) return 'ungraded'

  const level = Number(item?.level ?? item?.scoreLevel ?? 0)
  if (level === 1) return 'excellent'
  if (level === 2) return 'good'
  if (level === 3) return 'pass'
  if (level === 4) return 'fail'

  const rate = Number(item?.scoreRate ?? 0)
  if (rate >= 90) return 'excellent'
  if (rate >= 80) return 'good'
  if (rate >= 60) return 'pass'
  if (rate > 0) return 'fail'
  return 'default'
}

function getGradingStatusText(item: any) {
  const performanceLevel = resolvePerformanceLevel(item)
  if (performanceLevel === 'unsubmitted') return '未提交'
  if (performanceLevel === 'ungraded') return '未批改'
  if (performanceLevel === 'excellent') return '优秀'
  if (performanceLevel === 'good') return '良好'
  if (performanceLevel === 'pass') return '及格'
  if (performanceLevel === 'fail') return '不及格'
  return '已批改'
}

function getTableRowClassName({ row }: { row: any }) {
  return row.rowClass || ''
}

function getCorrectLabel(actualScore: any, score: any) {
  if (typeof actualScore !== 'number') return '-'
  if (actualScore === score) return '满分'
  if (actualScore === 0) return '答错'
  return '部分正确'
}
function getCorrectClass(actualScore: any, score: any) {
  if (typeof actualScore !== 'number') return ''
  if (actualScore === score) return 'mark-success'
  if (actualScore === 0) return 'mark-error'
  return 'mark-partial'
}

const finalScore = computed(() => {
  const list = paperRecordDetail.value?.titleVoList
  if (!Array.isArray(list)) return '-'
  let total = 0
  for (const item of list) {
    total += Number(item.actualScore ?? 0)
  }
  return total
})

const rows = computed(() => {
  const list = Array.isArray(props.students) ? props.students : []
  return list.map((item: any, idx: number) => {
    const score = Number(item?.score ?? 0)
    const duration = formatDuration(item?.costTime)
    const canView = item.status !== 0 && item.gradingStatus !== 0
    const performanceLevel = resolvePerformanceLevel(item)
    return {
      index: idx + 1,
      className: item?.className ?? item?.gradeClassName ?? '',
      userName: item?.userName ?? item?.name ?? item?.studentName ?? '',
      costTime: duration,
      costTimeMs: Number(item?.costTime ?? 0),
      score,
      scoreRate: Number(item?.scoreRate ?? 0),
      scoreRateText: `${item.scoreRate.toFixed(2)}%`,
      gradingStatusText: getGradingStatusText(item),
      performanceLevel,
      rowClass: `student-row--${performanceLevel}`,
      recordId: item?.recordId ?? '',
      canView,
      canRegrade: canView,
    }
  })
})

function isSubjectiveItem(item: any): boolean {
  const typeName = item?.titleChannelTypeName || ''
  const objectiveTypes = [
    '单选题',
    '多选题',
    '选择题',
    '判断题',
    '填空题',
    '短文还原',
  ]
  if (objectiveTypes.some((t) => typeName.includes(t))) return false
  if (item?.objective === 1) return false
  const hasOptions =
    Array.isArray(item?.optionList) && item.optionList.length > 0
  if (hasOptions) return false
  return true
}

function getAiVerdict(item: any) {
  const actual = item.actualScore
  const max = item.score
  if (typeof actual !== 'number')
    return { label: '未批改', type: 'info' as const, icon: '⏳' }
  if (actual === max)
    return { label: '正确', type: 'success' as const, icon: '✅' }
  if (actual === 0)
    return { label: '错误', type: 'danger' as const, icon: '❌' }
  return {
    label: '部分正确',
    type: 'warning' as const,
    icon: '⚖️',
  }
}

function escapeRegExp(str: string): string {
  // eslint-disable-next-line unicorn/prefer-string-replace-all
  return str.replace(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`)
}

function highlightKeywords(text: string, keywords: string[]): string {
  if (!text || !keywords?.length) return text || ''
  let result = text
  for (const kw of keywords) {
    if (!kw) continue
    const pattern = new RegExp(escapeRegExp(kw), 'gi')
    result = result.replace(pattern, '<mark class="kw-highlight">$&</mark>')
  }
  return result
}

function formatParagraphs(text: string): string {
  if (!text) return ''
  return text
    .split(/\n+/)
    .filter((p) => p.trim())
    .map((p) => `<p class="answer-paragraph">${p.trim()}</p>`)
    .join('')
}

function getAiKeywords(item: any): string[] {
  const raw = item?.aiKeywords ?? item?.keywords ?? item?.ai_keywords ?? ''
  if (Array.isArray(raw)) return raw.filter(Boolean)
  if (typeof raw === 'string' && raw.trim()) {
    return raw
      .split(/[,，;；、\s]+/)
      .map((s: string) => s.trim())
      .filter(Boolean)
  }
  return []
}

function getAiReason(item: any): string {
  return (
    item?.aiAnalyze ??
    item?.aiReason ??
    item?.ai_analyze ??
    item?.analyze ??
    item?.gradingRemark ??
    ''
  )
}

function ensureLatexDelimiters(text: string): string {
  if (!text || !text.includes('\\')) return text

  const mathRegions: string[] = []
  let processed = text.replace(/\$\$[\s\S]*?\$\$|\$[^$]+?\$/g, (match) => {
    mathRegions.push(match)
    return `\x00MATH${mathRegions.length - 1}\x00`
  })

  // Wrap undelimited \commands (with surrounding math context) in $...$
  processed = processed.replace(
    /((?:[0-9+\-*/=^_|(\[{. ]*\\[a-zA-Z]+(?:\[[^\]]*\])?(?:\{(?:[^{}]|\{[^{}]*\})*\})*)+[0-9+\-*/=^_|)\]}. ]*)/g,
    (match) => `$${match.trim()}$`,
  )

  processed = processed.replace(
    /\x00MATH(\d+)\x00/g,
    (_, idx) => mathRegions[Number(idx)]!,
  )

  return processed
}

function renderAiReasonMarkdown(item: any): string {
  const content = getAiReason(item)
  if (!content) return ''
  return aiReasonMarkdown.render(ensureLatexDelimiters(content))
}

const handleView = (row: any) => {
  taskMgrService.getPaperRecordDetail(row.recordId).then((res: any) => {
    const data = res?.data ?? res
    paperRecordDetail.value = data || {}
    paperRecordDetail.value.userName =
      paperRecordDetail.value.userName || row.userName || row.name
    studentDialogVisible.value = true
  })
}

const regradingRecordId = ref('')

async function handleRegrade(row: any) {
  if (!row.recordId) {
    ElMessage.warning('缺少学生答卷记录ID')
    return
  }
  try {
    await ElMessageBox.confirm('确定重新批改该学生试卷吗？', '提示', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning',
    })
  } catch {
    return
  }

  regradingRecordId.value = row.recordId
  try {
    await taskMgrService.regradePaper(row.recordId)
    ElMessage.success('已发起重新批改')
    emit('refresh')
  } catch {
    ElMessage.error('重新批改失败，请重试')
  } finally {
    regradingRecordId.value = ''
  }
}

const correctionDialogVisible = ref(false)
const correctionForm = ref({
  itemId: 0,
  correctResult: 1,
  actualScore: 0,
  teacherAnalyze: '',
  submitAnswerPic: '',
  maxScore: 0,
})
const correctionLoading = ref(false)
let currentCorrectionIndex = -1

function openCorrectionDialog(item: any, idx: number) {
  currentCorrectionIndex = idx
  const actual = typeof item.actualScore === 'number' ? item.actualScore : 0
  const max = Number(item.score ?? 0)
  let result = 1
  if (actual === 0) result = 0
  else if (actual >= max) result = 1

  correctionForm.value = {
    itemId: item.itemId ?? item.id ?? 0,
    correctResult: result,
    actualScore: actual,
    teacherAnalyze: item.teacherAnalyze ?? '',
    submitAnswerPic: item.submitAnswerPic ?? '',
    maxScore: max,
  }
  correctionDialogVisible.value = true
}

const correctionPicList = computed(() => {
  const raw = correctionForm.value.submitAnswerPic
  if (!raw) return []
  return String(raw).split(',').filter(Boolean)
})

const annotatorVisible = ref(false)
const annotatorSrc = ref('')
let annotatingPicIndex = -1
const annotatorUploading = ref(false)

function closeAnnotator() {
  annotatorVisible.value = false
  annotatorSrc.value = ''
  annotatingPicIndex = -1
}

function openAnnotator(pic: string, idx: number) {
  annotatorSrc.value = pic
  annotatingPicIndex = idx
  annotatorVisible.value = true
}

function onAnnotatorCancel() {
  closeAnnotator()
}

function dataURLtoFile(dataUrl: string, filename: string): File {
  const arr = dataUrl.split(',')
  const mime = arr[0]?.match(/:(.*?);/)?.[1] ?? 'image/png'
  const bstr = atob(arr[1] ?? '')
  let n = bstr.length
  const u8arr = new Uint8Array(n)
  while (n--) {
    u8arr[n] = bstr.codePointAt(n) ?? 0
  }
  return new File([u8arr], filename, { type: mime })
}

function getUploadedFilePath(res: any): string {
  if (typeof res?.data?.filePath === 'string') return res.data.filePath
  if (typeof res?.filePath === 'string') return res.filePath
  if (typeof res?.data?.url === 'string') return res.data.url
  if (typeof res?.url === 'string') return res.url
  if (typeof res?.path === 'string') return res.path
  if (typeof res?.data === 'string') return res.data
  if (typeof res === 'string') return res
  return ''
}

async function onAnnotatorSave(dataUrl: string) {
  annotatorUploading.value = true
  try {
    const file = dataURLtoFile(dataUrl, `annotated_${Date.now()}.png`)
    const fd = new FormData()
    fd.append('file', file)
    const res: any = await uploadImgToPath(fd)
    const url = getUploadedFilePath(res)
    if (!url || typeof url !== 'string') {
      ElMessage.error('图片上传失败')
      return
    }
    const pics = [...correctionPicList.value]
    if (annotatingPicIndex >= 0 && annotatingPicIndex < pics.length) {
      pics[annotatingPicIndex] = url
    } else {
      pics.push(url)
    }
    correctionForm.value.submitAnswerPic = pics.join(',')
    closeAnnotator()
    ElMessage.success('涂鸦已保存')
  } catch {
    ElMessage.error('上传失败，请重试')
  } finally {
    annotatorUploading.value = false
  }
}

function onCorrectionResultChange(val: any) {
  if (val === 1) {
    correctionForm.value.actualScore = correctionForm.value.maxScore
  } else if (val === 0) {
    correctionForm.value.actualScore = 0
  }
}

async function submitCorrection() {
  const { actualScore, maxScore } = correctionForm.value
  if (actualScore < 0 || actualScore > maxScore) {
    ElMessage.warning(`分数需在 0 ~ ${maxScore} 之间`)
    return
  }
  correctionLoading.value = true
  try {
    await taskMgrService.teacherGradeItem({
      itemId: correctionForm.value.itemId,
      correctResult: correctionForm.value.correctResult,
      actualScore: correctionForm.value.actualScore,
      teacherAnalyze: correctionForm.value.teacherAnalyze,
      submitAnswerPic: correctionForm.value.submitAnswerPic,
    })
    ElMessage.success('修正成功')
    if (
      currentCorrectionIndex >= 0 &&
      Array.isArray(paperRecordDetail.value?.titleVoList)
    ) {
      const target = paperRecordDetail.value.titleVoList[currentCorrectionIndex]
      if (target) {
        target.actualScore = correctionForm.value.actualScore
        target.correctResult = correctionForm.value.correctResult
        target.teacherAnalyze = correctionForm.value.teacherAnalyze
        target.submitAnswerPic = correctionForm.value.submitAnswerPic
      }
    }
    correctionDialogVisible.value = false
  } catch {
    ElMessage.error('修正失败，请重试')
  } finally {
    correctionLoading.value = false
  }
}
</script>

<template>
  <div class="student-wrapper">
    <div class="student">
      <div class="title">学生情况</div>
      <el-table :data="rows" border :row-class-name="getTableRowClassName">
        <el-table-column
          prop="index"
          label="序号"
          width="100"
          header-align="center"
          align="center"
        />
        <el-table-column
          prop="className"
          label="班级"
          header-align="center"
          align="center"
        />
        <el-table-column
          prop="userName"
          label="姓名"
          header-align="center"
          align="center"
        />
        <el-table-column
          prop="costTimeMs"
          label="答题时长"
          sortable
          header-align="center"
          align="center"
        >
          <template #default="{ row }">
            {{ row.costTime }}
          </template>
        </el-table-column>
        <el-table-column
          prop="scoreRate"
          label="得分/得分率"
          sortable
          header-align="center"
          align="center"
        >
          <template #default="{ row }">
            {{ row.score }}/{{ row.scoreRateText }}
          </template>
        </el-table-column>
        <el-table-column
          prop="gradingStatusText"
          label="状态"
          header-align="center"
          align="center"
        >
          <template #default="{ row }">
            <span
              class="student-status-text"
              :class="`student-status-text--${row.performanceLevel}`"
            >
              {{ row.gradingStatusText }}
            </span>
          </template>
        </el-table-column>
        <el-table-column
          label="操作"
          header-align="center"
          align="center"
          width="240"
        >
          <template #default="{ row }">
            <el-button
              link
              type="primary"
              :disabled="!row.canView"
              @click="handleView(row)"
            >
              <p style="color: #fff">查看</p>
            </el-button>
            <el-button
              v-if="row.canRegrade"
              type="warning"
              :loading="regradingRecordId === row.recordId"
              @click="handleRegrade(row)"
            >
              重新批改
            </el-button>
          </template>
        </el-table-column>
      </el-table>
    </div>

    <!-- 学生答卷详情弹框 -->
    <el-dialog
      v-model="studentDialogVisible"
      title="学生答卷详情"
      width="960px"
      align-center
      destroy-on-close
      class="student-detail-dialog"
    >
      <div class="paper-info">
        <div class="paper-title">
          试卷：{{ paperRecordDetail?.paperName || '-' }}
        </div>
        <div class="paper-meta">
          <span>作答人：{{ paperRecordDetail?.userName || '-' }}</span>
          <span>最终总分：{{ finalScore }}</span>
          <span>
            作答时长：{{ formatDuration(paperRecordDetail?.costTime) ?? '-' }}
          </span>
        </div>
        <div v-if="paperRecordDetail?.remark" class="paper-remark">
          备注：{{ paperRecordDetail.remark }}
        </div>
      </div>

      <div v-if="Array.isArray(paperRecordDetail?.titleVoList)">
        <div
          v-for="(item, idx) in paperRecordDetail.titleVoList"
          :key="item.itemId ?? item.id ?? idx"
          class="question-detail-block"
          :class="{ 'is-subjective': isSubjectiveItem(item) }"
        >
          <!-- 1. 题目 -->
          <PaperQuestionItem
            :index="item.sort != null ? Number(item.sort) : Number(idx) + 1"
            :question="{
              ...item,
              title: item.title,
              optionList: item.optionList,
              titleAnalyze: item.titleAnalyze,
              answer: item.answer,
            }"
            :preview="true"
          />

          <!-- === 客观题：保持原有简洁展示 === -->
          <template v-if="!isSubjectiveItem(item)">
            <div class="student-answer">
              <span class="label">学生答案：</span>
              <span class="content">{{ item.submitAnswer || '-' }}</span>
            </div>
            <div class="system-mark">
              <span class="label">系统批改结果：</span>
              <span class="content">
                <span :class="getCorrectClass(item.actualScore, item.score)">
                  {{ getCorrectLabel(item.actualScore, item.score) }}
                </span>
              </span>
            </div>
            <div class="final-score">
              <span class="label">最终得分：</span>
              <span class="content">
                {{
                  typeof item.actualScore === 'number' ? item.actualScore : '-'
                }}
              </span>
            </div>
          </template>

          <!-- === 主观题：AI 批改 + 老师复核布局 === -->
          <template v-else>
            <!-- 2. 学生完整回答（高亮关键词） -->
            <div class="subjective-answer-section">
              <div class="section-label">
                <span class="dot"></span>
                学生回答
              </div>
              <!-- eslint-disable-next-line vue/no-v-html -->
              <div
                class="answer-body"
                v-html="
                  highlightKeywords(
                    formatParagraphs(item.submitAnswer || '未作答'),
                    getAiKeywords(item),
                  )
                "
              ></div>
            </div>

            <!-- 答案图片 -->
            <div v-if="item.submitAnswerPic" class="student-answer-pics">
              <span class="label">答案图片：</span>
              <div class="pics">
                <el-image
                  v-for="(pic, pidx) in String(item.submitAnswerPic)
                    .split(',')
                    .filter(Boolean)"
                  :key="pidx"
                  :src="pic"
                  fit="contain"
                  style="width: 120px; height: 90px; margin-right: 8px"
                  :preview-src-list="
                    String(item.submitAnswerPic).split(',').filter(Boolean)
                  "
                />
              </div>
            </div>

            <!-- 3. AI 批改结论（卡片式突出显示） -->
            <div
              class="ai-grading-card"
              :class="[`ai-card--${getAiVerdict(item).type}`]"
            >
              <div class="ai-card-header">
                <span class="ai-card-icon">📝</span>
                <span class="ai-card-title">AI 批改结果</span>
              </div>
              <div class="ai-card-body">
                <div class="ai-card-row verdict-row">
                  <span class="ai-row-label">判定：</span>
                  <span
                    class="ai-verdict-badge"
                    :class="[`badge--${getAiVerdict(item).type}`]"
                  >
                    {{ getAiVerdict(item).icon }}
                    {{ getAiVerdict(item).label }}
                  </span>
                </div>
                <div class="ai-card-row score-row">
                  <span class="ai-row-label">得分：</span>
                  <span class="ai-score-display">
                    <span class="ai-actual-score">
                      {{
                        typeof item.actualScore === 'number'
                          ? item.actualScore
                          : '-'
                      }}
                    </span>
                    <span class="ai-score-sep">/</span>
                    <span class="ai-max-score">
                      {{ item.score ?? '-' }}
                    </span>
                  </span>
                </div>
                <div v-if="getAiReason(item)" class="ai-card-row reason-row">
                  <span class="ai-row-label">AI 判据：</span>
                  <!-- eslint-disable-next-line vue/no-v-html -->
                  <div
                    class="ai-reason-markdown"
                    v-html="renderAiReasonMarkdown(item)"
                  ></div>
                </div>
                <div
                  v-if="getAiKeywords(item).length"
                  class="ai-card-row keywords-row"
                >
                  <span class="ai-row-label">AI 识别关键词：</span>
                  <span class="ai-keywords">
                    <el-tag
                      v-for="kw in getAiKeywords(item)"
                      :key="kw"
                      size="small"
                      effect="plain"
                      class="kw-tag"
                    >
                      {{ kw }}
                    </el-tag>
                  </span>
                </div>
              </div>
            </div>

            <!-- 老师评语（如果已修正） -->
            <div v-if="item.teacherAnalyze" class="teacher-remark">
              <span class="teacher-remark-label">🧑‍🏫 老师评语：</span>
              <span class="teacher-remark-text">
                {{ item.teacherAnalyze }}
              </span>
            </div>

            <!-- 4. 修正按钮 -->
            <div class="correction-action">
              <el-button
                type="warning"
                plain
                size="large"
                class="correction-btn"
                @click="openCorrectionDialog(item, Number(idx))"
              >
                ✏️ 人工修正
              </el-button>
            </div>
          </template>

          <!-- 客观题图片 -->
          <div
            v-if="!isSubjectiveItem(item) && item.submitAnswerPic"
            class="student-answer-pics"
          >
            <span class="label">答案图片：</span>
            <div class="pics">
              <el-image
                v-for="(pic, pidx) in String(item.submitAnswerPic)
                  .split(',')
                  .filter(Boolean)"
                :key="pidx"
                :src="pic"
                fit="contain"
                style="width: 120px; height: 90px; margin-right: 8px"
                :preview-src-list="
                  String(item.submitAnswerPic).split(',').filter(Boolean)
                "
              />
            </div>
          </div>
        </div>
      </div>
      <div v-else>暂无答题详情</div>
    </el-dialog>

    <!-- 人工修正弹框 -->
    <el-dialog
      v-model="correctionDialogVisible"
      title="人工修正"
      :width="correctionPicList.length ? '780px' : '520px'"
      align-center
      destroy-on-close
      append-to-body
    >
      <el-form label-width="100px" label-position="top" class="correction-form">
        <el-form-item label="判定结果">
          <el-radio-group
            v-model="correctionForm.correctResult"
            size="large"
            @change="onCorrectionResultChange"
          >
            <el-radio-button :value="1">✅ 正确</el-radio-button>
            <el-radio-button :value="0">❌ 错误</el-radio-button>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="修改分数">
          <el-input-number
            v-model="correctionForm.actualScore"
            :min="0"
            :max="correctionForm.maxScore"
            :step="0.5"
            :precision="1"
            controls-position="right"
            style="width: 200px"
          />
          <span class="max-score-hint">
            满分 {{ correctionForm.maxScore }}
          </span>
        </el-form-item>

        <!-- 学生提交图片 · 涂鸦修改 -->
        <el-form-item
          v-if="correctionPicList.length"
          label="学生提交图片（点击可涂鸦批注）"
        >
          <div class="pic-edit-grid">
            <div
              v-for="(pic, pidx) in correctionPicList"
              :key="pidx"
              class="pic-edit-item"
              @click="openAnnotator(pic, pidx)"
            >
              <el-image :src="pic" fit="contain" class="pic-edit-thumb" />
              <div class="pic-edit-overlay">
                <span>✏️ 涂鸦批注</span>
              </div>
            </div>
          </div>
        </el-form-item>

        <el-form-item label="填写 / 修改评语">
          <el-input
            v-model="correctionForm.teacherAnalyze"
            type="textarea"
            :rows="3"
            placeholder="请输入评语（选填）"
            maxlength="500"
            show-word-limit
          />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="correctionDialogVisible = false">取 消</el-button>
        <el-button
          type="primary"
          :loading="correctionLoading"
          @click="submitCorrection"
        >
          保存修正
        </el-button>
      </template>
    </el-dialog>

    <!-- 图片涂鸦弹框 -->
    <el-dialog
      v-model="annotatorVisible"
      title="图片涂鸦批注"
      width="900px"
      align-center
      destroy-on-close
      append-to-body
      :close-on-click-modal="false"
      @closed="closeAnnotator"
    >
      <ImageAnnotator
        v-if="annotatorVisible && annotatorSrc"
        :src="annotatorSrc"
        @save="onAnnotatorSave"
        @cancel="onAnnotatorCancel"
      />
      <div v-if="annotatorUploading" class="annotator-uploading-mask">
        <span class="uploading-spinner"></span>
        <span>正在上传...</span>
      </div>
    </el-dialog>
  </div>
</template>

<style scoped>
@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

.title {
  width: 100%;
  margin-top: 24px;
  margin-bottom: 16px;
  font-size: 20px;
  font-weight: bold;
  text-align: center;
}

.student-status-text {
  font-weight: 600;
}

.student-status-text--unsubmitted,
.student-status-text--fail {
  color: #f56c6c;
}

.student-status-text--ungraded {
  color: #909399;
}

.student-status-text--excellent {
  color: #67c23a;
}

.student-status-text--good {
  color: #409eff;
}

.student-status-text--pass {
  color: #e6a23c;
}

.student-status-text--default {
  color: #606266;
}

:deep(.student-row--unsubmitted > td) {
  color: #f56c6c;
  background-color: #fef0f0 !important;
}

:deep(.student-row--ungraded > td) {
  background-color: #f4f4f5 !important;
}

:deep(.student-row--excellent > td) {
  background-color: #f0f9eb !important;
}

:deep(.student-row--good > td) {
  background-color: #ecf5ff !important;
}

:deep(.student-row--pass > td) {
  background-color: #fdf6ec !important;
}

:deep(.student-row--fail > td) {
  background-color: #fef0f0 !important;
}

/* ========== 弹框基础 ========== */
.paper-info {
  padding: 10px 12px;
  margin: 0 0 12px;
  background-color: #f8fafc;
  border: 1px solid #e6e8eb;
  border-radius: 10px;
}

.paper-title {
  margin-bottom: 6px;
  font-size: 18px;
  font-weight: 700;
  color: #1f2328;
}

.paper-meta {
  display: flex;
  gap: 20px;
  font-size: 16px;
  color: #344054;
}

.paper-remark {
  margin-top: 6px;
  font-size: 14px;
  color: #667085;
}

/* ========== 每道题的大块 ========== */
.question-detail-block {
  position: relative;
  padding: 16px 20px;
  margin: 16px 0 24px;
  overflow: hidden;
  background-color: #fff;
  border: 1px solid #e6e8eb;
  border-radius: 10px;
  box-shadow: 0 4px 12px rgb(31 35 40 / 6%);
}

.question-detail-block::before {
  position: absolute;
  top: 0;
  left: 0;
  width: 4px;
  height: 100%;
  content: '';
  background: #246cff;
}

.question-detail-block.is-subjective::before {
  background: linear-gradient(180deg, #246cff 0%, #7c3aed 100%);
}

/* ========== 客观题原始样式 ========== */
.student-answer {
  display: flex;
  gap: 8px;
  align-items: flex-start;
  padding: 12px 0 4px;
  font-size: 16px;
  line-height: 1.8;
  border-top: 1px dashed #dcdcdc;
}

.student-answer .label {
  min-width: 110px;
  font-weight: 600;
  color: #246cff;
}

.student-answer .content {
  flex: 1;
  color: #1f2328;
}

.system-mark,
.final-score {
  display: flex;
  gap: 8px;
  align-items: center;
  padding: 8px 0 0;
  font-size: 16px;
  line-height: 1.8;
}

.system-mark .label,
.final-score .label {
  min-width: 110px;
  font-weight: 600;
  color: #246cff;
}

.mark-success {
  font-size: 16px;
  font-weight: 700;
  color: #67c23a;
}

.mark-partial {
  font-size: 16px;
  font-weight: 700;
  color: #e6a23c;
}

.mark-error {
  font-size: 16px;
  font-weight: 700;
  color: #f56c6c;
}

.student-answer-pics {
  display: flex;
  gap: 8px;
  align-items: center;
  padding: 6px 0 0;
}

.student-answer-pics .label {
  color: #246cff;
}

.student-answer-pics .pics {
  display: flex;
  align-items: center;
}

/* ========== 主观题 · 学生回答区 ========== */
.subjective-answer-section {
  padding: 14px 0 6px;
  margin-top: 8px;
  border-top: 1px dashed #dcdcdc;
}

.section-label {
  display: flex;
  gap: 6px;
  align-items: center;
  margin-bottom: 8px;
  font-size: 15px;
  font-weight: 600;
  color: #344054;
}

.section-label .dot {
  display: inline-block;
  width: 6px;
  height: 6px;
  background-color: #246cff;
  border-radius: 50%;
}

.answer-body {
  padding: 12px 16px;
  font-size: 15px;
  line-height: 1.9;
  color: #1f2328;
  background-color: #fafbfc;
  border: 1px solid #eef0f2;
  border-radius: 8px;
}

.answer-body :deep(.answer-paragraph) {
  margin: 0 0 6px;
}

.answer-body :deep(.answer-paragraph:last-child) {
  margin-bottom: 0;
}

.answer-body :deep(.kw-highlight) {
  padding: 1px 3px;
  font-weight: 600;
  color: #92400e;
  background-color: #fef3c7;
  border-radius: 3px;
}

/* ========== AI 批改卡片 ========== */
.ai-grading-card {
  margin: 14px 0 10px;
  overflow: hidden;
  border: 2px solid #e6e8eb;
  border-radius: 12px;
  transition: border-color 0.2s;
}

.ai-card--success {
  border-color: #b7eb8f;
}

.ai-card--warning {
  border-color: #ffe58f;
}

.ai-card--danger {
  border-color: #ffa39e;
}

.ai-card--info {
  border-color: #d9d9d9;
}

.ai-card-header {
  display: flex;
  gap: 6px;
  align-items: center;
  padding: 10px 16px;
  font-size: 15px;
  font-weight: 700;
  color: #1f2328;
  background-color: #f6f8fa;
  border-bottom: 1px solid #eef0f2;
}

.ai-card--success .ai-card-header {
  background-color: #f6ffed;
}

.ai-card--warning .ai-card-header {
  background-color: #fffbe6;
}

.ai-card--danger .ai-card-header {
  background-color: #fff2f0;
}

.ai-card-icon {
  font-size: 18px;
}

.ai-card-body {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 14px 16px;
}

.ai-card-row {
  display: flex;
  gap: 8px;
  align-items: center;
  font-size: 15px;
}

.reason-row {
  align-items: flex-start;
}

.ai-row-label {
  min-width: 120px;
  font-weight: 600;
  color: #667085;
  white-space: nowrap;
}

.ai-verdict-badge {
  display: inline-flex;
  align-items: center;
  padding: 3px 14px;
  font-size: 16px;
  font-weight: 700;
  border-radius: 6px;
}

.badge--success {
  color: #389e0d;
  background-color: #f6ffed;
  border: 1px solid #b7eb8f;
}

.badge--warning {
  color: #d48806;
  background-color: #fffbe6;
  border: 1px solid #ffe58f;
}

.badge--danger {
  color: #cf1322;
  background-color: #fff2f0;
  border: 1px solid #ffa39e;
}

.badge--info {
  color: #8c8c8c;
  background-color: #fafafa;
  border: 1px solid #d9d9d9;
}

.ai-score-display {
  display: flex;
  gap: 2px;
  align-items: baseline;
  font-size: 22px;
  font-weight: 700;
}

.ai-actual-score {
  color: #246cff;
}

.ai-score-sep {
  color: #c0c4cc;
}

.ai-max-score {
  font-size: 16px;
  color: #909399;
}

.ai-reason-markdown {
  flex: 1;
  line-height: 1.8;
  color: #1f2328;
  overflow-wrap: anywhere;
}

.ai-reason-markdown :deep(p),
.ai-reason-markdown :deep(ul),
.ai-reason-markdown :deep(ol),
.ai-reason-markdown :deep(blockquote),
.ai-reason-markdown :deep(pre) {
  margin: 0 0 8px;
}

.ai-reason-markdown :deep(p:last-child),
.ai-reason-markdown :deep(ul:last-child),
.ai-reason-markdown :deep(ol:last-child),
.ai-reason-markdown :deep(blockquote:last-child),
.ai-reason-markdown :deep(pre:last-child) {
  margin-bottom: 0;
}

.ai-reason-markdown :deep(ul),
.ai-reason-markdown :deep(ol) {
  padding-left: 20px;
}

.ai-reason-markdown :deep(li + li) {
  margin-top: 4px;
}

.ai-reason-markdown :deep(blockquote) {
  padding: 8px 12px;
  color: #475467;
  background-color: #f8fafc;
  border-left: 4px solid #d0d5dd;
  border-radius: 6px;
}

.ai-reason-markdown :deep(code) {
  padding: 2px 6px;
  font-size: 13px;
  background-color: #f2f4f7;
  border-radius: 4px;
}

.ai-reason-markdown :deep(pre) {
  padding: 12px;
  overflow-x: auto;
  background-color: #101828;
  border-radius: 8px;
}

.ai-reason-markdown :deep(pre code) {
  padding: 0;
  color: #f9fafb;
  background-color: transparent;
}

.ai-reason-markdown :deep(a) {
  color: #246cff;
  text-decoration: none;
}

.ai-reason-markdown :deep(a:hover) {
  text-decoration: underline;
}

.ai-keywords {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.kw-tag {
  font-weight: 500;
  border-color: #d9d9d9;
}

/* ========== 老师评语 ========== */
.teacher-remark {
  display: flex;
  gap: 8px;
  align-items: flex-start;
  padding: 10px 14px;
  margin: 6px 0 10px;
  font-size: 14px;
  line-height: 1.7;
  background-color: #f0f9ff;
  border: 1px solid #bae0ff;
  border-radius: 8px;
}

.teacher-remark-label {
  font-weight: 600;
  color: #0958d9;
  white-space: nowrap;
}

.teacher-remark-text {
  color: #1f2328;
}

/* ========== 修正按钮 ========== */
.correction-action {
  display: flex;
  justify-content: flex-end;
  padding-top: 10px;
}

.correction-btn {
  min-width: 130px;
  font-size: 15px;
  font-weight: 600;
}

/* ========== 修正弹框 ========== */
.correction-form :deep(.el-form-item__label) {
  font-weight: 600;
  color: #1f2328;
}

.max-score-hint {
  margin-left: 12px;
  font-size: 14px;
  color: #909399;
}

/* ========== 图片涂鸦编辑 ========== */
.pic-edit-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

.pic-edit-item {
  position: relative;
  width: 160px;
  height: 120px;
  overflow: hidden;
  cursor: pointer;
  border: 2px solid #e6e8eb;
  border-radius: 8px;
  transition: all 0.2s;
}

.pic-edit-item:hover {
  border-color: #246cff;
  box-shadow: 0 2px 12px rgb(36 108 255 / 20%);
}

.pic-edit-item:hover .pic-edit-overlay {
  opacity: 1;
}

.pic-edit-thumb {
  width: 100%;
  height: 100%;
}

.pic-edit-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
  font-weight: 600;
  color: #fff;
  background-color: rgb(0 0 0 / 45%);
  opacity: 0;
  transition: opacity 0.2s;
}

.annotator-uploading-mask {
  position: absolute;
  inset: 0;
  z-index: 10;
  display: flex;
  flex-direction: column;
  gap: 10px;
  align-items: center;
  justify-content: center;
  font-size: 16px;
  color: #246cff;
  background-color: rgb(255 255 255 / 80%);
}

.uploading-spinner {
  display: inline-block;
  width: 28px;
  height: 28px;
  border: 3px solid #e6e8eb;
  border-top-color: #246cff;
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

/* ========== PaperQuestionItem 覆盖 ========== */
:deep(.paper-question-item .preview-toolbar) {
  display: none !important;
}

:deep(.paper-question-item) {
  padding: 6px 8px;
  margin-top: 10px;
  border: 1px dashed #e6e8eb;
  border-radius: 6px;
}
</style>
