<!-- 拖拽验证组件 -->
<template>
  <div
    ref="dragVerify"
    class="drag_verify"
    :style="dragVerifyStyle"
    @mousemove="dragMoving"
    @mouseup="dragFinish"
    @mouseleave="dragFinish"
    @touchmove="dragMoving"
    @touchend="dragFinish"
  >
    <!-- 进度条 -->
    <div
      class="dv_progress_bar"
      :class="{ goFirst2: isOk }"
      ref="progressBar"
      :style="progressBarStyle"
    >
    </div>

    <!-- 提示文本 -->
    <div class="dv_text" :style="textStyle" ref="messageRef">
      <slot name="textBefore" v-if="$slots.textBefore"></slot>
      {{ message }}
      <slot name="textAfter" v-if="$slots.textAfter"></slot>
    </div>

    <!-- 滑块处理器 -->
    <div
      class="dv_handler dv_handler_bg"
      :class="{ goFirst: isOk }"
      @mousedown="dragStart"
      @touchstart="dragStart"
      ref="handler"
      :style="handlerStyle"
    >
      <i class="iconfont-sys" v-html="value ? successIcon : handlerIcon"></i>
    </div>
  </div>
</template>

<script setup lang="ts">
  defineOptions({ name: 'ArtDragVerify' })

  // 事件定义
  const emit = defineEmits(['handlerMove', 'update:value', 'passCallback'])

  // 组件属性接口定义
  interface PropsType {
    /** 是否通过验证 */
    value: boolean
    /** 组件宽度 */
    width?: number | string
    /** 组件高度 */
    height?: number
    /** 默认提示文本 */
    text?: string
    /** 成功提示文本 */
    successText?: string
    /** 背景色 */
    background?: string
    /** 进度条背景色 */
    progressBarBg?: string
    /** 完成状态背景色 */
    completedBg?: string
    /** 是否圆角 */
    circle?: boolean
    /** 圆角大小 */
    radius?: string
    /** 滑块图标 */
    handlerIcon?: string
    /** 成功图标 */
    successIcon?: string
    /** 滑块背景色 */
    handlerBg?: string
    /** 文本大小 */
    textSize?: string
    /** 文本颜色 */
    textColor?: string
  }

  // 属性默认值设置
  const props = withDefaults(defineProps<PropsType>(), {
    value: false,
    width: '100%',
    height: 40,
    text: '按住滑块拖动',
    successText: 'success',
    background: '#eee',
    progressBarBg: '#1385FF',
    completedBg: '#57D187',
    circle: false,
    radius: 'calc(var(--custom-radius) / 3 + 2px)',
    handlerIcon: '&#xea50;',
    successIcon: '&#xe621;',
    handlerBg: '#fff',
    textSize: '13px',
    textColor: '#333'
  })

  // 组件状态接口定义
  interface stateType {
    isMoving: boolean // 是否正在拖拽
    x: number // 拖拽起始位置
    isOk: boolean // 是否验证成功
  }

  // 响应式状态定义
  const state = reactive(<stateType>{
    isMoving: false,
    x: 0,
    isOk: false
  })

  // 解构响应式状态
  const { isOk } = toRefs(state)

  // DOM 元素引用
  const dragVerify = ref()
  const messageRef = ref()
  const handler = ref()
  const progressBar = ref()

  // 触摸事件变量 - 用于禁止页面滑动
  let startX: number, startY: number, moveX: number, moveY: number

  /**
   * 触摸开始事件处理
   * @param e 触摸事件对象
   */
  const onTouchStart = (e: any) => {
    startX = e.targetTouches[0].pageX
    startY = e.targetTouches[0].pageY
  }

  /**
   * 触摸移动事件处理 - 判断是否为横向滑动，如果是则阻止默认行为
   * @param e 触摸事件对象
   */
  const onTouchMove = (e: any) => {
    moveX = e.targetTouches[0].pageX
    moveY = e.targetTouches[0].pageY

    // 如果横向移动距离大于纵向移动距离，阻止默认行为（防止页面滑动）
    if (Math.abs(moveX - startX) > Math.abs(moveY - startY)) {
      e.preventDefault()
    }
  }

  // 全局事件监听器添加
  document.addEventListener('touchstart', onTouchStart)
  document.addEventListener('touchmove', onTouchMove, { passive: false })

  // 获取数值形式的宽度
  const getNumericWidth = (): number => {
    if (typeof props.width === 'string') {
      // 如果是字符串，尝试从DOM元素获取实际宽度
      return dragVerify.value?.offsetWidth || 260
    }
    return props.width
  }

  // 获取样式字符串形式的宽度
  const getStyleWidth = (): string => {
    if (typeof props.width === 'string') {
      return props.width
    }
    return props.width + 'px'
  }

  // 组件挂载后的初始化
  onMounted(() => {
    // 设置 CSS 自定义属性
    dragVerify.value?.style.setProperty('--textColor', props.textColor)

    // 等待DOM更新后设置宽度相关属性
    nextTick(() => {
      const numericWidth = getNumericWidth()
      dragVerify.value?.style.setProperty('--width', Math.floor(numericWidth / 2) + 'px')
      dragVerify.value?.style.setProperty('--pwidth', -Math.floor(numericWidth / 2) + 'px')
    })

    // 重复添加事件监听器（确保事件绑定）
    document.addEventListener('touchstart', onTouchStart)
    document.addEventListener('touchmove', onTouchMove, { passive: false })
  })

  // 组件卸载前清理事件监听器
  onBeforeUnmount(() => {
    document.removeEventListener('touchstart', onTouchStart)
    document.removeEventListener('touchmove', onTouchMove)
  })

  // 滑块样式计算
  const handlerStyle = {
    left: '0',
    width: props.height + 'px',
    height: props.height + 'px',
    background: props.handlerBg
  }

  // 主容器样式计算
  const dragVerifyStyle = computed(() => ({
    width: getStyleWidth(),
    height: props.height + 'px',
    lineHeight: props.height + 'px',
    background: props.background,
    borderRadius: props.circle ? props.height / 2 + 'px' : props.radius
  }))

  // 进度条样式计算
  const progressBarStyle = {
    background: props.progressBarBg,
    height: props.height + 'px',
    borderRadius: props.circle
      ? props.height / 2 + 'px 0 0 ' + props.height / 2 + 'px'
      : props.radius
  }

  // 文本样式计算
  const textStyle = computed(() => ({
    height: props.height + 'px',
    width: getStyleWidth(),
    fontSize: props.textSize
  }))

  // 显示消息计算属性
  const message = computed(() => {
    return props.value ? props.successText : props.text
  })

  /**
   * 拖拽开始处理函数
   * @param e 鼠标或触摸事件对象
   */
  const dragStart = (e: any) => {
    if (!props.value) {
      state.isMoving = true
      handler.value.style.transition = 'none'
      // 计算拖拽起始位置
      state.x =
        (e.pageX || e.touches[0].pageX) - parseInt(handler.value.style.left.replace('px', ''), 10)
    }
    emit('handlerMove')
  }

  /**
   * 拖拽移动处理函数
   * @param e 鼠标或触摸事件对象
   */
  const dragMoving = (e: any) => {
    if (state.isMoving && !props.value) {
      const numericWidth = getNumericWidth()
      // 计算当前位置
      let _x = (e.pageX || e.touches[0].pageX) - state.x

      // 在有效范围内移动
      if (_x > 0 && _x <= numericWidth - props.height) {
        handler.value.style.left = _x + 'px'
        progressBar.value.style.width = _x + props.height / 2 + 'px'
      } else if (_x > numericWidth - props.height) {
        // 拖拽到末端，触发验证成功
        handler.value.style.left = numericWidth - props.height + 'px'
        progressBar.value.style.width = numericWidth - props.height / 2 + 'px'
        passVerify()
      }
    }
  }

  /**
   * 拖拽结束处理函数
   * @param e 鼠标或触摸事件对象
   */
  const dragFinish = (e: any) => {
    if (state.isMoving && !props.value) {
      const numericWidth = getNumericWidth()
      // 计算最终位置
      let _x = (e.pageX || e.changedTouches[0].pageX) - state.x

      if (_x < numericWidth - props.height) {
        // 未拖拽到末端，重置位置
        state.isOk = true
        handler.value.style.left = '0'
        handler.value.style.transition = 'all 0.2s'
        progressBar.value.style.width = '0'
        state.isOk = false
      } else {
        // 拖拽到末端，保持验证成功状态
        handler.value.style.transition = 'none'
        handler.value.style.left = numericWidth - props.height + 'px'
        progressBar.value.style.width = numericWidth - props.height / 2 + 'px'
        passVerify()
      }
      state.isMoving = false
    }
  }

  /**
   * 验证通过处理函数
   */
  const passVerify = () => {
    emit('update:value', true)
    state.isMoving = false
    // 更新样式为成功状态
    progressBar.value.style.background = props.completedBg
    messageRef.value.style['-webkit-text-fill-color'] = 'unset'
    messageRef.value.style.animation = 'slidetounlock2 3s infinite'
    messageRef.value.style.color = '#fff'
    emit('passCallback')
  }

  /**
   * 重置验证状态函数
   */
  const reset = () => {
    // 重置滑块位置
    handler.value.style.left = '0'
    progressBar.value.style.width = '0'
    // 重置图标
    handler.value.children[0].innerHTML = props.handlerIcon
    // 重置文本样式
    messageRef.value.style['-webkit-text-fill-color'] = 'transparent'
    messageRef.value.style.animation = 'slidetounlock 3s infinite'
    messageRef.value.style.color = props.background
    // 重置状态
    emit('update:value', false)
    state.isOk = false
    state.isMoving = false
    state.x = 0
  }

  // 暴露重置方法给父组件
  defineExpose({
    reset
  })
