<template>
  <view>
    <view v-if="visible" class="s-verify-mask" @touchmove.stop.prevent>
      <view class="s-verify-wrapper" @tap.stop>
        <view class="s-verify-header">
          <text class="s-verify-title">安全验证</text>
          <view class="s-verify-close" @tap="handleClose">×</view>
        </view>

        <view class="s-verify-body">
          <view class="s-verify-image" :style="imageStyle">
            <image v-if="backgroundImage" class="s-verify-image-bg" :src="backgroundImage" mode="aspectFill" />
            <image
              v-if="showBlock"
              class="s-verify-image-block"
              :style="blockStyle"
              :src="blockImage"
              mode="widthFix"
            />

            <view class="s-verify-refresh" @tap="refreshCaptcha">
              <text>↻</text>
            </view>

            <view v-if="loading" class="s-verify-loading">加载中...</view>
          </view>

          <view v-if="tipMessage" :class="['s-verify-tip', tipClass]">
            {{ tipMessage }}
          </view>

          <view
            class="s-verify-slider"
            ref="sliderRef"
            @touchstart.stop.prevent="onDragStart"
            @touchmove.stop.prevent="onDragMove"
            @touchend.stop.prevent="onDragEnd"
            @mousedown.stop="onMouseDown"
          >
            <view class="s-verify-slider-track" />
            <view class="s-verify-slider-fill" :style="sliderFillStyle" />
            <view
              class="s-verify-slider-handle"
              :class="{
                's-verify-slider-success': success,
                's-verify-slider-fail': failAnimate
              }"
              :style="sliderHandleStyle"
            >
              <text class="s-verify-handle-icon">{{ success ? '✔' : '›' }}</text>
            </view>
            <text class="s-verify-slider-text">{{ sliderHint }}</text>
          </view>
        </view>
      </view>
    </view>
  </view>
</template>

<script setup>
import { computed, nextTick, onBeforeUnmount, reactive, ref } from 'vue'
import nx from '@/nx'
import { aesEncrypt } from './utils/aes'

const props = defineProps({
  captchaType: {
    type: String,
    default: 'blockPuzzle'
  },
  mode: {
    type: String,
    default: 'pop'
  }
})

const emits = defineEmits(['success', 'error'])

const visible = ref(false)
const loading = ref(false)
const success = ref(false)
const failAnimate = ref(false)
const verifying = ref(false)
const tipMessage = ref('')
const sliderRef = ref(null)

const sliderWidth = 310
const sliderHeight = 40
const blockWidth = Math.floor((sliderWidth * 47) / 310)
const imageHeight = 155

const dragState = reactive({
  startClientX: 0,
  baseOffset: 0,
  isDragging: false
})

const captchaState = reactive({
  originalImageBase64: '',
  jigsawImageBase64: '',
  token: '',
  secretKey: ''
})

const handleOffset = ref(0)
const maxOffset = computed(() => Math.max(sliderWidth - blockWidth, 0))

const showBlock = computed(() => props.captchaType === 'blockPuzzle' && blockImage.value)
const backgroundImage = computed(() =>
  captchaState.originalImageBase64 ? `data:image/png;base64,${captchaState.originalImageBase64}` : ''
)
const blockImage = computed(() =>
  captchaState.jigsawImageBase64 ? `data:image/png;base64,${captchaState.jigsawImageBase64}` : ''
)

const imageStyle = computed(() => ({
  width: `${sliderWidth}px`,
  height: `${imageHeight}px`
}))

const blockStyle = computed(() => ({
  width: `${blockWidth}px`,
  height: `${imageHeight}px`,
  transform: `translateX(${handleOffset.value}px)`
}))

const sliderHandleStyle = computed(() => ({
  width: `${blockWidth}px`,
  height: `${sliderHeight}px`,
  transform: `translateX(${handleOffset.value}px)`
}))

const sliderFillStyle = computed(() => ({
  width: `${handleOffset.value > 0 ? Math.min(handleOffset.value + blockWidth, sliderWidth) : 0}px`
}))

const sliderHint = computed(() => {
  if (success.value) {
    return '验证通过'
  }
  if (loading.value) {
    return '验证码加载中'
  }
  return '请按住滑块，拖动完成拼图'
})

