<template>
  <div class="article-reading-view">
    <!-- 顶部操作栏 -->
    <div class="reading-topbar">
      <div class="reading-topbar__progress">
        {{ progressLabel }}
      </div>
      <div class="reading-topbar__right">
        <span class="timer-pill">
          <svg width="15" height="15" viewBox="0 0 24 24" fill="none" aria-hidden="true">
            <rect x="9.5" y="1.5" width="5" height="2.2" rx="1" fill="#6B7280" />
            <circle cx="12" cy="14" r="8.2" fill="#F3F4F6" stroke="#9CA3AF" stroke-width="1.8" />
            <path d="M12 14V9.4" stroke="#6B7280" stroke-width="1.8" stroke-linecap="round" />
            <path d="M12 14l3 1.8" stroke="#6B7280" stroke-width="1.8" stroke-linecap="round" />
          </svg>
          {{ timerLabel }}
        </span>
        <button
          class="icon-btn"
          :title="lang.ssArticleMore as string"
          :disabled="readOnly"
          @click="showExitConfirm = true"
        >
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
            <circle cx="5" cy="12" r="1" /><circle cx="12" cy="12" r="1" /><circle cx="19" cy="12" r="1" />
          </svg>
        </button>
      </div>
    </div>

    <!-- 段落列表：当前段全不透明，其余淡出 -->
    <div ref="listRef" class="reading-list">
      <div class="reading-list__inner">
        <div
          v-for="paragraph in paragraphs"
          :key="paragraph.idx"
          :ref="el => setCardRef(paragraph.idx, el)"
          class="paragraph-shell"
          :class="{ 'paragraph-shell--current': paragraph.idx === currentParagraphIdx }"
          @click="selectParagraph(paragraph.idx)"
        >
          <!-- 「已读」只由卡内那颗状态药丸说（已录 / 处理中 / 评估失败）。
               这里曾经还有一个绝对定位的打勾徽章，和绿色药丸说的是同一件事。 -->
          <ArticleParagraphCard
            :paragraph="paragraph"
            :expanded="isExpanded(paragraph)"
            :interactive="paragraph.idx === currentParagraphIdx"
            :demo-audio-loading="loadingId === `demo-${paragraph.idx}`"
            :demo-audio-playing="playingId === `demo-${paragraph.idx}`"
            :own-audio-loading="loadingId === `own-${paragraph.idx}`"
            :own-audio-playing="playingId === `own-${paragraph.idx}`"
            :own-audio-available="hasOwnAudio(paragraph)"
            @toggle="toggleExpanded"
            @play-demo="emit('play-demo', paragraph.idx)"
            @play-own="emit('play-own', paragraph.idx)"
          />
        </div>
      </div>
    </div>

    <!-- 错误横幅：麦克风 / 示范音频 / 轮询失败都靠它露出。
         到点 complete 那条重试动作已随 §4.4 移除 —— 判定权在后端，前端没有会失败的
         complete 需要重试；学生只需要看见发生了什么，然后再点一次原来那个按钮。 -->
    <div v-if="error" class="reading-error" role="alert">
      <span class="reading-error__text">{{ error }}</span>
      <button class="reading-error__dismiss" @click="emit('dismiss-error')">
        {{ lang.ssArticleDismiss }}
      </button>
    </div>

    <!-- 底部录音栏：idle / recording / processing / done 四态 -->
    <div class="reading-bottombar">
      <div class="bottombar-progress">
        <div class="bottombar-progress__track" :class="{ active: recordingState === 'recording' }">
          <div class="bottombar-progress__fill" :class="{ active: recordingState === 'recording' }"></div>
        </div>
      </div>

      <div class="bottombar-states">
        <!-- idle -->
        <div class="bottombar-state bottombar-state--idle" :class="{ visible: recordingState === 'idle' }">
          <button v-if="!isFirstParagraph" class="nav-btn" @click="goPrevious">
            <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
              <path d="M15 18l-6-6 6-6" />
            </svg>
            {{ lang.ssArticlePrevious }}
          </button>
          <div v-else class="nav-spacer"></div>

          <div class="bottombar-center">
            <button class="record-btn" :disabled="busy || readOnly" @click="emit('start-recording')">
              <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
                <path d="M12 14a3 3 0 003-3V5a3 3 0 00-6 0v6a3 3 0 003 3z" />
                <path d="M19 11a7 7 0 01-14 0H3a9 9 0 008 8.94V23h2v-3.06A9 9 0 0021 11h-2z" />
              </svg>
              {{ currentRecorded ? lang.ssArticleRerecordBtn : lang.ssArticleStartRecord }}
            </button>
            <button v-if="currentRecorded" class="replay-btn" :title="lang.ssArticleReplay as string" @click="emit('play-own', currentParagraphIdx)">
              <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
                <path d="M8 5v14l11-7z" />
              </svg>
              {{ lang.ssArticleReplay }}
            </button>
          </div>

          <button v-if="canViewReport && showReport" class="report-btn" @click="emit('view-report')">
            {{ lang.ssArticleViewReport }}
            <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
              <path d="M5 12h14M13 6l6 6-6 6" />
            </svg>
          </button>
          <button v-else-if="!isLastParagraph" class="nav-btn" @click="goNext">
            {{ lang.ssArticleNext }}
            <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
              <path d="M5 12h14M13 6l6 6-6 6" />
            </svg>
          </button>
          <div v-else class="nav-spacer"></div>
        </div>

        <!-- recording -->
        <div class="bottombar-state bottombar-state--recording" :class="{ visible: recordingState === 'recording' }">
          <div class="recording-pill">
            <button class="cancel-btn" :disabled="busy" @click="emit('cancel-recording')">
              <svg width="12" height="12" 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>
              {{ lang.ssArticleCancelRecord }}
            </button>
            <div class="recording-indicator">
              <span class="recording-dot"></span>
              <span class="recording-time">{{ formatTime(recordingDuration) }}</span>
            </div>
            <button class="finish-btn" :disabled="busy" @click="emit('finish-recording')">
              <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
                <path d="M20 6L9 17l-5-5" />
              </svg>
              {{ lang.ssArticleFinishRecord }}
            </button>
          </div>
        </div>

        <!-- processing。原型第四态「完成 ✓」不存在于此：ack 一到就打回 idle 并跳下一段
             （决策 A），没有可以停留的时刻，详见 ArticleRecordingState 的注释 -->
        <div class="bottombar-state bottombar-state--centered" :class="{ visible: recordingState === 'processing' }">
          <span class="state-spinner"></span>
          <span class="state-text">{{ lang.ssArticleProcessing }}</span>
        </div>
      </div>
    </div>

    <!-- 退出确认 -->
    <div v-if="showExitConfirm" class="exit-overlay" @click.self="showExitConfirm = false">
      <div class="exit-dialog">
        <div class="exit-dialog__head">
          <h3 class="exit-dialog__title">{{ lang.ssArticleExitTitle }}</h3>
          <button class="exit-dialog__close" @click="showExitConfirm = false">
            <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>
        <p class="exit-dialog__hint">{{ lang.ssArticleExitHint }}</p>
        <div class="exit-dialog__actions">
          <button class="exit-dialog__secondary" @click="showExitConfirm = false">
            {{ lang.ssArticleContinuePractice }}
          </button>
          <button class="exit-dialog__secondary" @click="handleRestart">
            {{ lang.ssArticleRestart }}
          </button>
          <button v-if="showReport" class="exit-dialog__primary" @click="confirmExit">
            {{ lang.ssArticleEndAndReport }}
          </button>
        </div>
      </div>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { ref, computed, watch, nextTick } from 'vue'
