<template>
  <div class="container">
    <div v-if="step === 1">
      <div class="faceWindowWrapper">
        <div class="faceWindow" :style="{ borderColor: tagColor }">
          <video id="page_draw-video" muted playsinline></video>
        </div>
        <div class="changeButton">
          <a-button type="primary" shape="circle"
            @click="state.constraints.video.facingMode === 'user' ? state.constraints.video.facingMode = 'environment' : state.constraints.video.facingMode = 'user'">
            <icon-sync />
          </a-button>
        </div>
      </div>

      <div class="faceInfo">
        <a-tag size="large" :color="tagColor">
          {{ tagInfo }}
        </a-tag>
      </div>
      <div class="countTime" v-if="state.isRecording">
        <span>还需等待：</span>
        <span class="time">{{ recTime }}</span>
        <span>s</span>
      </div>
    </div>

    <div v-if="step === 2" class="spin">
      <a-spin dot tip="人脸信息上传中，请稍候..." />
    </div>

    <div v-if="step === 3">
      <a-result status="success" title="采集完成">
        <template #extra>
          <a-space>
            <a-button type='primary' @click="$router.go(0)">返回</a-button>
          </a-space>
        </template>
      </a-result>
    </div>

    <div v-if="step === 4">
      <a-result status="error" title="采集失败">
        <template #extra>
          <a-space>
            <a-button type='primary' @click="$router.go(0)">返回重试</a-button>
          </a-space>
        </template>
      </a-result>
    </div>

    <div class="userInfo">
      <div class="left">
        <a-avatar>
          <img :alt="props.userInfo.name ?? props.userInfo.student_num"
            :src="props.userInfo.user_avatar ?? 'https://lepao-cloud.xxoo365.top/view.php/25aa126dc406974ff3579a99a2c6501a.png'" />
        </a-avatar>
      </div>
      <div class="right">
        <div class="name">{{ props.userInfo.name }}</div>
        <div class="sub">{{ props.userInfo.academy_name }}</div>
      </div>
    </div>
  </div>

  <div class="page" style="display: none">
    <div class="page_draw" v-show="!state.netsLoadModel">
      <img id="page_draw-img-target" :src="props.userInfo.face_img" />
      <canvas id="page_draw-canvas-target"></canvas>
      <canvas id="page_draw-video-canvas"></canvas>
    </div>
  </div>

  <div class="loading" v-if="state.netsLoadModel">
    <div class="loadingTip">
      <icon-loading :size="32" />
      <div class="text">努力加载中</div>
    </div>
  </div>
</template>

<script setup>
import { Message } from "@arco-design/web-vue"
import * as faceapi from "@vladmandic/face-api"
import axios from 'axios'
import { ref, onMounted, onUnmounted, reactive, watch } from "vue"

const tagInfo = ref('请将面部完全放置在取景框中央')
const tagColor = ref('blue')
const step = ref(1)
const recTime = ref(10)
let isSuccessRecording = false
let countdownTimer = null

const props = defineProps({
  userInfo: {
    type: Object,
    required: true
  }
})

/**属性状态 */
const state = reactive({
  netsLoadModel: true,
  netsType: "tinyFaceDetector",
  netsOptions: {
    ssdMobilenetv1: undefined,
    tinyFaceDetector: undefined,
  },
  faceMatcher: null,
  targetImgEl: null,
  targetCanvasEl: null,
  discernVideoEl: null,
  discernCanvasEl: null,
  timer: 0,
  constraints: {
    audio: false,
    video: {
      width: { min: 320, ideal: 720, max: 1280 },
      height: { min: 200, ideal: 480, max: 720 },
      frameRate: { min: 7, ideal: 15, max: 30 },
      facingMode: "user",
    },
  },
  stream: null,

  /** 录制相关 */
  recorder: null,
  recordingChunks: [],
  recordingTimer: null,
  isRecording: false,
  lastBlob: null,
  failCount: 0,
  uploading: false, // 上传状态
})

