<template>
  <view class="digital-human">
    <view class="avatar-stage">
      <view class="avatar-ring" :class="{ speaking: isSpeaking }">
        <!-- Default CSS avatar if image fails -->
        <view class="avatar-default" v-if="imgFailed">
          <text class="avatar-initials">AI</text>
        </view>
        <image class="avatar-img" :src="avatarSrc" mode="aspectFill" @error="imgFailed = true" v-else />
        <canvas
          v-if="isH5"
          id="dh-mouth"
          class="mouth-canvas"
        ></canvas>
      </view>
      <text class="role-label">AI 面试官</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, computed, watch, onMounted, onBeforeUnmount, nextTick } from 'vue'

const props = defineProps({
  text: { type: String, default: '' },
  audioUrl: { type: String, default: '' },
  avatarUrl: { type: String, default: '' },
  autoPlay: { type: Boolean, default: true },
})

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

const isH5 = ref(false)
const isSpeaking = ref(false)
const currentText = ref('')
const imgFailed = ref(false)
let audioEl = null
let audioCtx = null
let analyser = null
let animFrameId = null
let mouthScale = 0

onMounted(() => {
  isH5.value = typeof window !== 'undefined' && typeof document !== 'undefined'
  initCanvas()
})

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

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

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

const avatarSrc = computed(() => {
  return props.avatarUrl || '/static/default-avatar.png'
})

function initCanvas() {
  if (!isH5.value) return
  const canvas = document.getElementById('dh-mouth')
  if (!canvas) return
  // Size the canvas to match mouth area (~40% width, ~15% height, centered bottom)
  const parent = canvas.parentElement
  if (!parent) return
  const rect = parent.getBoundingClientRect()
  canvas.width = rect.width * 0.4
  canvas.height = rect.height * 0.15
  canvas.style.width = canvas.width + 'px'
  canvas.style.height = canvas.height + 'px'
  canvas.style.position = 'absolute'
  canvas.style.bottom = '18%'
  canvas.style.left = '30%'
}

function drawMouth(openRatio) {
  if (!isH5.value) return
  const canvas = document.getElementById('dh-mouth')
  if (!canvas) return
  const ctx = canvas.getContext('2d')
  if (!ctx) return

  const w = canvas.width
  const h = canvas.height
  ctx.clearRect(0, 0, w, h)

  const mouthH = Math.max(2, h * openRatio)
  const mouthW = w * 0.8

  ctx.fillStyle = '#C97B84'
  ctx.beginPath()
  ctx.ellipse(w / 2, h / 2 + (h - mouthH) / 2, mouthW / 2, mouthH / 2, 0, 0, Math.PI * 2)
  ctx.fill()

  if (openRatio > 0.1) {
    ctx.fillStyle = '#2D1B1E'
    ctx.beginPath()
    ctx.ellipse(w / 2, h / 2 + (h - mouthH) / 2 + 1, mouthW / 4, mouthH / 4, 0, 0, Math.PI * 2)
    ctx.fill()
  }
}

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

  try {
    audioEl = new Audio(url)

    if (isH5.value) {
      audioCtx = new (window.AudioContext || window.webkitAudioContext)()
      const source = audioCtx.createMediaElementSource(audioEl)
      analyser = audioCtx.createAnalyser()
      analyser.fftSize = 256
      source.connect(analyser)
      analyser.connect(audioCtx.destination)
    }

    audioEl.onended = () => {
      finishSpeaking()
    }

    audioEl.onerror = () => {
      finishSpeaking()
    }

    await audioEl.play()

    if (analyser) {
      animateMouth()
    }
  } catch (e) {
    finishSpeaking()
  }
}

function animateMouth() {
  if (!analyser) return
  const dataArray = new Uint8Array(analyser.frequencyBinCount)

  function tick() {
    if (!isSpeaking.value) return
    analyser.getByteFrequencyData(dataArray)
    const sum = dataArray.reduce((a, b) => a + b, 0)
    const avg = sum / dataArray.length
    mouthScale = Math.min(1, avg / 128)
    // Smooth
    mouthScale = Math.max(0.05, mouthScale)
    drawMouth(mouthScale)
    animFrameId = requestAnimationFrame(tick)
  }

  tick()
}

function finishSpeaking() {
  isSpeaking.value = false
  emit('speaking-end')
  if (analyser) {
    mouthScale = 0
    drawMouth(0)
  }
  cleanupAudio()
}

function cleanupAudio() {
  if (animFrameId) { cancelAnimationFrame(animFrameId); animFrameId = null }
  if (audioCtx) { audioCtx.close().catch(() => {}); audioCtx = null }
  analyser = null
  audioEl = null
}

function stopAudio() {
  if (audioEl) {
    try { audioEl.pause(); audioEl.src = '' } catch {}
  }
  finishSpeaking()
}

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

<style scoped>
.digital-human {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 20rpx 0;
}

/* Avatar stage */
.avatar-stage {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12rpx;
}

.avatar-ring {
  width: 200rpx;
  height: 200rpx;
  border-radius: 50%;
  border: 4rpx solid #E5E7EB;
  overflow: hidden;
  position: relative;
  transition: border-color 0.3s, box-shadow 0.3s;
}

.avatar-ring.speaking {
  border-color: #6366F1;
  box-shadow: 0 0 30rpx rgba(99, 102, 241, 0.3);
}

.avatar-img {
  width: 100%;
  height: 100%;
}

.avatar-default {
  width: 100%;
  height: 100%;
  background: linear-gradient(135deg, #6366F1, #8B5CF6);
  display: flex;
  align-items: center;
  justify-content: center;
}

.avatar-initials {
  font-size: 48rpx;
  font-weight: 700;
  color: #FFFFFF;
}

.mouth-canvas {
  pointer-events: none;
}

.role-label {
  font-size: 22rpx;
  color: #6B7280;
  background: #F3F4F6;
  padding: 4rpx 20rpx;
  border-radius: 20rpx;
}

/* Speech bubble */
.speech-area {
  margin-top: 24rpx;
  padding: 0 40rpx;
  width: 100%;
  box-sizing: border-box;
}

.speech-bubble {
  background: #FFFFFF;
  border-radius: 16rpx;
  padding: 24rpx 28rpx;
  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: 28rpx;
  line-height: 1.7;
  color: #1F2937;
}
</style>
