<template>
  <div class="kling-page">
    <div class="page-header">
      <h1>可灵数字人 · 人脸识别与生成</h1>
      <p class="page-desc">上传视频，识别其中的人脸，并生成数字人视频</p>
    </div>

    <div class="kling-content">
      <!-- 左侧配置 -->
      <div class="upload-panel">
        <!-- 文案输入 -->
        <div class="section">
          <h3>文案</h3>
          <a-textarea
            v-model:value="ttsText"
            placeholder="请输入你想让角色说话的内容"
            :rows="4"
            class="tts-textarea"
          />
        </div>

        <!-- 音色选择 -->
        <div class="section">
          <h3>音色</h3>

          <div v-if="displayedVoices.length === 0" class="empty-voices">
            还没有配音，可先在"配音管理"中上传
          </div>

          <div class="voice-list">
            <div
              v-for="voice in displayedVoices"
              :key="voice.id"
              class="voice-list-item"
              :class="{ selected: selectedTtsVoice === `${voice.source}-${voice.id}` }"
              @click="selectVoiceProfile(voice)"
            >
              <div class="voice-item-info">
                <div class="voice-avatar">{{ voice.gender === 'male' ? '👨' : '👩' }}</div>
                <div class="voice-item-text">
                  <div class="voice-name">{{ voice.name }}</div>
                  <div class="voice-desc">{{ voice.description }}</div>
                </div>
              </div>
              <div class="voice-item-meta">
                <a-button
                  size="small"
                  type="link"
                  class="preview-btn"
                  :disabled="previewLoadingVoiceId === voice.id"
                  @click.stop="playVoiceSample(voice)"
                >
                  <template #icon>
                    <SoundOutlined
                      v-if="!previewLoadingVoiceId && !playingPreviewVoiceId"
                      class="preview-icon"
                    />
                    <LoadingOutlined
                      v-else-if="previewLoadingVoiceId === voice.id"
                      class="preview-icon spinning"
                    />
                    <SoundOutlined
                      v-else-if="playingPreviewVoiceId === voice.id"
                      class="preview-icon playing"
                    />
                    <SoundOutlined
                      v-else
                      class="preview-icon idle"
                    />
                  </template>
                </a-button>
              </div>
            </div>
          </div>
        </div>

        <!-- TTS 控制 -->
        <div class="section">
          <h3>语音控制</h3>
          <div class="control-group">
            <div class="control-label">语速</div>
            <div class="slider-card">
              <div class="slider-info">
                <div class="slider-value">{{ speechRateDisplay }}</div>
                <button class="reset-btn" @click="speechRate = 1">重置</button>
              </div>
              <a-slider
                v-model:value="speechRate"
                :min="0.5"
                :max="2"
                :step="0.1"
                :marks="speechRateMarks"
                :tooltip-open="false"
              />
            </div>
          </div>
        </div>

        <!-- 视频上传 -->
        <div class="section">
          <h3>视频</h3>
          <div class="upload-zone" @drop.prevent="handleDrop" @dragover.prevent="dragOver = true" @dragleave.prevent="dragOver = false">
            <input ref="fileInput" type="file" accept=".mp4,.mov" style="display: none" @change="handleFileSelect" />

            <div v-if="!uploadedVideo" class="upload-placeholder">
              <h3>上传视频文件</h3>
              <p>支持 MP4、MOV</p>
              <a-button type="primary" size="large" @click="triggerFileSelect">
                选择文件
              </a-button>
            </div>

            <div v-else class="video-preview">
              <video :src="uploadedVideo" controls class="preview-video"></video>
              <p>{{ videoFile?.name }}</p>
            </div>
          </div>
        </div>

        <!-- 任务状态显示 -->
        <div v-if="currentTaskId && (isGenerating || currentTaskStatus)" class="task-status-section">
          <h3>任务状态</h3>
          <div class="status-card">
            <div class="status-row">
              <span class="status-label">状态：</span>
              <span class="status-value">{{ getStatusText(currentTaskStatus) }}</span>
            </div>
            <div v-if="currentTaskStep" class="status-row">
              <span class="status-label">步骤：</span>
              <span class="status-value">{{ getStepText(currentTaskStep) }}</span>
            </div>
            <div v-if="isGenerating" class="progress-row">
              <a-progress :percent="generationProgress" :show-info="true" :status="currentTaskStatus === 'FAILED' ? 'exception' : 'active'" />
            </div>
            <div v-if="currentTaskStatus === 'FAILED' && currentTaskError" class="error-row">
              <span class="error-label">错误：</span>
              <span class="error-value">{{ currentTaskError }}</span>
            </div>
          </div>
        </div>

        <!-- 按钮组 -->
        <div class="action-buttons">
          <a-button
            type="primary"
            size="large"
            :disabled="!canIdentify || identifying || identified"
            :loading="identifying"
            block
            @click="handleIdentify"
          >
            {{ identifying ? '识别中...' : (identified ? '已识别' : '人脸识别') }}
          </a-button>

          <a-button
            type="default"
            size="large"
            :disabled="!canGenerate || isGenerating"
            :loading="isGenerating"
            block
            @click="handleGenerate"
          >
            {{ isGenerating ? '生成中...' : '生成数字人视频' }}
          </a-button>
        </div>
      </div>

      <!-- 右侧结果 -->
      <div class="result-panel">
        <div v-if="!previewVideoUrl" class="result-placeholder">
          <h3>生成的视频将在这里显示</h3>
        </div>

        <div v-else class="result-content">
          <!-- 生成的视频 -->
          <div class="result-section">
            <h3>生成的数字人视频</h3>
            <video :src="previewVideoUrl" controls class="generated-video"></video>
            <div class="video-actions">
              <a-button type="primary" @click="downloadVideo">下载视频</a-button>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed, onMounted } from 'vue'