/**启动录制 */
function startRecording() {
  if (state.isRecording || !state.stream) return
  state.recordingChunks = []
  state.recorder = new MediaRecorder(state.stream, { mimeType: "video/webm" })

  state.recorder.ondataavailable = (e) => {
    if (e.data.size > 0) {
      state.recordingChunks.push(e.data)
    }
  }

  // 停止录制时
  state.recorder.onstop = () => {
    const blob = new Blob(state.recordingChunks, { type: "video/webm" })

    if (isSuccessRecording && blob) {
      uploadVideo(blob)
    }
  }

  state.recorder.start()
  state.isRecording = true
  recTime.value = 10   // ✅ 开始时重置倒计时

  // 倒计时逻辑
  countdownTimer = setInterval(() => {
    if (recTime.value > 0) {
      recTime.value -= 1
    }
  }, 1000)

  // 10秒后自动停止并标记为成功
  state.recordingTimer = setTimeout(() => {
    isSuccessRecording = true
    stopRecording()   // ✅ 成功录制
    fnClose()

    step.value = 3

  }, recTime.value * 1000)
}

/**结束录制 */
function stopRecording() {
  if (state.recorder && state.isRecording) {
    state.recorder.stop()
    state.isRecording = false

    clearTimeout(state.recordingTimer)
    state.recordingTimer = null

    clearInterval(countdownTimer)
    countdownTimer = null
    recTime.value = 10
  }
}

/**上传视频到服务器 */
async function uploadVideo(blob) {
  try {
    step.value = 2
    const formData = new FormData()
    formData.append("student_num", props.userInfo.student_num)
    formData.append("key", props.userInfo.key)
    formData.append("upload", blob, "face_record.webm")

    const url = import.meta.env.VITE_APP_API_BASE_URL + '/UploadFaceVideo'

    const res = await axios.post(url, formData, {
      headers: { "Content-Type": "multipart/form-data" }
    })

    if (!res || !res.data || res.data.code !== 0) {
      step.value = 4
      throw new Error(res?.data?.msg ?? '上传失败，请稍后再试')
    }
    Message.success("人脸数据上传成功")
    step.value = 3
  } catch (err) {
    step.value = 4
    Message.error(err.message ?? "人脸数据上传失败")
    console.error(err)
  }
}

/**重新开始录制 */
function restartRecording() {
  stopRecording()
  // 小延迟确保上一次 recorder 彻底结束
  setTimeout(() => {
    startRecording()
  }, 200)
}

/**初始化模型加载 */
async function fnLoadModel() {
  const modelsPath = `/models`
  await faceapi.nets.faceLandmark68Net.load(modelsPath)
  await faceapi.nets.faceRecognitionNet.load(modelsPath)
  await faceapi.nets.tinyFaceDetector.load(modelsPath)
  state.netsOptions.tinyFaceDetector = new faceapi.TinyFaceDetectorOptions({
    inputSize: 416,
    scoreThreshold: 0.5,
  })

  state.targetImgEl = document.getElementById("page_draw-img-target")
  state.discernVideoEl = document.getElementById("page_draw-video")
  state.discernCanvasEl = document.getElementById("page_draw-video-canvas")

  state.netsLoadModel = false
}

/**根据模型参数识别绘制--目标图 */
async function fnRedrawTarget() {
  const detect = await faceapi
    .detectAllFaces(state.targetImgEl, state.netsOptions[state.netsType])
    .withFaceLandmarks()
    .withFaceDescriptors()
  if (!detect || detect.length === 0) {
    state.faceMatcher = null
    return
  }
  state.faceMatcher = new faceapi.FaceMatcher(detect)
}