const tipClass = computed(() => {
  if (success.value) return 's-verify-tip-success'
  if (failAnimate.value) return 's-verify-tip-error'
  return ''
})

let closeTimer = null
let mouseMoveListener = null
let mouseUpListener = null

function show() {
  if (visible.value) return
  visible.value = true
  resetState()
  nextTick(() => {
    loadCaptcha()
  })
}

function handleClose() {
  visible.value = false
  cleanupMouseListeners()
  clearCloseTimer()
}

function refreshCaptcha() {
  if (loading.value) return
  resetSlider()
  loadCaptcha()
}

function resetSlider() {
  handleOffset.value = 0
  dragState.baseOffset = 0
  dragState.startClientX = 0
  dragState.isDragging = false
  success.value = false
  failAnimate.value = false
  tipMessage.value = ''
  verifying.value = false
}

function resetState() {
  captchaState.originalImageBase64 = ''
  captchaState.jigsawImageBase64 = ''
  captchaState.token = ''
  captchaState.secretKey = ''
  resetSlider()
}

async function loadCaptcha() {
  loading.value = true
  try {
    const res = await nx.$api.sys.getCaptchaCode({ captchaType: props.captchaType })
    if (res && (res.repCode === '0000' || res.code === 0)) {
      const data = res.repData || res.data || {}
      captchaState.originalImageBase64 = data.originalImageBase64 || ''
      captchaState.jigsawImageBase64 = data.jigsawImageBase64 || ''
      captchaState.token = data.token || ''
      captchaState.secretKey = data.secretKey || ''
    } else {
      tipMessage.value = res?.repMsg || res?.msg || '验证码获取失败，请重试'
      emits('error', res)
    }
  } catch (error) {
    console.error('captcha load error', error)
    tipMessage.value = '验证码加载异常，请稍后再试'
    emits('error', error)
  } finally {
    loading.value = false
  }
}

function onDragStart(event) {
  if (loading.value || success.value) return
  dragState.isDragging = true
  dragState.startClientX = getClientX(event)
  dragState.baseOffset = handleOffset.value
  failAnimate.value = false
  tipMessage.value = ''
}

function onDragMove(event) {
  if (!dragState.isDragging) return
  const currentX = getClientX(event)
  const delta = currentX - dragState.startClientX
  let next = dragState.baseOffset + delta
  if (next < 0) next = 0
  if (next > maxOffset.value) next = maxOffset.value
  handleOffset.value = next
}

function onDragEnd() {
  if (!dragState.isDragging) return
  dragState.isDragging = false
  submitVerification()
}

function onMouseDown(event) {
  if (event?.button !== 0) return
  if (typeof window !== 'undefined') {
    mouseMoveListener = ev => {
      onDragMove(ev)
      ev.preventDefault()
    }
    mouseUpListener = ev => {
      cleanupMouseListeners()
      onDragEnd(ev)
    }
    window.addEventListener('mousemove', mouseMoveListener)
    window.addEventListener('mouseup', mouseUpListener)
  }
  onDragStart(event)
}

function cleanupMouseListeners() {
  if (typeof window === 'undefined') return
  if (mouseMoveListener) {
    window.removeEventListener('mousemove', mouseMoveListener)
    mouseMoveListener = null
  }
  if (mouseUpListener) {
    window.removeEventListener('mouseup', mouseUpListener)
    mouseUpListener = null
  }
}

function clearCloseTimer() {
  if (closeTimer) {
    clearTimeout(closeTimer)
    closeTimer = null
  }
}

async function submitVerification() {
  if (verifying.value) return
  verifying.value = true
  failAnimate.value = false

  const point = {
    x: Math.round(handleOffset.value),
    y: 5.0
  }

  try {
    const payload = captchaState.secretKey
      ? aesEncrypt(JSON.stringify(point), captchaState.secretKey)
      : JSON.stringify(point)

    const res = await nx.$api.sys.verifyCaptcha({
      captchaType: props.captchaType,
      token: captchaState.token,
      pointJson: payload
    })

    if (res && (res.repCode === '0000' || res.code === 0)) {
      success.value = true
      tipMessage.value = '验证成功'
      const captchaVerification = captchaState.secretKey
        ? aesEncrypt(`${captchaState.token}---${JSON.stringify(point)}`, captchaState.secretKey)
        : `${captchaState.token}---${JSON.stringify(point)}`

      emits('success', {
        captchaVerification,
        token: captchaState.token,
        point
      })

      clearCloseTimer()
      closeTimer = setTimeout(() => {
        handleClose()
      }, 800)
    } else {
      handleFail(res?.repMsg || res?.msg || '验证失败，请重试')
    }
  } catch (error) {
    console.error('captcha verify error', error)
    handleFail('网络异常，请重试')
  } finally {
    verifying.value = false
  }
}