import { lang } from '@/main'
import ArticleParagraphCard from './ArticleParagraphCard.vue'
import type { ArticleParagraphResult } from '@/types/articleReading'
import type { ArticleRecordingState } from '../composables/useArticleReadingEngine'

const props = defineProps<{
  paragraphs: ArticleParagraphResult[]
  currentParagraphIdx: number
  recordingState: ArticleRecordingState
  recordingDuration: number
  elapsedPracticeSeconds: number | null
  canViewReport: boolean
  localAudioIdxs: number[]
  playingId: string | null
  loadingId: string | null
  showReport: boolean
  sessionEnded: boolean
  /** 总评已开始生成 → 后端已不接受重录，练习页转只读。 */
  overallStarted: boolean
  /** 已本地化的错误文案；null = 无错。 */
  error: string | null
}>()

// 具名元组式，不是调用签名式：事件多到这个数量后，调用签名的重载联合会把参数类型
// 推断成 `any[] | unknown[]`，父组件那边每个带参数的处理器都会报不可赋值。
const emit = defineEmits<{
  'select-paragraph': [idx: number]
  'start-recording': []
  'finish-recording': []
  'cancel-recording': []
  'play-demo': [idx: number]
  'play-own': [idx: number]
  'view-report': []
  'restart': []
  'dismiss-error': []
}>()

