<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
import { api } from '../api/client'
import { useUserStore } from '../stores/user'
import type {
  VoiceSessionDetail,
  VoiceSessionFinalizeResponse,
  VoiceSessionSummary,
  VoiceTurnAcceptedResponse,
  VoiceTurnSummary,
  VoiceTurnUploadAcceptedResponse,
} from '../types/voiceSession'
import BaseButton from '../components/ui/BaseButton.vue'
import BaseCard from '../components/ui/BaseCard.vue'
import BaseSelect from '../components/ui/BaseSelect.vue'
import BaseTextarea from '../components/ui/BaseTextarea.vue'
import LoadingSpinner from '../components/ui/LoadingSpinner.vue'
import EmptyState from '../components/ui/EmptyState.vue'
import {
  ArrowPathIcon,
  BookOpenIcon,
  ExclamationCircleIcon,
  MicrophoneIcon,
  PaperAirplaneIcon,
  SparklesIcon,
  StopIcon,
  XMarkIcon,
} from '@heroicons/vue/24/outline'

interface ChildProfile {
  id: string
  name: string
}

interface StoryUniverse {
  id: string
  name: string
}

const router = useRouter()
const userStore = useUserStore()

const sessions = ref<VoiceSessionSummary[]>([])
const activeSession = ref<VoiceSessionDetail | null>(null)
const profiles = ref<ChildProfile[]>([])
const universes = ref<StoryUniverse[]>([])
const selectedProfileId = ref('')
const selectedUniverseId = ref('')
const textTurnInput = ref('')
const uploadTranscriptHint = ref('')
const loadingSessions = ref(false)
const creatingSession = ref(false)
const loadingSessionDetail = ref(false)
const sendingTurn = ref(false)
const finalizing = ref(false)
const abandoning = ref(false)
const recording = ref(false)
const recordingDurationMs = ref(0)
const error = ref('')
const mediaError = ref('')
const recorderSupported = computed(() => typeof window !== 'undefined' && 'MediaRecorder' in window)

let mediaRecorder: MediaRecorder | null = null
let mediaStream: MediaStream | null = null
let recordingChunks: Blob[] = []
let recordingTimer: number | null = null
let recordingStartedAt = 0

const recordedBlob = ref<Blob | null>(null)
const recordedAudioUrl = ref<string | null>(null)

const profileOptions = computed(() =>
  profiles.value.map((profile) => ({ value: profile.id, label: profile.name })),
)
const universeOptions = computed(() =>
  universes.value.map((universe) => ({ value: universe.id, label: universe.name })),
)

const activeTurnList = computed(() => activeSession.value?.recent_turns ?? [])

function formatSessionStatus(status: string) {
  switch (status) {
    case 'draft':
      return '待开始'
    case 'processing_turn':
      return '处理中'
    case 'waiting_user':
      return '等待下一轮'
    case 'finalizing_story':
      return '保存中'
    case 'completed':
      return '已完成'
    case 'abandoned':
      return '已放弃'
    case 'failed':
      return '失败'
    default:
      return status
  }
}

function formatTurnStatus(status: string) {
  switch (status) {
    case 'received':
      return '已接收'
    case 'transcribing':
      return '转写中'
    case 'narrative_ready':
      return '文本已生成'
    case 'audio_ready':
      return '语音已生成'
    case 'failed':
      return '失败'
    default:
      return status
  }
}

function formatIntent(intent: string | null | undefined) {
  switch (intent) {
    case 'start_story':
      return '开启故事'
    case 'continue_story':
      return '继续讲述'
    case 'correct_story':
      return '修正走向'
    case 'end_story':
      return '结束本轮'
    case 'save_story':
      return '请求保存'
    default:
      return intent || '未知'
  }
}

function formatDate(dateStr: string) {
  return new Date(dateStr).toLocaleString('zh-CN', {
    month: 'short',
    day: 'numeric',
    hour: '2-digit',
    minute: '2-digit',
  })
}

function revokeRecordedAudioUrl() {
  if (recordedAudioUrl.value) {
    URL.revokeObjectURL(recordedAudioUrl.value)
    recordedAudioUrl.value = null
  }
}