/**根据模型参数识别绘制 */
async function fnRedrawDiscern() {
  if (!state.faceMatcher) return

  if (state.discernVideoEl.paused) {
    clearTimeout(state.timer)
    state.timer = 0
    return
  }

  const detect = await faceapi
    .detectAllFaces(state.discernVideoEl, state.netsOptions[state.netsType])
    .withFaceLandmarks()
    .withFaceDescriptors()

  if (!detect || detect.length === 0) {
    state.failCount++
    if (state.failCount >= 3) {
      tagColor.value = 'blue'
      tagInfo.value = '请将面部完全放置在取景框中央'
      if (state.isRecording) stopRecording()
    }

    // 延迟下一次识别，避免直接递归造成栈/CPU 问题
    clearTimeout(state.timer)
    state.timer = setTimeout(() => fnRedrawDiscern(), 300)
    return
  }

  const dims = faceapi.matchDimensions(
    state.discernCanvasEl,
    state.discernVideoEl,
    true
  )
  const result = faceapi.resizeResults(detect, dims)
  // 若检测到多张脸，逐一判断（若任一匹配成功则开始录制）
  for (const item of result) {
    const descriptor = item.descriptor
    const best = state.faceMatcher.findBestMatch(descriptor)

    if (best) {
      if (best._distance <= 0.6) {
        state.failCount = 0
        tagColor.value = 'green'
        tagInfo.value = '录制中，请确保面部不要离开取景框'
        if (!state.isRecording) startRecording()
        // 找到一个合格的人脸就可以停止遍历
        break
      } else {
        state.failCount = 3
        tagColor.value = 'red'
        tagInfo.value = `人脸匹配失败，请确保为 ${props?.userInfo?.name} 本人操作`
        if (state.isRecording) restartRecording()
      }
    }
  }

  // 继续调度下一次识别（0ms 相当于下一事件循环）
  clearTimeout(state.timer)
  state.timer = setTimeout(() => fnRedrawDiscern(), 0)
}

/**启动摄像头视频媒体 */
async function fnOpen() {
  if (state.stream !== null) return
  try {
    state.stream = {}
    const stream = await navigator.mediaDevices.getUserMedia(state.constraints)
    state.stream = stream
    state.discernVideoEl.srcObject = stream
    await state.discernVideoEl.play()
    state.discernCanvasEl.width = state.discernVideoEl.videoWidth
    state.discernCanvasEl.height = state.discernVideoEl.videoHeight
    // 稍作延迟再开始识别
    setTimeout(() => fnRedrawDiscern(), 300)
  } catch (error) {
    state.stream = null

    Message.error("视频媒体流获取错误: " + error)
  }
}

/**结束摄像头视频媒体 */
function fnClose() {
  if (state.stream === null) return
  try {
    state.discernVideoEl.pause()
    state.discernVideoEl.srcObject = null
    state.stream.getTracks().forEach((track) => track.stop())
  } catch (err) {
    console.warn("关闭流时出错：", err)
  } finally {
    state.stream = null
    clearTimeout(state.timer)
    state.timer = 0
    stopRecording()
  }
}

watch(
  () => state.constraints.video.facingMode,
  () => {
    if (state.stream !== null) {
      fnClose()
      fnOpen()
    } else {
      fnClose()
    }
  }
)

onMounted(() => {
  fnLoadModel().then(() => {
    fnRedrawTarget()
    fnOpen()
  })
})

onUnmounted(() => {
  fnClose()
})
</script>

<style lang="less" scoped>
.container {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.faceWindowWrapper {
  position: relative;
  width: 270px;
  height: 270px;
}

/* 圆形视频框 */
.faceWindow {
  width: 100%;
  height: 100%;
  border-radius: 50%;
  border: 5px solid;
  overflow: hidden;
  background: #fff;
  position: relative;
}

/* 按钮 */
.changeButton {
  position: absolute;
  right: -10px;
  /* 超出圆形框右边 */
  bottom: -10px;
  /* 超出圆形框底边 */
}

.faceInfo {
  margin-top: 20px;
}

.countTime {
  margin-top: 10px;
  font-size: 1.1em;
  font-family: AlibabaSans, -apple-system, BlinkMacSystemFont;

  .time {
    font-size: 1.3em;
  }
}

.faceWindow video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* 保持视频比例并填满圆形 */
  transform: scaleX(-1);
}

.spin {
  display: flex;
  align-items: center;
  min-height: 270px;
}

.userInfo {
  display: flex;
  align-items: center;
  text-align: left;
  height: 60px;
  min-width: 300px;
  margin-top: 20px;
  border-radius: 10px;
  background-color: rgba(190, 218, 255, 0.5);
  padding: 10px;

  .right {
    margin-left: 10px;

    .name {
      font-size: 1.2em;
      font-weight: bold;
    }
  }
}

.loading {
  position: fixed;
  top: 0;
  left: 0;
  height: 100%;
  width: 100%;
  background-color: rgba(0, 0, 0, 0.2);
  z-index: 9999;
  border-radius: 10px;

  .loadingTip {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    color: #3370FF;

    .text {
      font-family: 'Alimama ShuHeiTi';
      font-size: 1.2em;
    }
  }
}
</style>