const showExitConfirm = ref(false)
/**
 * 展开态只跟着「当前段」走：选中的那一段只要评完了就默认摊开，学生不用再点一次箭头。
 * 这个 ref 是学生手动收起当前段的一次性覆盖，换段即失效 —— 非当前段的箭头本来就点不到
 * （interactive=false），所以不需要按 idx 记一整张表。
 */
const currentCollapsed = ref(false)
const listRef = ref<HTMLElement | null>(null)
const cardRefs = new Map<number, HTMLElement>()

function setCardRef(idx: number, el: unknown) {
  if (el instanceof HTMLElement) cardRefs.set(idx, el)
  else cardRefs.delete(idx)
}

// 录音/处理中禁止重复触发
const busy = computed(() => props.recordingState === 'processing')

// 两条路都把练习页转为只读：
//   ① 总评已经开始生成 —— 后端在这之后拒绝重录（409 `Report already generated`，2026-07-27）。
//      **两种模式都成立**：不锁的话那颗录音按钮按下去必然报错。
//   ② 关闭「展示学习报告」且这一轮已结束（spec §9.2.1）。只禁 ⋯ 而留录音会让学生
//      「能改成绩却不能重来」，老师端状态也在完成/进行中之间反复跳，所以一并禁用。
const readOnly = computed(() =>
  props.overallStarted || (!props.showReport && props.sessionEnded)
)

const isFirstParagraph = computed(() => props.currentParagraphIdx <= 0)
const isLastParagraph = computed(() => props.currentParagraphIdx >= props.paragraphs.length - 1)

const progressLabel = computed(() =>
  String(lang.ssArticleParagraphProgress)
    .replace('{cur}', String(props.currentParagraphIdx + 1))
    .replace('{total}', String(props.paragraphs.length)),
)

const timerLabel = computed(() =>
  props.elapsedPracticeSeconds === null
    ? (lang.ssArticleUnlimitedLabel as string)
    : formatTime(props.elapsedPracticeSeconds),
)

// 分钟不补零，还原 enspeak ArticleReadingView.tsx:276-280 的 `${mins}:${secs.padStart(2,'0')}`。
// 录音药丸共用本函数，原型同样共用，所以录音计时一并变成 0:07 —— 是还原，不是副作用。
function formatTime(seconds: number): string {
  const safe = Math.max(0, Math.floor(seconds))
  const mm = String(Math.floor(safe / 60))
  const ss = String(safe % 60).padStart(2, '0')
  return `${mm}:${ss}`
}

function isRecorded(paragraph: ArticleParagraphResult): boolean {
  return paragraph.status !== 'pending' || props.localAudioIdxs.includes(paragraph.idx)
}

function hasOwnAudio(paragraph: ArticleParagraphResult): boolean {
  return props.localAudioIdxs.includes(paragraph.idx) || Boolean(paragraph.audioUrl)
}

const currentRecorded = computed(() => {
  const paragraph = props.paragraphs.find(p => p.idx === props.currentParagraphIdx)
  return paragraph ? isRecorded(paragraph) : false
})

function selectParagraph(idx: number) {
  if (idx === props.currentParagraphIdx) return
  emit('select-paragraph', idx)
}

function isExpanded(paragraph: ArticleParagraphResult): boolean {
  return paragraph.idx === props.currentParagraphIdx && !currentCollapsed.value
}

function toggleExpanded() {
  currentCollapsed.value = !currentCollapsed.value
}

function goPrevious() {
  if (!isFirstParagraph.value) selectParagraph(props.currentParagraphIdx - 1)
}

function goNext() {
  if (!isLastParagraph.value) selectParagraph(props.currentParagraphIdx + 1)
}

function confirmExit() {
  showExitConfirm.value = false
  emit('view-report')
}