import { message } from 'ant-design-vue'
import { SoundOutlined, LoadingOutlined } from '@ant-design/icons-vue'
import { createKlingTaskAndIdentify ,createLipSyncTask , getLipSyncTask} from '@/api/kling'
import { MaterialService } from '@/api/material'
import { VoiceService } from '@/api/voice'
import { useVoiceCopyStore } from '@/stores/voiceCopy'

const voiceStore = useVoiceCopyStore()

// 状态管理
const uploadedVideo = ref('')
const videoFile = ref(null)
const identifying = ref(false)
const identified = ref(false) // 是否已识别
const previewVideoUrl = ref('')
const isGenerating = ref(false)
const currentTaskId = ref(null)
const currentTaskStatus = ref('')
const currentTaskStep = ref('')
const generationProgress = ref(0)
const currentTaskError = ref('') // 任务错误信息

// 识别结果存储
const identifySessionId = ref('') // 人脸识别会话ID
const identifyFaceId = ref('') // 选中的人脸ID
const identifyVideoFileId = ref(null) // 视频文件ID

// 音频试听缓存
const previewAudioCache = new Map()
const MAX_PREVIEW_CACHE_SIZE = 50

// TTS 配置
const ttsText = ref('')
const selectedTtsVoice = ref('')
const speechRate = ref(1.0)

// 试听状态
const previewLoadingVoiceId = ref('')
const playingPreviewVoiceId = ref('')

// 用户音色列表
const userVoiceCards = computed(() =>
  (voiceStore.profiles || []).map(profile => ({
    id: `user-${profile.id}`,
    rawId: profile.id,
    name: profile.name || '未命名',
    category: profile.gender === 'male' ? '男青年' : '女青',
    gender: profile.gender || 'female',
    description: profile.note || '我的配音',
    fileUrl: profile.fileUrl,
    transcription: profile.transcription || '',
    source: 'user',
    voiceId: profile.voiceId
  }))
)

const displayedVoices = computed(() => userVoiceCards.value)

const selectedVoiceMeta = computed(() =>
  displayedVoices.value.find(voice => `user-${voice.id}` === selectedTtsVoice.value)
)

// 生成条件
const canIdentify = computed(() => {
  return !!uploadedVideo.value
})