function clearRecordedAudio() {
  revokeRecordedAudioUrl()
  recordedBlob.value = null
  recordingDurationMs.value = 0
}

async function fetchProfiles() {
  if (!userStore.user) return
  const data = await api.get<{ profiles: ChildProfile[] }>('/api/profiles')
  profiles.value = data.profiles
  if (!selectedProfileId.value && profiles.value.length > 0) {
    selectedProfileId.value = profiles.value[0].id
  }
}

async function fetchUniverses(profileId: string) {
  selectedUniverseId.value = ''
  if (!profileId) {
    universes.value = []
    return
  }
  const data = await api.get<{ universes: StoryUniverse[] }>(`/api/profiles/${profileId}/universes`)
  universes.value = data.universes
  if (universes.value.length > 0) {
    selectedUniverseId.value = universes.value[0].id
  }
}

async function loadSessions() {
  if (!userStore.user) return
  loadingSessions.value = true
  try {
    sessions.value = await api.get<VoiceSessionSummary[]>('/api/voice-sessions?limit=8')
    if (!activeSession.value) {
      const resumable = sessions.value.find((item) => item.can_continue)
      if (resumable) {
        await loadSessionDetail(resumable.id)
      }
    }
  } catch (err) {
    error.value = err instanceof Error ? err.message : '会话列表加载失败'
  } finally {
    loadingSessions.value = false
  }
}

async function loadSessionDetail(sessionId: string) {
  loadingSessionDetail.value = true
  error.value = ''
  try {
    activeSession.value = await api.get<VoiceSessionDetail>(`/api/voice-sessions/${sessionId}`)
  } catch (err) {
    error.value = err instanceof Error ? err.message : '会话详情加载失败'
  } finally {
    loadingSessionDetail.value = false
  }
}

async function createSession() {
  creatingSession.value = true
  error.value = ''
  try {
    const session = await api.post<VoiceSessionSummary>('/api/voice-sessions', {
      child_profile_id: selectedProfileId.value || null,
      universe_id: selectedUniverseId.value || null,
      target_mode: 'story',
    })
    await loadSessions()
    await loadSessionDetail(session.id)
    textTurnInput.value = ''
    uploadTranscriptHint.value = ''
    clearRecordedAudio()
  } catch (err) {
    error.value = err instanceof Error ? err.message : '创建语音共创会话失败'
  } finally {
    creatingSession.value = false
  }
}

async function pollTurnResult(sessionId: string, turnId: string) {
  const terminalStatuses = new Set(['audio_ready', 'narrative_ready', 'failed'])
  for (let attempt = 0; attempt < 20; attempt += 1) {
    const turn = await api.get<VoiceTurnSummary>(`/api/voice-sessions/${sessionId}/turns/${turnId}`)
    if (terminalStatuses.has(turn.status)) {
      return turn
    }
    await new Promise((resolve) => window.setTimeout(resolve, 600))
  }
  throw new Error('本轮语音共创处理超时，请刷新后查看最新状态')
}

async function refreshAfterTurn(sessionId: string, turnId: string) {
  await pollTurnResult(sessionId, turnId)
  await loadSessionDetail(sessionId)
  await loadSessions()
}

async function submitTextTurn() {
  if (!activeSession.value || !textTurnInput.value.trim()) return
  sendingTurn.value = true
  error.value = ''
  try {
    const result = await api.post<VoiceTurnAcceptedResponse>(
      `/api/voice-sessions/${activeSession.value.id}/turns/fallback`,
      {
        transcript_text: textTurnInput.value.trim(),
      },
    )
    textTurnInput.value = ''
    await refreshAfterTurn(result.session_id, result.turn_id)
  } catch (err) {
    error.value = err instanceof Error ? err.message : '文本共创回合提交失败'
  } finally {
    sendingTurn.value = false
  }
}