</script>

<style lang="scss" scoped>
  .drag_verify {
    position: relative;
    box-sizing: border-box;
    overflow: hidden;
    text-align: center;
    border: 1px solid var(--art-border-dashed-color);

    .dv_handler {
      position: absolute;
      top: 0;
      left: 0;
      display: flex;
      align-items: center;
      justify-content: center;
      cursor: move;

      i {
        padding-left: 0;
        font-size: 14px;
        color: #999;
      }

      .el-icon-circle-check {
        margin-top: 9px;
        color: #6c6;
      }
    }

    .dv_progress_bar {
      position: absolute;
      width: 0;
      height: 34px;
    }

    .dv_text {
      position: absolute;
      top: 0;
      color: transparent;
      user-select: none;
      background: linear-gradient(
        to right,
        var(--textColor) 0%,
        var(--textColor) 40%,
        #fff 50%,
        var(--textColor) 60%,
        var(--textColor) 100%
      );
      background-clip: text;
      background-clip: text;
      animation: slidetounlock 3s infinite;
      -webkit-text-fill-color: transparent;
      text-size-adjust: none;

      * {
        -webkit-text-fill-color: var(--textColor);
      }
    }
  }

  .goFirst {
    left: 0 !important;
    transition: left 0.5s;
  }

  .goFirst2 {
    width: 0 !important;
    transition: width 0.5s;
  }
</style>

<style lang="scss">
  @keyframes slidetounlock {
    0% {
      background-position: var(--pwidth) 0;
    }

    100% {
      background-position: var(--width) 0;
    }
  }

  @keyframes slidetounlock2 {
    0% {
      background-position: var(--pwidth) 0;
    }

    100% {
      background-position: var(--pwidth) 0;
    }
  }
</style>
