<template>
  <view class="digital-human">
    <view class="avatar-stage">
      <view class="avatar-body" :class="{ speaking: isSpeaking }">
        <image class="avatar-img" :src="avatarSrc" mode="aspectFill" @error="avatarError = true" />
        <view class="mouth-overlay" :style="mouthStyle"></view>
      </view>
      <image src="/static/avatar-default.png" style="display:none" />
      <image src="/static/avatar-software.png" style="display:none" />
      <image src="/static/avatar-frontend.png" style="display:none" />
      <image src="/static/avatar-backend.png" style="display:none" />
      <image src="/static/avatar-algo.png" style="display:none" />
      <image src="/static/avatar-pm.png" style="display:none" />
      <image src="/static/avatar-data.png" style="display:none" />
      <image src="/static/avatar-marketing.png" style="display:none" />
      <image src="/static/avatar-ops.png" style="display:none" />
      <image src="/static/avatar-ui.png" style="display:none" />
      <view class="status-dot" :class="{ active: isSpeaking }"></view>
      <text class="role-label">{{ positionLabel }}</text>
    </view>
    <view class="speech-area" v-if="currentText">
      <view class="speech-bubble">
        <text class="speech-text">{{ currentText }}</text>
      </view>
    </view>
  </view>
</template>

<script setup>
import { ref, watch, computed, onBeforeUnmount, nextTick } from 'vue'

const props = defineProps({
  text: { type: String, default: '' },
  audioUrl: { type: String, default: '' },
  amplitudeData: { type: Array, default: () => [] },
  avatarUrl: { type: String, default: '' },
  position: { type: String, default: '' },
  autoPlay: { type: Boolean, default: true },
})

const emit = defineEmits(['speaking-start', 'speaking-end'])

const isSpeaking = ref(false)
const currentText = ref('')
const mouthOpenness = ref(0)
const avatarError = ref(false)

let audioEl = null
let animFrame = null

const allAvatars = [
  '/static/avatar-default.png',
  '/static/avatar-software.png',
  '/static/avatar-frontend.png',
  '/static/avatar-backend.png',
  '/static/avatar-algo.png',
  '/static/avatar-pm.png',
  '/static/avatar-data.png',
  '/static/avatar-marketing.png',
  '/static/avatar-ops.png',
  '/static/avatar-ui.png',
]

const avatarMap = {
  '软件开发': '/static/avatar-software.png',
  '前端开发': '/static/avatar-frontend.png',
  '后端开发': '/static/avatar-backend.png',
  '算法工程师': '/static/avatar-algo.png',
  '产品经理': '/static/avatar-pm.png',
  '数据分析': '/static/avatar-data.png',
  '市场营销': '/static/avatar-marketing.png',
  '运营': '/static/avatar-ops.png',
  'UI设计': '/static/avatar-ui.png',
}

const avatarSrc = computed(() => {
  if (avatarError.value) return '/static/avatar-default.png'
  if (props.avatarUrl) return props.avatarUrl
  const key = Object.keys(avatarMap).find(k => props.position.includes(k))
  return key ? avatarMap[key] : '/static/avatar-default.png'
})

const positionLabel = computed(() => {
  return props.position ? `${props.position} 面试官` : 'AI 面试官'
})

onBeforeUnmount(() => {
  stopAudio()
})

watch(() => props.audioUrl, (url) => {
  if (url && props.autoPlay) {
    nextTick(() => playAudio(url))
  }
})

watch(() => props.text, (txt) => {
  currentText.value = txt
})

const mouthStyle = computed(() => {
  const o = mouthOpenness.value
  const h = 3 + o * o * 22
  const w = 18 + o * 8
  return {
    height: h + 'rpx',
    width: w + 'rpx',
    borderRadius: o > 0.3 ? '50%' : '4rpx',
    background: o > 0.5 ? 'linear-gradient(180deg, #C97B84, #A85562)' : '#C97B84',
    opacity: o > 0.01 ? 1 : 0.3,
  }
})

function getAmplitude(positionMs) {
  const amp = props.amplitudeData
  if (!amp || amp.length === 0) return -1
  const idx = Math.floor(positionMs / 50)
  if (idx >= amp.length) return -1
  return amp[idx]
}