async function submitRecordedTurn() {
  if (!activeSession.value || !recordedBlob.value) return
  sendingTurn.value = true
  error.value = ''
  try {
    const formData = new FormData()
    formData.append('audio_file', recordedBlob.value, 'voice-turn.webm')
    if (recordingDurationMs.value > 0) {
      formData.append('duration_ms', String(recordingDurationMs.value))
    }
    if (uploadTranscriptHint.value.trim()) {
      formData.append('transcript_hint', uploadTranscriptHint.value.trim())
    }
    const result = await api.postForm<VoiceTurnUploadAcceptedResponse>(
      `/api/voice-sessions/${activeSession.value.id}/turns`,
      formData,
    )
    clearRecordedAudio()
    uploadTranscriptHint.value = ''
    await refreshAfterTurn(result.session_id, result.turn_id)
  } catch (err) {
    error.value = err instanceof Error ? err.message : '录音回合提交失败'
  } finally {
    sendingTurn.value = false
  }
}

async function finalizeSession() {
  if (!activeSession.value) return
  finalizing.value = true
  error.value = ''
  try {
    const result = await api.post<VoiceSessionFinalizeResponse>(
      `/api/voice-sessions/${activeSession.value.id}/finalize`,
      {
        save_story: true,
        generate_cover: true,
        generate_final_audio: false,
      },
    )
    await loadSessions()
    await loadSessionDetail(activeSession.value.id)
    if (result.story_id) {
      router.push(`/story/${result.story_id}`)
    }
  } catch (err) {
    error.value = err instanceof Error ? err.message : '保存语音共创故事失败'
  } finally {
    finalizing.value = false
  }
}

async function abandonSession() {
  if (!activeSession.value) return
  abandoning.value = true
  error.value = ''
  try {
    const summary = await api.post<VoiceSessionSummary>(
      `/api/voice-sessions/${activeSession.value.id}/abandon`,
      { reason: '用户在语音共创页主动结束会话' },
    )
    await loadSessions()
    activeSession.value = {
      ...(activeSession.value as VoiceSessionDetail),
      ...summary,
    }
  } catch (err) {
    error.value = err instanceof Error ? err.message : '放弃会话失败'
  } finally {
    abandoning.value = false
  }
}

async function startRecording() {
  mediaError.value = ''
  if (!recorderSupported.value) {
    mediaError.value = '当前浏览器不支持录音，请先使用文本共创模式。'
    return
  }
  try {
    clearRecordedAudio()
    mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true })
    mediaRecorder = new MediaRecorder(mediaStream)
    recordingChunks = []
    mediaRecorder.ondataavailable = (event) => {
      if (event.data.size > 0) {
        recordingChunks.push(event.data)
      }
    }
    mediaRecorder.onstop = () => {
      const blob = new Blob(recordingChunks, { type: mediaRecorder?.mimeType || 'audio/webm' })
      recordedBlob.value = blob
      revokeRecordedAudioUrl()
      recordedAudioUrl.value = URL.createObjectURL(blob)
    }
    mediaRecorder.start()
    recording.value = true
    recordingStartedAt = Date.now()
    recordingTimer = window.setInterval(() => {
      recordingDurationMs.value = Date.now() - recordingStartedAt
    }, 200)
  } catch (err) {
    mediaError.value = err instanceof Error ? err.message : '无法访问麦克风'
  }
}

function stopRecording() {
  if (!mediaRecorder || !recording.value) return
  mediaRecorder.stop()
  mediaRecorder = null
  recording.value = false
  if (recordingTimer) {
    window.clearInterval(recordingTimer)
    recordingTimer = null
  }
  if (mediaStream) {
    mediaStream.getTracks().forEach((track) => track.stop())
    mediaStream = null
  }
}

function resetRecording() {
  if (recording.value) {
    stopRecording()
  }
  clearRecordedAudio()
}

watch(selectedProfileId, (newId) => {
  if (newId) {
    void fetchUniverses(newId)
  } else {
    universes.value = []
    selectedUniverseId.value = ''
  }
})

onMounted(async () => {
  if (!userStore.user) {
    await userStore.fetchSession()
  }
  if (!userStore.user) return
  await fetchProfiles()
  await loadSessions()
})

onBeforeUnmount(() => {
  if (recording.value) {
    stopRecording()
  }
  clearRecordedAudio()
})
</script>