const canGenerate = computed(() => {
  const hasText = ttsText.value.trim()
  const hasVoice = selectedVoiceMeta.value
  const hasVideo = uploadedVideo.value
  const notGenerating = !isGenerating.value
  return !!(hasText && hasVoice && hasVideo && notGenerating)
})

// UI 控制
const speechRateMarks = { 0.5: '0.5x', 1: '1x', 1.5: '1.5x', 2: '2x' }
const speechRateDisplay = computed(() => `${speechRate.value.toFixed(1)}x`)

// 音色选择
const selectVoiceProfile = (voice) => {
  selectedTtsVoice.value = `user-${voice.id}`
}

// 音频试听
const playVoiceSample = async (voice) => {
  if (!voice) return
  if (previewLoadingVoiceId.value === voice.id || playingPreviewVoiceId.value === voice.id) {
    return
  }
  if (playingPreviewVoiceId.value && playingPreviewVoiceId.value !== voice.id) {
    try {
      previewAudio?.pause?.()
      previewAudio = null
    } catch {}
  }

  previewLoadingVoiceId.value = voice.id
  playingPreviewVoiceId.value = voice.id

  const cacheKey = generatePreviewCacheKey(voice)
  const cachedAudio = previewAudioCache.get(cacheKey)

  if (cachedAudio) {
    console.log('使用缓存音频:', voice.name)
    playCachedAudio(cachedAudio, resetPreviewState)
    return
  }

  try {
    const params = buildPreviewParams(voice)
    if (!params) return resetPreviewState()

    console.log(`试听音色: ${voice.name}, voiceId: ${voice.voiceId || voice.rawId}`)

    const res = await VoiceService.preview(params)
    if (res.code !== 0) {
      message.error(res.msg || '试听失败')
      return resetPreviewState()
    }

    if (res.data?.audioUrl) {
      playAudioPreview(res.data.audioUrl, { onEnded: resetPreviewState })
    } else if (res.data?.audioBase64) {
      const audioData = await decodeAndCacheBase64(res.data.audioBase64, res.data.format, cacheKey)
      playCachedAudio(audioData, resetPreviewState)
    } else {
      message.error('试听失败')
      resetPreviewState()
    }
  } catch (error) {
    console.error('preview error:', error)
    message.error('试听失败')
    resetPreviewState()
  }
}

const generatePreviewCacheKey = (voice) => {
  const voiceId = voice.voiceId || voice.rawId || voice.id
  const text = ttsText.value.trim()
  const rate = speechRate.value
  return `${voiceId}:${text}:${rate}`
}

const decodeAndCacheBase64 = async (audioBase64, format = 'mp3', cacheKey) => {
  const byteCharacters = window.atob(audioBase64)
  const byteNumbers = new Array(byteCharacters.length)
  for (let i = 0; i < byteCharacters.length; i++) {
    byteNumbers[i] = byteCharacters.charCodeAt(i)
  }
  const mime = format === 'mp3' ? 'audio/mpeg' : `audio/${format}`
  const blob = new Blob([new Uint8Array(byteNumbers)], { type: mime })
  const objectUrl = URL.createObjectURL(blob)

  const audioData = { blob, objectUrl, format }

  previewAudioCache.set(cacheKey, audioData)

  if (previewAudioCache.size > MAX_PREVIEW_CACHE_SIZE) {
    const firstKey = previewAudioCache.keys().next().value
    const oldData = previewAudioCache.get(firstKey)
    URL.revokeObjectURL(oldData.objectUrl)
    previewAudioCache.delete(firstKey)
  }

  return audioData
}

const playCachedAudio = (audioData, onEnded) => {
  if (previewObjectUrl && previewObjectUrl !== audioData.objectUrl) {
    URL.revokeObjectURL(previewObjectUrl)
  }
  previewObjectUrl = audioData.objectUrl

  playAudioPreview(previewObjectUrl, {
    revokeOnEnd: false,
    onEnded: () => {
      onEnded && onEnded()
    }
  })
}

const resetPreviewState = () => {
  previewLoadingVoiceId.value = ''
  playingPreviewVoiceId.value = ''
}