function tickMouth() {
  if (!audioEl) return
  const currentTime = audioEl.currentTime * 1000 || 0
  const amp = getAmplitude(currentTime)
  if (amp >= 0) {
    const openness = Math.pow(Math.min(1, amp * 2.5), 0.7)
    mouthOpenness.value = openness
  } else {
    const t = Date.now() / 1000
    const idle = (Math.sin(t * 4) + 1) / 2 * 0.15
    mouthOpenness.value = Math.max(0.03, idle)
  }
  animFrame = setTimeout(tickMouth, 50)
}

function playAudio(url) {
  stopAudio()
  isSpeaking.value = true
  emit('speaking-start')
  tickMouth()

  try {
    const innerAudio = uni.createInnerAudioContext()
    audioEl = innerAudio
    innerAudio.src = url
    innerAudio.autoplay = true
    innerAudio.onEnded(() => finishSpeaking())
    innerAudio.onError(() => finishSpeaking())
    innerAudio.onStop(() => finishSpeaking())
  } catch (e) {
    finishSpeaking()
  }
}

function finishSpeaking() {
  isSpeaking.value = false
  mouthOpenness.value = 0
  cleanupTimers()
  cleanupAudio()
  emit('speaking-end')
}

function cleanupTimers() {
  if (animFrame) { clearTimeout(animFrame); animFrame = null }
}

function cleanupAudio() {
  if (audioEl) {
    try { audioEl.stop(); audioEl.destroy() } catch {}
    audioEl = null
  }
}

function stopAudio() {
  if (audioEl) { try { audioEl.stop(); audioEl.destroy() } catch {} }
  finishSpeaking()
}

defineExpose({ play: playAudio, stop: stopAudio })
</script>

<style scoped>
.digital-human {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 10rpx 0;
}
.avatar-stage {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10rpx;
  position: relative;
}
.avatar-body {
  width: 200rpx;
  height: 260rpx;
  border-radius: 20rpx;
  border: 4rpx solid #E5E7EB;
  overflow: hidden;
  position: relative;
  transition: border-color 0.3s, box-shadow 0.3s;
  animation: breathing 4s ease-in-out infinite;
}
.avatar-body.speaking {
  border-color: #6366F1;
  box-shadow: 0 0 40rpx rgba(99, 102, 241, 0.4);
  animation: speakPulse 1.5s ease-in-out infinite;
}
@keyframes speakPulse {
  0%, 100% { box-shadow: 0 0 20rpx rgba(99, 102, 241, 0.3); }
  50% { box-shadow: 0 0 50rpx rgba(99, 102, 241, 0.6); }
}
@keyframes breathing {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.012); }
}
.avatar-img {
  width: 100%;
  height: 100%;
}
.mouth-overlay {
  position: absolute;
  top: 36.8%;
  left: 50%;
  transform: translateX(-50%);
  transition: all 0.08s cubic-bezier(0.25, 0.1, 0.25, 1);
}
.status-dot {
  width: 14rpx;
  height: 14rpx;
  border-radius: 50%;
  background: #9CA3AF;
  position: absolute;
  top: 8rpx;
  right: 8rpx;
  transition: background 0.3s;
}
.status-dot.active {
  background: #10B981;
  box-shadow: 0 0 12rpx rgba(16, 185, 129, 0.6);
}
.role-label {
  font-size: 22rpx;
  color: #6B7280;
  background: #F3F4F6;
  padding: 4rpx 20rpx;
  border-radius: 20rpx;
}
.speech-area {
  margin-top: 12rpx;
  padding: 0 40rpx;
  width: 100%;
  box-sizing: border-box;
}
.speech-bubble {
  background: #FFFFFF;
  border-radius: 16rpx;
  padding: 20rpx 24rpx;
  box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
  position: relative;
}
.speech-bubble::before {
  content: '';
  position: absolute;
  top: -12rpx;
  left: 60rpx;
  border: 12rpx solid transparent;
  border-bottom-color: #FFFFFF;
  border-top: none;
}
.speech-text {
  font-size: 26rpx;
  line-height: 1.6;
  color: #1F2937;
}
</style>