<template>
  <div class="max-w-7xl mx-auto space-y-8">
    <div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
      <div>
        <h1 class="text-3xl font-bold gradient-text">语音共创工作台</h1>
        <p class="mt-2 text-gray-500">
          第一阶段先跑通回合式共创：孩子说一句，系统接一句，再决定是否保存为正式故事。
        </p>
      </div>
      <div class="flex flex-wrap gap-3">
        <BaseButton variant="secondary" @click="loadSessions" :loading="loadingSessions">
          <ArrowPathIcon class="h-5 w-5" />
          刷新会话
        </BaseButton>
        <BaseButton @click="createSession" :loading="creatingSession">
          <SparklesIcon class="h-5 w-5" />
          开始新会话
        </BaseButton>
      </div>
    </div>

    <div v-if="!userStore.user" class="py-12">
      <EmptyState
        :icon="BookOpenIcon"
        title="需要先登录"
        description="登录后才能使用语音共创工作台。"
      />
    </div>

    <template v-else>
      <div class="grid grid-cols-1 gap-6 xl:grid-cols-[320px_minmax(0,1fr)]">
        <div class="space-y-6">
          <BaseCard>
            <div class="space-y-4">
              <div>
                <h2 class="text-lg font-semibold text-gray-900">创建条件</h2>
                <p class="mt-1 text-sm text-gray-500">
                  先选孩子档案与故事宇宙，让共创更容易复用现有角色和世界观。
                </p>
              </div>
              <BaseSelect
                v-model="selectedProfileId"
                label="孩子档案"
                :options="profileOptions"
                placeholder="请选择孩子档案"
              />
              <BaseSelect
                v-model="selectedUniverseId"
                label="故事宇宙"
                :options="universeOptions"
                placeholder="可选，默认不绑定宇宙"
              />
              <BaseButton class="w-full" @click="createSession" :loading="creatingSession">
                创建语音共创会话
              </BaseButton>
            </div>
          </BaseCard>

          <BaseCard>
            <div class="flex items-center justify-between">
              <div>
                <h2 class="text-lg font-semibold text-gray-900">最近会话</h2>
                <p class="mt-1 text-sm text-gray-500">支持恢复最近还在等待下一轮的 session。</p>
              </div>
              <span class="text-xs text-gray-400">{{ sessions.length }} 个</span>
            </div>

            <div v-if="loadingSessions" class="py-8">
              <LoadingSpinner text="加载会话中..." />
            </div>

            <div v-else-if="sessions.length === 0" class="pt-6">
              <EmptyState
                :icon="SparklesIcon"
                title="还没有语音共创会话"
                description="先创建一个会话，再通过文本或录音开始第一轮故事。"
              />
            </div>

            <div v-else class="mt-4 space-y-3">
              <button
                v-for="session in sessions"
                :key="session.id"
                type="button"
                class="w-full rounded-2xl border px-4 py-3 text-left transition-all"
                :class="activeSession?.id === session.id
                  ? 'border-purple-300 bg-purple-50'
                  : 'border-gray-100 bg-white hover:border-gray-300'"
                @click="loadSessionDetail(session.id)"
              >
                <div class="flex items-center justify-between gap-3">
                  <div class="min-w-0">
                    <div class="truncate font-medium text-gray-900">
                      {{ session.working_title || '未命名语音会话' }}
                    </div>
                    <div class="mt-1 text-xs text-gray-500">
                      {{ formatSessionStatus(session.status) }} · {{ session.total_turns }} 轮
                    </div>
                  </div>
                  <div class="text-right text-xs text-gray-400">
                    {{ formatDate(session.updated_at) }}
                  </div>
                </div>
              </button>
            </div>
          </BaseCard>
        </div>

        <div class="space-y-6">
          <BaseCard v-if="error" class="border border-rose-100 bg-rose-50 text-rose-600">
            <div class="flex items-start gap-3">
              <ExclamationCircleIcon class="mt-0.5 h-5 w-5 shrink-0" />
              <div>{{ error }}</div>
            </div>
          </BaseCard>

          <div v-if="loadingSessionDetail" class="py-16">
            <LoadingSpinner text="正在加载会话详情..." />
          </div>

          <BaseCard v-else-if="activeSession">
            <div class="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
              <div>
                <div class="flex flex-wrap items-center gap-2">
                  <h2 class="text-2xl font-semibold text-gray-900">
                    {{ activeSession.working_title || '语音共创会话' }}
                  </h2>
                  <span class="rounded-full bg-purple-100 px-3 py-1 text-xs font-medium text-purple-700">
                    {{ formatSessionStatus(activeSession.status) }}
                  </span>
                </div>
                <p class="mt-2 text-sm text-gray-500">
                  最近意图：{{ formatIntent(activeSession.latest_detected_intent) }} ·
                  已完成 {{ activeSession.total_turns }} 轮
                </p>
              </div>

              <div class="flex flex-wrap gap-3">
                <BaseButton
                  variant="secondary"
                  @click="finalizeSession"
                  :loading="finalizing"
                  :disabled="!activeSession.can_finalize"
                >
                  <BookOpenIcon class="h-5 w-5" />
                  保存为正式故事
                </BaseButton>
                <BaseButton
                  variant="ghost"
                  class="text-rose-500 hover:bg-rose-50"
                  @click="abandonSession"
                  :loading="abandoning"
                  :disabled="activeSession.status === 'completed' || activeSession.status === 'abandoned'"
                >
                  <XMarkIcon class="h-5 w-5" />
                  放弃会话
                </BaseButton>
              </div>
            </div>

            <div class="mt-6 grid grid-cols-1 gap-6 xl:grid-cols-[minmax(0,1.2fr)_minmax(0,0.8fr)]">
              <div class="space-y-6">
                <div class="rounded-2xl border border-gray-100 bg-white p-4">
                  <div class="flex items-center justify-between">
                    <h3 class="font-semibold text-gray-900">文本共创回合</h3>
                    <span class="text-xs text-gray-400">最稳的 fallback 路径</span>
                  </div>
                  <div class="mt-4 space-y-4">
                    <BaseTextarea
                      v-model="textTurnInput"
                      label="本轮你想让故事怎么发展"
                      placeholder="例如：不要让它害怕，我想让它遇见一个新朋友。"
                      :rows="4"
                      :max-length="1000"
                      :disabled="sendingTurn || !activeSession.can_continue"
                    />
                    <BaseButton
                      @click="submitTextTurn"
                      :loading="sendingTurn"
                      :disabled="!activeSession.can_continue || !textTurnInput.trim()"
                    >
                      <PaperAirplaneIcon class="h-5 w-5" />
                      发送文本回合
                    </BaseButton>
                  </div>
                </div>

                <div class="rounded-2xl border border-gray-100 bg-white p-4">
                  <div class="flex items-center justify-between">
                    <h3 class="font-semibold text-gray-900">录音共创回合</h3>
                    <span class="text-xs text-gray-400">已支持上传音频 turn</span>
                  </div>
                  <p class="mt-2 text-sm text-gray-500">
                    当前默认是 demo 转写模式。若本地未接真实 ASR，可在下方填写转写提示辅助开发验证。
                  </p>

                  <div class="mt-4 flex flex-wrap gap-3">
                    <BaseButton
                      v-if="!recording"
                      variant="secondary"
                      @click="startRecording"
                      :disabled="sendingTurn || !activeSession.can_continue"
                    >
                      <MicrophoneIcon class="h-5 w-5" />
                      开始录音
                    </BaseButton>
                    <BaseButton
                      v-else
                      variant="danger"
                      @click="stopRecording"
                    >
                      <StopIcon class="h-5 w-5" />
                      停止录音
                    </BaseButton>

                    <BaseButton
                      variant="ghost"
                      @click="resetRecording"
                      :disabled="(!recordedBlob && !recording) || sendingTurn"
                    >
                      <XMarkIcon class="h-5 w-5" />
                      清空录音
                    </BaseButton>
                  </div>

                  <p v-if="recording" class="mt-3 text-sm text-amber-600">
                    正在录音：{{ Math.round(recordingDurationMs / 1000) }}s
                  </p>
                  <p v-if="mediaError" class="mt-3 text-sm text-rose-600">
                    {{ mediaError }}
                  </p>

                  <div v-if="recordedAudioUrl" class="mt-4 rounded-2xl border border-gray-100 bg-gray-50 p-4">
                    <div class="text-sm font-medium text-gray-700">录音预览</div>
                    <audio class="mt-3 w-full" :src="recordedAudioUrl" controls></audio>
                  </div>

                  <div class="mt-4">
                    <BaseTextarea
                      v-model="uploadTranscriptHint"
                      label="开发转写提示（可选）"
                      placeholder="如果当前环境还是 demo 转写模式，可以把你刚才说的话写在这里。"
                      :rows="3"
                      :max-length="1000"
                    />
                  </div>

                  <div class="mt-4">
                    <BaseButton
                      @click="submitRecordedTurn"
                      :loading="sendingTurn"
                      :disabled="!activeSession.can_continue || !recordedBlob"
                    >
                      <SparklesIcon class="h-5 w-5" />
                      上传录音回合
                    </BaseButton>
                  </div>
                </div>

                <div class="rounded-2xl border border-gray-100 bg-white p-4">
                  <div class="flex items-center justify-between">
                    <h3 class="font-semibold text-gray-900">共创过程</h3>
                    <span class="text-xs text-gray-400">{{ activeTurnList.length }} 条最近 turn</span>
                  </div>

                  <div class="mt-4 space-y-4">
                    <div
                      v-for="turn in activeTurnList"
                      :key="turn.id"
                      class="rounded-2xl border border-gray-100 bg-gray-50 p-4"
                    >
                      <div class="flex flex-wrap items-center gap-2 text-xs text-gray-400">
                        <span>第 {{ turn.turn_index }} 轮</span>
                        <span>·</span>
                        <span>{{ formatTurnStatus(turn.status) }}</span>
                        <span>·</span>
                        <span>{{ formatIntent(turn.detected_intent) }}</span>
                        <span v-if="turn.transcription_provider">· {{ turn.transcription_provider }}</span>
                      </div>
                      <div class="mt-3 text-sm text-gray-800">
                        <span class="font-medium text-gray-900">孩子：</span>
                        {{ turn.user_transcript || '暂无转写内容' }}
                      </div>
                      <div v-if="turn.user_audio_url" class="mt-3">
                        <audio class="w-full" :src="turn.user_audio_url" controls></audio>
                      </div>
                      <div v-if="turn.assistant_text" class="mt-4 text-sm text-gray-700">
                        <span class="font-medium text-purple-700">织机回应：</span>
                        {{ turn.assistant_text }}
                      </div>
                      <div v-if="turn.assistant_audio_url" class="mt-3">
                        <audio class="w-full" :src="turn.assistant_audio_url" controls></audio>
                      </div>
                      <div v-if="turn.error_message" class="mt-3 text-sm text-rose-600">
                        {{ turn.error_message }}
                      </div>
                    </div>

                    <EmptyState
                      v-if="activeTurnList.length === 0"
                      :icon="MicrophoneIcon"
                      title="从第一轮开始"
                      description="先发送一句文本或录音，让这个会话拥有第一段故事。"
                    />
                  </div>
                </div>
              </div>

              <div class="space-y-6">
                <div class="rounded-2xl border border-gray-100 bg-white p-4">
                  <h3 class="font-semibold text-gray-900">故事状态快照</h3>
                  <pre class="mt-4 overflow-x-auto rounded-xl bg-gray-950 p-4 text-xs leading-6 text-emerald-200">{{ JSON.stringify(activeSession.story_state, null, 2) }}</pre>
                </div>

                <div class="rounded-2xl border border-gray-100 bg-white p-4">
                  <h3 class="font-semibold text-gray-900">最近事件</h3>
                  <div class="mt-4 space-y-3">
                    <div
                      v-for="event in activeSession.events.slice(-10)"
                      :key="event.id"
                      class="rounded-xl border border-gray-100 bg-gray-50 px-3 py-3"
                    >
                      <div class="flex items-center justify-between gap-3 text-xs text-gray-400">
                        <span>{{ event.event_type }}</span>
                        <span>{{ formatDate(event.created_at) }}</span>
                      </div>
                      <div class="mt-1 text-sm font-medium text-gray-800">
                        {{ event.message || event.status }}
                      </div>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </BaseCard>

          <div v-else class="py-16">
            <EmptyState
              :icon="SparklesIcon"
              title="创建或恢复一个语音共创会话"
              description="左侧可以直接创建新会话，也可以恢复最近仍在等待下一轮的 session。"
            />
          </div>
        </div>
      </div>
    </template>
  </div>
</template>