const buildPreviewParams = (voice) => {
  const configId = voice.rawId || extractIdFromString(voice.id)
  if (!configId) {
    message.error('配音配置无效')
    return null
  }
  return {
    voiceConfigId: configId,
    inputText: ttsText.value,
    speechRate: speechRate.value || 1.0,
    audioFormat: 'mp3',
    timestamp: Date.now()
  }
}

const extractIdFromString = (idStr) => {
  if (typeof idStr !== 'string' || !idStr.startsWith('user-')) return null
  const extractedId = parseInt(idStr.replace('user-', ''))
  return Number.isNaN(extractedId) ? null : extractedId
}

// 视频上传
const triggerFileSelect = () => document.querySelector('input[type="file"]').click()

const handleFileSelect = (event) => {
  const file = event.target.files[0]
  if (file) handleFile(file)
}

const handleDrop = (event) => {
  const file = event.dataTransfer.files[0]
  if (file) handleFile(file)
}

const handleFile = (file) => {
  if (!file.name.match(/\.(mp4|mov)$/i)) {
    message.error('仅支持 MP4 和 MOV')
    return
  }
  videoFile.value = file
  uploadedVideo.value = URL.createObjectURL(file)
  identified.value = false // 重置识别状态
  previewVideoUrl.value = ''
  // 重置识别结果
  identifySessionId.value = ''
  identifyFaceId.value = ''
  identifyVideoFileId.value = null
}

// 人脸识别
const handleIdentify = async () => {
  if (!videoFile.value) return

  identifying.value = true
  try {
    const res = await createKlingTaskAndIdentify(videoFile.value)
    // 保存识别结果
    identifySessionId.value = res.data.sessionId
    identifyVideoFileId.value = res.data.fileId
    identifyVideoFileId.value = res.data.faceId
    identified.value = true
    message.success('识别完成！')
    console.log( '识别结果:', res.data)
    // 识别成功后，延迟1.5秒自动生成数字人视频
    await handleGenerate()
  } catch (error) {
    message.error(error.message || '识别失败')
  } finally {
    identifying.value = false
  }
}

// 生成数字人视频
const handleGenerate = async () => {
  if (!canGenerate.value) {
    message.warning('请先完成配置')
    return
  }

  const voice = selectedVoiceMeta.value
  if (!voice) {
    message.warning('请选择音色')
    return
  }

  isGenerating.value = true
  try {
    // 如果已经识别过，使用识别时的视频文件ID和识别结果
    let videoFileId = identifyVideoFileId.value
   
    // 2. 创建任务
    const taskData = {
      taskName: `可灵数字人任务_${Date.now()}`,
      videoFileId: videoFileId,
      inputText: ttsText.value,
      speechRate: speechRate.value,
      volume: 0,
      guidanceScale: 1,
      seed: 8888,
      klingSessionId: identifySessionId.value,
      klingFaceId: identifyFaceId.value,
      aiProvider: 'kling'
    }

    const configId = voice.rawId || extractIdFromString(voice.id)
    if (!configId) {
      message.warning('音色配置无效')
      return
    }
    taskData.voiceConfigId = configId

    message.loading('正在创建任务...', 0)
    const res = await createLipSyncTask(taskData)
    message.destroy()

    if (res.code === 0) {
      currentTaskId.value = res.data
      message.success('任务创建成功，开始处理')
      // 3. 轮询任务状态
      pollTaskStatus()
    } else {
      throw new Error(res.msg || '任务创建失败')
    }
  } catch (error) {
    console.error('generate error:', error)
    message.destroy()
    message.error(error.message || '视频生成失败')
    isGenerating.value = false
  }
}