function handleRestart() {
  showExitConfirm.value = false
  emit('restart')
}

// 选中段自动滚到视野中央。收起状态一并归零 —— 换段不只有点击一条路，
// 录完一段后引擎也会自己往下推（advanceAfterAck），那条路不经过 selectParagraph。
watch(() => props.currentParagraphIdx, async idx => {
  currentCollapsed.value = false
  await nextTick()
  cardRefs.get(idx)?.scrollIntoView({ behavior: 'smooth', block: 'center' })
})
</script>

<style lang="scss" scoped>
.article-reading-view {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  background: #fff;
}

// ---- 顶部栏 ----
.reading-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 12px;
  border-bottom: 1px solid #f3f4f6;
  flex-shrink: 0;
}

.reading-topbar__progress {
  font-size: 11px;
  font-weight: 500;
  color: #6b7280;
}

.reading-topbar__right {
  display: flex;
  align-items: center;
  gap: 12px;
}

.timer-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: 999px;
  border: 1px solid #e5e7eb;
  background: #f9fafb;
  color: #4b5563;
  font-size: 13px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 6px;
  border: none;
  border-radius: 6px;
  background: transparent;
  color: #9ca3af;
  cursor: pointer;
  transition: background 0.2s, color 0.2s;

  &:hover {
    background: #f3f4f6;
    color: #4b5563;
  }

  &:disabled {
    opacity: 0.4;
    cursor: not-allowed;
  }
}

// ---- 段落列表 ----
.reading-list {
  flex: 1;
  overflow-y: auto;
  padding: 24px;
}

.reading-list__inner {
  max-width: 672px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.paragraph-shell {
  position: relative;
  padding: 16px;
  border: 2px solid #e5e7eb;
  border-radius: 8px;
  background: #fff;
  cursor: pointer;
  opacity: 0.4;
  transition: all 0.3s;

  &:hover { opacity: 0.6; border-color: #d1d5db; }

  &--current {
    border-color: #f97316;
    background: #fff7ed;
    box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
    transform: scale(1.02);
    opacity: 1;

    &:hover { opacity: 1; border-color: #f97316; }
  }
}

// ---- 错误横幅（spec §9.2.3）----
.reading-error {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 16px 8px;
  padding: 8px 12px;
  border: 1px solid #fecaca;
  border-radius: 8px;
  background: #fef2f2;
}

.reading-error__text {
  flex: 1;
  font-size: 12px;
  line-height: 1.5;
  color: #b91c1c;
}

.reading-error__action,
.reading-error__dismiss {
  flex-shrink: 0;
  padding: 4px 10px;
  border-radius: 6px;
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.2s;
}

.reading-error__action {
  border: 1px solid #fdba74;
  background: #fff;
  color: #ea580c;

  &:hover {
    background: #fff7ed;
  }
}

.reading-error__dismiss {
  border: none;
  background: transparent;
  color: #9ca3af;

  &:hover {
    background: #fee2e2;
  }
}

// ---- 底部录音栏 ----
.reading-bottombar {
  flex-shrink: 0;
  border-top: 1px solid #f3f4f6;
  background: #fff;
  padding: 10px 16px 12px;
}

.bottombar-progress {
  max-width: 384px;
  margin: 0 auto 8px;
}

.bottombar-progress__track {
  height: 1px;
  border-radius: 999px;
  overflow: hidden;
  background: transparent;

  &.active { background: #f3f4f6; }
}

.bottombar-progress__fill {
  height: 100%;
  width: 0;
  opacity: 0;
  border-radius: 999px;
  background: #fb923c;
  transition: width 1s linear, opacity 0.2s ease;

  &.active { width: 100%; opacity: 1; }
}

.bottombar-states {
  position: relative;
  max-width: 384px;
  height: 36px;
  margin: 0 auto;
}

.bottombar-state {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.18s ease-out;

  &.visible {
    opacity: 1;
    pointer-events: auto;
  }

  &--idle {
    justify-content: space-between;
    gap: 4px;
  }

  &--centered {
    justify-content: center;
    gap: 8px;
    pointer-events: none;
  }
}

.bottombar-center {
  display: flex;
  align-items: center;
  gap: 6px;
}

.nav-spacer {
  width: 60px;
  flex-shrink: 0;
}

.nav-btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 10px;
  border-radius: 999px;
  border: 1px solid #e5e7eb;
  background: #fff;
  color: #6b7280;
  font-size: 11px;
  font-weight: 500;
  cursor: pointer;
  flex-shrink: 0;
  transition: background 0.2s, border-color 0.2s, color 0.2s;

  &:hover {
    background: #f9fafb;
    border-color: #d1d5db;
    color: #4b5563;
  }
}

.report-btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 10px;
  border-radius: 999px;
  border: none;
  background: #f97316;
  color: #fff;
  font-size: 11px;
  font-weight: 500;
  cursor: pointer;
  flex-shrink: 0;
  transition: background 0.2s;

  &:hover { background: #ea580c; }
}

.record-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 24px;
  border-radius: 999px;
  border: none;
  background: #f97316;
  color: #fff;
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  box-shadow: 0 4px 6px -1px rgba(249, 115, 22, 0.25);
  transition: background 0.2s, transform 0.15s;

  &:hover:not(:disabled) { background: #ea580c; }
  &:active:not(:disabled) { transform: scale(0.95); }
  &:disabled { opacity: 0.6; cursor: not-allowed; }
}

.replay-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 14px;
  border-radius: 999px;
  border: 1px solid #fdba74;
  background: #fff;
  color: #ea580c;
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  flex-shrink: 0;
  transition: background 0.2s, transform 0.15s;

  &:hover { background: #fff7ed; }
  &:active { transform: scale(0.95); }
}

.recording-pill {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 6px 10px;
  border-radius: 999px;
  border: 1px solid #f3f4f6;
  background: #f9fafb;
}

.recording-indicator {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  flex: 1;
}

.recording-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #ef4444;
  animation: article-pulse 1.4s ease-in-out infinite;
}

@keyframes article-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}

.recording-time {
  font-size: 12px;
  font-weight: 600;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  color: #374151;
}

.cancel-btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 10px;
  border-radius: 999px;
  border: 1px solid #e5e7eb;
  background: #fff;
  color: #6b7280;
  font-size: 11px;
  font-weight: 500;
  cursor: pointer;
  flex-shrink: 0;
  transition: background 0.2s, border-color 0.2s, color 0.2s;

  &:hover:not(:disabled) {
    background: #fef2f2;
    border-color: #fecaca;
    color: #ef4444;
  }

  &:disabled { opacity: 0.6; cursor: not-allowed; }
}

