<template>
  <div class="report-modal-overlay" @click.self="emit('close')">
    <div class="report-modal">
      <div class="report-modal__head">
        <span class="report-modal__name">{{ student.name }}</span>
        <button class="report-modal__close" @click="emit('close')">
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
            <line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" />
          </svg>
        </button>
      </div>

      <div class="report-modal__body">
        <div v-if="loading" class="report-modal__state">
          <span class="report-modal__spinner"></span>
          {{ lang.ssArticleLoadingReport }}
        </div>

        <div v-else-if="error" class="report-modal__state">
          <p class="report-modal__error">{{ error }}</p>
          <button class="report-modal__retry" @click="loadReport">{{ lang.ssArticleRetry }}</button>
        </div>

        <ArticleDetailedReport
          v-else-if="report"
          :report="report"
          :playing-id="player.playingId.value"
          :loading-id="player.loadingId.value"
          @play-demo="playDemo"
          @play-own="playOwn"
        />
      </div>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { lang } from '@/main'
import ArticleDetailedReport from '@/views/Editor/EnglishSpeaking/preview/ArticleDetailedReport.vue'
import { useAudioPlayer } from '@/views/Editor/EnglishSpeaking/composables/useAudioPlayer'
import { getArticleReport, getArticleDemoAudio } from '@/views/Editor/EnglishSpeaking/services/articleReading'
import { friendlyArticleError } from '@/views/Editor/EnglishSpeaking/services/articleErrors'
import type { ArticleReadingReport, ArticleClassSessionSummary } from '@/types/articleReading'

const props = defineProps<{ student: ArticleClassSessionSummary }>()
const emit = defineEmits<{ close: [] }>()

const report = ref<ArticleReadingReport | null>(null)
const loading = ref(false)
const error = ref<string | null>(null)
const player = useAudioPlayer()
const demoUrlCache = new Map<string, string>()

let loadToken = 0

async function loadReport() {
  const sessionId = props.student.sessionId
  if (!sessionId) return
  const token = ++loadToken
  loading.value = true
  error.value = null
  try {
    const next = await getArticleReport(sessionId)
    if (token !== loadToken) return
    report.value = next
  } catch (cause) {
    if (token !== loadToken) return
    // 老师看到的是学生真实结果，绝不用样例分数顶替。
    //
    // 文案必须过 friendlyArticleError（spec §9.2.5）：`request()` 现在抛的是后端那句
    // **裸英文 detail**（`Session not found` / `Config not found` …），它是给映射表当 key 用的
    // 机器可读值，不是给人看的。直接渲染 `cause.message` 就是把英文短句甩给老师 ——
    // §9.2.5 收敛全部 call site 时漏掉的最后一处。
    error.value = friendlyArticleError(cause)
  } finally {
    if (token === loadToken) loading.value = false
  }
}

async function playDemo(idx: number) {
  const id = `demo-${idx}`
  if (player.playingId.value === id) {
    player.stop()
    return
  }
  const sessionId = props.student.sessionId
  if (!sessionId) return
  const cacheKey = `${sessionId}:${idx}`
  let url = demoUrlCache.get(cacheKey)
  if (!url) {
    try {
      const res = await getArticleDemoAudio(sessionId, idx)
      url = res.url
      demoUrlCache.set(cacheKey, url)
    } catch (cause) {
      console.error('[article-reading] demo audio failed:', cause)
      return
    }
  }
  await player.play(id, { kind: 'url', url })
}

async function playOwn(idx: number) {
  const id = `own-${idx}`
  if (player.playingId.value === id) {
    player.stop()
    return
  }
  const url = report.value?.paragraphs.find(paragraph => paragraph.idx === idx)?.audioUrl
  if (!url) return
  await player.play(id, { kind: 'url', url })
}

onMounted(() => { void loadReport() })
onUnmounted(() => {
  loadToken += 1
  player.stop()
})
</script>

<style lang="scss" scoped>
.report-modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 2000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 32px;
  background: rgba(0, 0, 0, 0.35);
}

.report-modal {
  width: 100%;
  max-width: 760px;
  height: 100%;
  max-height: 80vh;
  display: flex;
  flex-direction: column;
  border-radius: 16px;
  background: #fff;
  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.25);
  overflow: hidden;
}

.report-modal__head {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 20px;
  border-bottom: 1px solid #f3f4f6;
}

.report-modal__name {
  font-size: 14px;
  font-weight: 600;
  color: #111827;
}

.report-modal__close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border: none;
  border-radius: 8px;
  background: #f3f4f6;
  color: #6b7280;
  cursor: pointer;
  transition: background 0.2s;

  &:hover { background: #e5e7eb; }
}

.report-modal__body {
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

.report-modal__state {
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  font-size: 13px;
  color: #9ca3af;
}

.report-modal__spinner {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  border: 3px solid #fb923c;
  border-top-color: transparent;
  animation: article-modal-spin 0.8s linear infinite;
}

@keyframes article-modal-spin {
  to { transform: rotate(360deg); }
}

.report-modal__error {
  margin: 0;
  color: #dc2626;
}

.report-modal__retry {
  padding: 4px 16px;
  border: 1px solid #fdba74;
  border-radius: 999px;
  background: #fff;
  color: #ea580c;
  font-size: 12px;
  cursor: pointer;
}
</style>