// 轮询任务状态
const pollTaskStatus = async () => {
  const interval = setInterval(async () => {
    if (!currentTaskId.value) {
      clearInterval(interval)
      return
    }

    try {
      const res = await getDigitalHumanTask(currentTaskId.value)
      if (res.code === 0 && res.data) {
        const task = res.data
        currentTaskStatus.value = task.status
        currentTaskStep.value = task.currentStep
        generationProgress.value = task.progress || 0

        if (task.status === 'SUCCESS') {
          clearInterval(interval)
          previewVideoUrl.value = task.resultVideoUrl
          isGenerating.value = false
          currentTaskStatus.value = 'SUCCESS'
          currentTaskError.value = ''
          // 保存成功的任务ID
          localStorage.setItem('digital_human_last_task_id', currentTaskId.value)
          message.success('视频生成成功！')
        } else if (task.status === 'FAILED') {
          clearInterval(interval)
          isGenerating.value = false
          currentTaskStatus.value = 'FAILED'
          currentTaskError.value = task.errorMessage || '未知错误'
          // 失败时清除缓存
          localStorage.removeItem('digital_human_last_task_id')
          message.error(`任务失败：${currentTaskError.value}`)
        } else if (task.status === 'CANCELED') {
          clearInterval(interval)
          isGenerating.value = false
          currentTaskStatus.value = 'CANCELED'
          currentTaskError.value = ''
          // 取消时也清除缓存
          localStorage.removeItem('digital_human_last_task_id')
          message.info('任务已取消')
        } else if (task.status === 'PROCESSING') {
          isGenerating.value = true
          currentTaskError.value = ''
        }
      }
    } catch (error) {
      console.error('polling error:', error)
    }
  }, 10000)
}

// 加载最近一次任务结果
const loadLastTask = async () => {
  try {
    const lastTaskId = localStorage.getItem('digital_human_last_task_id')
    if (!lastTaskId) return

    const res = await getDigitalHumanTask(lastTaskId)
    if (res.code === 0 && res.data) {
      const task = res.data
      currentTaskId.value = lastTaskId

      // 如果任务是成功状态，显示结果
      if (task.status === 'SUCCESS' && task.resultVideoUrl) {
        previewVideoUrl.value = task.resultVideoUrl
        currentTaskStatus.value = 'SUCCESS'
        currentTaskError.value = ''
        message.success('已自动加载最近一次任务结果')
      } else if (task.status === 'PROCESSING') {
        // 如果任务还在处理中，继续轮询
        currentTaskStatus.value = 'PROCESSING'
        currentTaskStep.value = task.currentStep
        generationProgress.value = task.progress || 0
        currentTaskError.value = ''
        isGenerating.value = true
        pollTaskStatus()
      } else if (task.status === 'FAILED') {
        currentTaskStatus.value = 'FAILED'
        currentTaskError.value = task.errorMessage || '未知错误'
      }

      // 重置识别状态，允许重新识别
      identified.value = false
    }
  } catch (error) {
    console.error('loadLastTask error:', error)
    // 清理无效的缓存
    localStorage.removeItem('digital_human_last_task_id')
  }
}

// 工具函数
const formatTime = (ms) => {
  const seconds = Math.floor(ms / 1000)
  const minutes = Math.floor(seconds / 60)
  return String(minutes).padStart(2, '0') + ':' + String(seconds % 60).padStart(2, '0')
}

// 状态描述映射
const getStatusText = (status) => {
  const statusMap = {
    'PENDING': '等待处理',
    'PROCESSING': '处理中',
    'SUCCESS': '已完成',
    'FAILED': '失败',
    'CANCELED': '已取消'
  }
  return statusMap[status] || status || '未知'
}

const getStepText = (step) => {
  const stepMap = {
    'prepare_files': '准备文件',
    'synthesize_voice': '语音合成',
    'sync_lip': '口型同步',
    'finishing': '完成处理',
    'canceled': '已取消'
  }
  return stepMap[step] || step || '未知'
}

const playAudioPreview = (url, options = {}) => {
  if (!url) return message.warning('暂无可试听的音频')

  try {
    previewAudio?.pause?.()
    previewAudio = null
  } catch {}

  const audio = new Audio(url)
  const cleanup = () => {
    if (options.revokeOnEnd && url.startsWith('blob:')) {
      URL.revokeObjectURL(url)
      previewObjectUrl === url && (previewObjectUrl = '')
    }
    previewAudio = null
    options.onEnded && options.onEnded()
  }

  audio.play()
    .then(() => {
      previewAudio = audio
      audio.onended = cleanup
      audio.onerror = () => {
        console.error('播放失败')
        cleanup()
        message.error('播放失败')
      }
    })
    .catch(err => {
      console.error('播放失败:', err)
      cleanup()
      message.error('播放失败')
    })
}