.finish-btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 10px;
  border-radius: 999px;
  border: none;
  background: #f97316;
  color: #fff;
  font-size: 11px;
  font-weight: 500;
  cursor: pointer;
  flex-shrink: 0;
  transition: background 0.2s;

  &:hover:not(:disabled) { background: #ea580c; }
  &:disabled { opacity: 0.6; cursor: not-allowed; }
}

.state-spinner {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 2px solid #fb923c;
  border-top-color: transparent;
  animation: article-spin 0.7s linear infinite;
}

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

.state-text {
  font-size: 12px;
  color: #9ca3af;
}

// ---- 退出确认 ----
.exit-overlay {
  position: absolute;
  inset: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(0, 0, 0, 0.3);
  backdrop-filter: blur(2px);
}

.exit-dialog {
  width: 100%;
  max-width: 320px;
  padding: 24px;
  border-radius: 16px;
  background: #fff;
  box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.2);
}

.exit-dialog__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 4px;
}

.exit-dialog__title {
  margin: 0;
  font-size: 14px;
  font-weight: 600;
  color: #111827;
}

.exit-dialog__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; }
}

.exit-dialog__hint {
  margin: 0 0 16px;
  font-size: 12px;
  color: #9ca3af;
}

.exit-dialog__actions {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.exit-dialog__secondary {
  width: 100%;
  padding: 10px 0;
  border: 1px solid #e5e7eb;
  border-radius: 12px;
  background: transparent;
  color: #4b5563;
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.2s;

  &:hover { background: #f9fafb; }
}

.exit-dialog__primary {
  width: 100%;
  padding: 10px 0;
  border: none;
  border-radius: 12px;
  background: #f97316;
  color: #fff;
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.2s;

  &:hover { background: #ea580c; }
}
</style>
