<template>
  <div class="container">
    <h2>人脸认证</h2>
    <div class="video-box">
      <video ref="video" autoplay muted playsinline webkit-playsinline x5-playsinline></video>
    </div>
    <canvas ref="overlay" class="overlay"></canvas>

    <!--    <div class="message">{{ message }}</div>-->
    <div v-if="currentAction" class="action-tip">
      {{ actionTips[currentAction] }}
    </div>
  </div>
</template>

<script setup>
import { ref, reactive, onMounted } from 'vue'
import * as faceapi from 'face-api.js'
import { checkLiveness, uploadLivePhoto, verifyCard } from '@/api/modules/livenessVerification.ts'
import { useRouter } from 'vue-router'
import { setStorage } from '@/utils/storage/index.ts'
import { getUserBaseInfo } from '@/api/modules/user.ts'

const router = useRouter()

// DOM refs
const video = ref(null)
const overlay = ref(null)

// 状态
const message = ref('初始化中...')
const capturedFiles = reactive({ front: null, mouth: null, yaw: null })

// 动作提示
const currentAction = ref(null)
const actionTips = {
  front: '请保持正面朝向摄像头',
  mouth: '请张开嘴',
  yaw: '请左右摇头',
}

// 阈值参数
const MOUTH_OPEN_THRESHOLD = 15
const YAW_THRESHOLD = 20

// --------- 核心函数 ---------
async function init() {
  try {
    message.value = '加载模型中...'
    await faceapi.nets.tinyFaceDetector.load('/models/face')
    await faceapi.nets.faceLandmark68Net.load('/models/face')
    message.value = '模型加载完成，打开摄像头...'

    const stream = await navigator.mediaDevices.getUserMedia({ video: true })
    video.value.srcObject = stream
    await video.value.play()
    message.value = '准备就绪'
    startDetection()
  } catch (err) {
    console.error(err)
    message.value = '摄像头或模型加载失败，请检查'
  }
}

function startDetection() {
  const actions = ['front', 'mouth', 'yaw']
  let index = 0

  const nextAction = async () => {
    if (index >= actions.length) {
      message.value = '认证完成！'
      console.log('抓拍文件结果：', capturedFiles)
      let formData = new FormData()
      formData.append('live_photo1', capturedFiles.front)
      formData.append('live_photo2', capturedFiles.mouth)
      formData.append('live_photo3', capturedFiles.yaw)
      try {
        await uploadLivePhoto(formData)
        const data = await checkLiveness()
        if (!data.flag) {
          ElMessage.error('检测失败，请重新检测')
          await init()
          return
        }
        const verifyCardData = await verifyCard()
        if (!verifyCardData.flag) {
          router.back()
          return ElMessage.error('检测失败')
        }
        const user = await getUserBaseInfo()
        data.userInfo = user.user
        setStorage('userInfo', user.user)
        // clearStorage()
        router.push('/dashboard')
        ElMessage.warning('请重新登录')
      } catch (e) {
        ElMessage.error(e.message)
      }

      return
    }

    currentAction.value = actions[index]
    await detectAction(actions[index])
    index++
    nextAction()
  }

  nextAction()
}

async function detectAction(action) {
  return new Promise((resolve) => {
    const interval = setInterval(async () => {
      const detection = await faceapi
        .detectSingleFace(video.value, new faceapi.TinyFaceDetectorOptions())
        .withFaceLandmarks()

      if (!detection) return

      const landmarks = detection.landmarks

      if (action === 'front') {
        const leftEye = landmarks.getLeftEye()
        const rightEye = landmarks.getRightEye()
        const eyeDiff = Math.abs(leftEye[0].y - rightEye[3].y)
        if (eyeDiff < 10) {
          capturedFiles.front = await captureImage(video.value, 'front.jpg')
          clearInterval(interval)
          resolve()
        }
      }

      if (action === 'mouth') {
        if (isMouthOpen(landmarks)) {
          capturedFiles.mouth = await captureImage(video.value, 'mouth.jpg')
          clearInterval(interval)
          resolve()
        }
      }

      if (action === 'yaw') {
        const yaw = estimateYaw(landmarks)
        if (Math.abs(yaw) > YAW_THRESHOLD) {
          capturedFiles.yaw = await captureImage(video.value, 'yaw.jpg')
          clearInterval(interval)
          resolve()
        }
      }
    }, 200)
  })
}

// ----------------- 工具函数 -----------------
async function captureImage(videoEl, fileName) {
  const canvas = document.createElement('canvas')
  canvas.width = videoEl.videoWidth
  canvas.height = videoEl.videoHeight
  const ctx = canvas.getContext('2d')
  ctx.drawImage(videoEl, 0, 0)

  return new Promise((resolve) => {
    canvas.toBlob(
      (blob) => {
        if (blob) {
          const file = new File([blob], fileName, { type: 'image/jpeg' })
          resolve(file)
        }
      },
      'image/jpeg',
      0.95,
    )
  })
}

function isMouthOpen(landmarks) {
  const mouth = landmarks.getMouth()
  const top = mouth[13]
  const bottom = mouth[19]
  const distance = Math.hypot(top.x - bottom.x, top.y - bottom.y)
  return distance > MOUTH_OPEN_THRESHOLD
}

function estimateYaw(landmarks) {
  const nose = landmarks.getNose()
  const noseTip = nose[3]
  const noseBase = nose[0]
  return noseTip.x - noseBase.x
}

onMounted(() => {
  init()
})
</script>

<style scoped>
.container {
  position: relative;
  width: 100%;
  max-width: 480px;
  margin: 20px auto;
}
h2 {
  text-align: center;
  line-height: 40px;
}
.video-box {
  width: 80vw;
  height: 80vw;
  border-radius: 50%;
  overflow: hidden;
  margin: 20px auto;
  padding: 0;
}
video {
  width: 100%;
  height: 100%;
  transform: scaleX(-1);
  object-fit: cover;
}
.overlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}
.message {
  text-align: center;
  margin-top: 10px;
  font-weight: bold;
}
.action-tip {
  text-align: center;
  margin-top: 80px;
  font-size: 18px;
  color: #333;
}
</style>