const downloadVideo = () => {
  if (!previewVideoUrl.value) return message.warning('没有可下载的视频')
  const link = document.createElement('a')
  link.href = previewVideoUrl.value
  link.download = `可灵数字人视频_${Date.now()}.mp4`
  link.click()
}

// 生命周期
onMounted(async () => {
  await voiceStore.refresh()

  // 默认选择第一个音色
  if (userVoiceCards.value.length > 0) {
    selectVoiceProfile(userVoiceCards.value[0])
  }

  // 加载最近一次任务结果
  await loadLastTask()
})

// 音频实例
let previewAudio = null
let previewObjectUrl = ''
</script>

<style scoped>
.kling-page { padding: 24px; min-height: 100vh; background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%); }
.page-header { text-align: center; margin-bottom: 40px; color: #fff; }
.page-header h1 { font-size: 32px; margin-bottom: 8px; background: linear-gradient(135deg, #3B82F6 0%, #8B5CF6 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
.page-desc { color: #94a3b8; }
.kling-content { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; max-width: 1400px; margin: 0 auto; }
.upload-panel, .result-panel { background: rgba(255, 255, 255, 0.05); border-radius: 16px; padding: 24px; }

.section { margin-bottom: 24px; }
.section h3 { color: #fff; margin-bottom: 12px; font-size: 16px; }

.tts-textarea {
  background: rgba(0, 0, 0, 0.3);
  border: 1px solid rgba(59, 130, 246, 0.2);
  border-radius: 8px;
  padding: 12px;
  color: #fff;
}

.voice-source-toggle {
  display: inline-flex;
  border: 1px solid rgba(59, 130, 246, 0.2);
  border-radius: 24px;
  overflow: hidden;
  margin-bottom: 12px;
}

.source-btn {
  padding: 6px 16px;
  border: none;
  background: transparent;
  color: var(--color-text-secondary);
  cursor: pointer;
}

.source-btn.active {
  background: rgba(59, 130, 246, 0.2);
  color: var(--color-text);
}

.empty-voices {
  padding: 8px 12px;
  font-size: 12px;
  color: var(--color-text-secondary);
  background: rgba(0, 0, 0, 0.3);
  border: 1px dashed rgba(59, 130, 246, 0.3);
  border-radius: var(--radius-card);
}

.voice-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.voice-list-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 12px 14px;
  border: 1px solid rgba(59, 130, 246, 0.15);
  border-radius: 10px;
  background: rgba(0, 0, 0, 0.35);
  cursor: pointer;
  color: #fff;
}

.voice-list-item:hover {
  border-color: rgba(59, 130, 246, 0.5);
}

.voice-list-item.selected {
  border-color: var(--color-primary);
  background: rgba(59, 130, 246, 0.2);
}

.voice-item-info {
  display: flex;
  align-items: center;
  gap: 10px;
}

.voice-avatar {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--color-surface);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 20px;
}

.voice-item-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.voice-name {
  font-size: 13px;
  font-weight: 600;
}

.voice-desc {
  font-size: 12px;
  color: var(--color-text-secondary);
}

.preview-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
}

.preview-icon {
  font-size: 18px;
  color: var(--color-text);
  transition: all 0.3s;
}

.preview-icon.idle {
  opacity: 0.5;
}

.preview-icon.spinning {
  animation: spin 1s linear infinite;
  color: var(--color-text);
}

@keyframes spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

.preview-icon.playing {
  animation: pulse 1.5s ease-in-out infinite;
  color: var(--color-text);
}

@keyframes pulse {
  0%, 100% { transform: scale(1); opacity: 1; }
  50% { transform: scale(1.1); opacity: 0.7; }
}

.control-group {
  margin-bottom: 16px;
}

.control-label {
  font-size: 14px;
  font-weight: 600;
  color: #fff;
  margin-bottom: 8px;
}

.slider-card {
  border: 1px solid rgba(59, 130, 246, 0.2);
  border-radius: 12px;
  padding: 10px 12px;
  background: rgba(0, 0, 0, 0.25);
}

.slider-info {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 12px;
  color: var(--color-text-secondary);
  margin-bottom: 8px;
}

.slider-value {
  font-size: 14px;
  font-weight: 600;
  color: #fff;
}

.reset-btn {
  padding: 4px 12px;
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-text);
  border-radius: 4px;
  cursor: pointer;
  font-size: 12px;
}

.control-tabs {
  display: inline-flex;
  border: 1px solid rgba(59, 130, 246, 0.2);
  border-radius: 20px;
  overflow: hidden;
  margin-bottom: 8px;
}

.tab-btn {
  padding: 6px 20px;
  border: none;
  background: transparent;
  color: var(--color-text-secondary);
  cursor: pointer;
  font-size: 12px;
}

.tab-btn.active {
  background: rgba(59, 130, 246, 0.2);
  color: #fff;
}

.emotion-buttons {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.emotion-btn {
  padding: 8px 16px;
  border: 1px solid rgba(59, 130, 246, 0.2);
  border-radius: 20px;
  font-size: 12px;
  color: var(--color-text-secondary);
  background: rgba(0, 0, 0, 0.3);
  cursor: pointer;
  transition: all 0.3s;
}

.emotion-btn:hover {
  border-color: rgba(59, 130, 246, 0.5);
  background: rgba(59, 130, 246, 0.1);
}

.emotion-btn.active {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: white;
}

.upload-zone {
  min-height: 300px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 2px dashed rgba(59, 130, 246, 0.3);
  border-radius: 12px;
}

.upload-placeholder {
  text-align: center;
  color: #94a3b8;
}

.upload-placeholder h3 {
  color: #fff;
}

.video-preview {
  text-align: center;
  color: #fff;
}

.preview-video {
  width: 100%;
  max-height: 300px;
  border-radius: 8px;
  margin-bottom: 16px;
}

.action-buttons {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-top: 24px;
}

.result-placeholder {
  min-height: 400px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #94a3b8;
}

.result-content {
  color: #fff;
}

.result-section {
  margin-bottom: 24px;
}

.result-section h3 {
  margin-bottom: 12px;
  font-size: 18px;
}

.face-list {
  display: flex;
  flex-direction: column;
  gap: 16px;
  margin-top: 16px;
}

.face-card {
  display: flex;
  gap: 16px;
  padding: 16px;
  background: rgba(255, 255, 255, 0.05);
  border-radius: 8px;
}

.generated-video {
  width: 100%;
  max-height: 400px;
  border-radius: 8px;
  margin-top: 12px;
}

.video-actions {
  margin-top: 16px;
  display: flex;
  justify-content: center;
}

.task-status-section {
  margin-bottom: 16px;
}

.task-status-section h3 {
  color: #fff;
  margin-bottom: 12px;
  font-size: 16px;
}

.status-card {
  background: rgba(0, 0, 0, 0.3);
  border: 1px solid rgba(59, 130, 246, 0.2);
  border-radius: 8px;
  padding: 12px;
}

.status-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
}

.status-row:last-child {
  margin-bottom: 0;
}

.status-label {
  font-size: 13px;
  color: var(--color-text-secondary);
  font-weight: 600;
}

.status-value {
  font-size: 13px;
  color: #fff;
}

.progress-row {
  margin: 12px 0;
}

.error-row {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin-top: 12px;
  padding: 8px;
  background: rgba(255, 0, 0, 0.1);
  border: 1px solid rgba(255, 0, 0, 0.3);
  border-radius: 6px;
}

.error-label {
  font-size: 13px;
  color: #ff6b6b;
  font-weight: 600;
  flex-shrink: 0;
}

.error-value {
  font-size: 13px;
  color: #ff6b6b;
  word-break: break-word;
}

@media (max-width: 1024px) {
  .kling-content {
    grid-template-columns: 1fr;
  }
}
</style>