function handleFail(message) {
  failAnimate.value = true
  tipMessage.value = message
  emits('error', message)
  setTimeout(() => {
    failAnimate.value = false
  }, 400)
  setTimeout(() => {
    refreshCaptcha()
  }, 500)
}

function getClientX(event) {
  if (event?.touches && event.touches.length) {
    return event.touches[0].clientX
  }
  if (event?.changedTouches && event.changedTouches.length) {
    return event.changedTouches[0].clientX
  }
  return event?.clientX || 0
}

onBeforeUnmount(() => {
  cleanupMouseListeners()
  clearCloseTimer()
})

defineExpose({
  show,
  close: handleClose,
  refresh: refreshCaptcha
})
</script>

<style lang="scss" scoped>
.s-verify-mask {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 9999;
}

.s-verify-wrapper {
  width: 340px;
  background: #ffffff;
  border-radius: 16px;
  box-shadow: 0 12px 36px rgba(0, 0, 0, 0.18);
  overflow: hidden;
}

.s-verify-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px;
  border-bottom: 1px solid #f2f3f5;
}

.s-verify-title {
  font-size: 16px;
  font-weight: 600;
  color: #1f2d3d;
}

.s-verify-close {
  width: 24px;
  height: 24px;
  line-height: 24px;
  text-align: center;
  border-radius: 50%;
  color: #999999;
  font-size: 18px;
}

.s-verify-body {
  padding: 16px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
}

.s-verify-image {
  position: relative;
  border-radius: 12px;
  overflow: hidden;
  background: #f5f5f5;
}

.s-verify-image-bg {
  width: 100%;
  height: 100%;
  display: block;
}

.s-verify-image-block {
  position: absolute;
  top: 0;
  display: block;
}

.s-verify-refresh {
  position: absolute;
  top: 10px;
  right: 10px;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.45);
  color: #ffffff;
  font-size: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.s-verify-loading {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.65);
  font-size: 14px;
  color: #666666;
}

.s-verify-tip {
  width: 100%;
  text-align: center;
  font-size: 14px;
  padding: 6px 0;
  border-radius: 6px;
}

.s-verify-tip-success {
  color: #52c41a;
  background: rgba(82, 196, 26, 0.12);
}

.s-verify-tip-error {
  color: #ff4d4f;
  background: rgba(255, 77, 79, 0.12);
}

.s-verify-slider {
  position: relative;
  width: 310px;
  height: 40px;
  border-radius: 20px;
  background: #f2f3f5;
  overflow: hidden;
}

.s-verify-slider-track {
  position: absolute;
  inset: 0;
  border-radius: 20px;
  background: #f2f3f5;
}

.s-verify-slider-fill {
  position: absolute;
  left: 0;
  top: 0;
  height: 100%;
  background: var(--ui-BG-Main, #409eff);
  border-radius: 20px;
  transition: width 0.05s linear;
}

.s-verify-slider-handle {
  position: absolute;
  top: 0;
  background: #ffffff;
  border-radius: 20px;
  box-shadow: 0 6px 14px rgba(25, 87, 170, 0.25);
  display: flex;
  align-items: center;
  justify-content: center;
  color: #1f2d3d;
  transition: transform 0.05s linear;
}

.s-verify-slider-success {
  background: #52c41a;
  color: #ffffff;
}

.s-verify-slider-fail {
  animation: s-verify-shake 0.3s;
}

.s-verify-handle-icon {
  font-size: 22px;
  font-weight: 600;
}

.s-verify-slider-text {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #8c8c8c;
  font-size: 14px;
}

@keyframes s-verify-shake {
  0% {
    transform: translateX(-4px);
  }
  25% {
    transform: translateX(4px);
  }
  50% {
    transform: translateX(-2px);
  }
  75% {
    transform: translateX(2px);
  }
  100% {
    transform: translateX(0);
  }
}
</style>
