<template>
  <div style="position: relative">
    <div v-if="type === '2'" class="verify-img-out" :style="{ height: parseInt(setSize.imgHeight) + vSpace + 'px' }">
      <div class="verify-img-panel" :style="{ width: setSize.imgWidth, height: setSize.imgHeight }">
        <img
          :src="'data:image/png;base64,' + backImgBase"
          alt="验证背景图"
          style="width: 100%; height: 100%; display: block"
        />
        <div class="verify-refresh" @click="refresh" v-show="showRefresh">
          <i class="iconfont icon-refresh"></i>
        </div>
        <transition name="tips">
          <span class="verify-tips" v-if="tipWords" :class="passFlag ? 'suc-bg' : 'err-bg'">{{ tipWords }}</span>
        </transition>
      </div>
    </div>
    <!-- 公共部分 -->
    <div
      class="verify-bar-area"
      :style="{
        width: setSize.imgWidth,
        height: barSize.height,
        'line-height': barSize.height,
      }"
    >
      <span class="verify-msg" v-text="text"></span>
      <div
        class="verify-left-bar"
        :style="{
          width: leftBarWidth !== undefined ? leftBarWidth : barSize.height,
          height: barSize.height,
          'border-color': leftBarBorderColor,
          transition: transitionWidth, // 原单词拼写错误：transaction -> transition
        }"
      >
        <span class="verify-msg" v-text="finishText"></span>
        <div
          class="verify-move-block"
          @touchstart="start"
          @mousedown="start"
          :style="{
            width: barSize.height,
            height: barSize.height,
            'background-color': moveBlockBackgroundColor,
            left: moveBlockLeft,
            transition: transitionLeft,
          }"
        >
          <i :class="['verify-icon iconfont', iconClass]" :style="{ color: iconColor }"></i>
          <div
            v-if="type === '2'"
            class="verify-sub-block"
            :style="{
              width: Math.floor((parseInt(setSize.imgWidth) * 47) / 310) + 'px',
              height: setSize.imgHeight,
              top: '-' + (parseInt(setSize.imgHeight) + vSpace) + 'px',
              'background-size': setSize.imgWidth + ' ' + setSize.imgHeight,
            }"
          >
            <img
              :src="'data:image/png;base64,' + blockBackImgBase"
              alt="滑块背景图"
              style="width: 100%; height: 100%; display: block; -webkit-user-drag: none"
            />
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
  import { ref, reactive, computed, onMounted, nextTick, watch } from 'vue';
  import { aesEncrypt } from './utils/aes';
  import { resetSize } from './utils/util';
  import { reqGet, reqCheck } from './utils/api';

  // -------------------------- 1. 定义 Props 类型及默认值 --------------------------
  interface ImgSize {
    width: string;
    height: string;
  }

  interface BlockSize {
    width: string;
    height: string;
  }

  interface BarSize {
    width: string;
    height: string;
  }

  const props = withDefaults(
    defineProps<{
      captchaType: string;
      type: string;
      mode: string;
      vSpace: number;
      explain: string;
      imgSize: ImgSize;
      blockSize: BlockSize;
      barSize: BarSize;
    }>(),
    {
      type: '1',
      mode: 'fixed',
      vSpace: 5,
      explain: '向右滑动完成验证',
      imgSize: () => ({
        width: '360px',
        height: '180px',
      }),
      blockSize: () => ({
        width: '50px',
        height: '50px',
      }),
      barSize: () => ({
        width: '310px',
        height: '40px',
      }),
    },
  );

  // -------------------------- 2. 定义 Emits 事件 --------------------------
  const emit = defineEmits<{
    (e: 'success', data: { captchaVerification: string }): void;
    (e: 'error', instance: HTMLElement): void;
  }>();

  // -------------------------- 3. 响应式数据定义 --------------------------
  // 后端返回相关
  const secretKey = ref(''); // ase加密秘钥
  const backImgBase = ref(''); // 验证码背景图片base64
  const blockBackImgBase = ref(''); // 滑块背景图片base64
  const backToken = ref(''); // 唯一token
  // 验证状态相关
  const passFlag = ref(false); // 是否验证通过
  const isEnd = ref(false); // 验证是否完成
  const status = ref(false); // 鼠标/触摸是否按下
  const showRefresh = ref(true); // 是否显示刷新按钮
  // 时间相关
  const startMoveTime = ref(0); // 移动开始时间
  const endMovetime = ref(0); // 移动结束时间
  // 文本相关
  const tipWords = ref(''); // 提示文字
  const text = ref(props.explain); // 滑动提示文字
  const finishText = ref(''); // 完成提示文字
  // 样式相关
  const setSize = reactive({
    imgHeight: 0,
    imgWidth: 0,
    barHeight: 0,
    barWidth: 0,
  });
  const moveBlockLeft = ref<string | undefined>(undefined); // 滑块左侧距离
  const leftBarWidth = ref<string | undefined>(undefined); // 左侧进度条宽度
  const moveBlockBackgroundColor = ref<string | undefined>(undefined); // 滑块背景色
  const leftBarBorderColor = ref('#ddd'); // 左侧进度条边框色
  const iconColor = ref<string | undefined>(undefined); // 图标颜色
  const iconClass = ref('icon-right'); // 图标类名
  const transitionLeft = ref(''); // 滑块过渡样式
  const transitionWidth = ref(''); // 进度条过渡样式
  const startLeft = ref(0); // 滑动起始偏移量

  // -------------------------- 4. 计算属性 --------------------------
  // 获取滑块容器元素
  const barArea = computed(() => {
    const el = document.querySelector('.verify-bar-area');
    return el as HTMLElement | null;
  });

  // -------------------------- 5. 核心方法 --------------------------
  /**
   * 初始化组件
   */
  const init = () => {
    text.value = props.explain;
    getPictrue(); // 请求验证码图片

    nextTick(() => {
      // @ts-ignore 兼容原 resetSize 方法（需确保 resetSize 支持传入组件实例）
      const sizeInfo = resetSize({ $el: document.querySelector('.verify-img-out')?.parentElement });
      if (sizeInfo) {
        setSize.imgHeight = sizeInfo.imgHeight;
        setSize.imgWidth = sizeInfo.imgWidth;
        setSize.barHeight = sizeInfo.barHeight;
        setSize.barWidth = sizeInfo.barWidth;
      }
      // 移除原有事件监听（防止重复绑定）
      window.removeEventListener('touchmove', handleMove);
      window.removeEventListener('mousemove', handleMove);
      window.removeEventListener('touchend', handleEnd);
      window.removeEventListener('mouseup', handleEnd);
      // 重新绑定事件
      window.addEventListener('touchmove', handleMove);
      window.addEventListener('mousemove', handleMove);
      window.addEventListener('touchend', handleEnd);
      window.addEventListener('mouseup', handleEnd);
    });

    // 禁止元素选中
    const rootEl = document.querySelector('.verify-img-out')?.parentElement;
    if (rootEl) {
      rootEl.onselectstart = () => false;
    }
  };

  /**
   * 鼠标/触摸按下事件（开始滑动）
   */
  const start = (e: MouseEvent | TouchEvent) => {
    e = e || window.event;
    let x = 0;

    // 兼容PC端和移动端
    if ('touches' in e && e.touches.length > 0) {
      x = e.touches[0].pageX; // 移动端
    } else {
      x = (e as MouseEvent).clientX; // PC端
    }

    // 修正：计算鼠标按下点相对滑块左侧的偏移（而非拖动条）
    // 先获取滑块当前位置（未滑动时为0）
    const currentLeft = moveBlockLeft.value ? parseInt(moveBlockLeft.value) : 0;
    startLeft.value = x - (barArea.value?.getBoundingClientRect().left || 0) - currentLeft;

    startMoveTime.value = +new Date(); // 记录开始时间

    if (!isEnd.value) {
      text.value = '';
      moveBlockBackgroundColor.value = '#337ab7';
      leftBarBorderColor.value = '#337AB7';
      iconColor.value = '#fff';
      e.stopPropagation();
      e.preventDefault(); // 新增：禁止默认行为（避免滑动时选中文本）
      status.value = true;
    }
  };

  /**
   * 鼠标/触摸移动事件（滑动中）
   */
  const handleMove = (e: MouseEvent | TouchEvent) => {
    e = e || window.event;
    if (!status.value || isEnd.value) return;

    let x = 0;
    // 兼容PC端和移动端
    if ('touches' in e && e.touches.length > 0) {
      x = e.touches[0].pageX; // 移动端
    } else {
      x = (e as MouseEvent).clientX; // PC端
    }

    if (!barArea.value) return;
    const bar_area_rect = barArea.value.getBoundingClientRect();
    const bar_area_left = bar_area_rect.left;
    const bar_width = bar_area_rect.width; // 拖动条实际宽度（适配响应式）
    const block_width = parseInt(props.blockSize.width); // 滑块宽度

    // 1. 计算滑块绝对左偏移（相对拖动条左侧）
    let move_block_left = x - bar_area_left - startLeft.value;

    // 2. 正确的边界限制：0 ≤ 偏移 ≤ 拖动条宽度 - 滑块宽度
    const minLeft = 0; // 最小偏移（滑块左边缘对齐拖动条）
    const maxLeft = bar_width - block_width; // 最大偏移（滑块右边缘对齐拖动条）

    // 限制偏移在合法范围
    move_block_left = Math.max(minLeft, Math.min(move_block_left, maxLeft));

    // 3. 更新滑块和进度条位置（直接用绝对偏移，无多余计算）
    moveBlockLeft.value = move_block_left + 'px';
    leftBarWidth.value = move_block_left + 'px';
  };

  /**
   * 鼠标/触摸松开事件（滑动结束，验证判断）
   */
  const handleEnd = () => {
    endMovetime.value = +new Date();
    if (!status.value || isEnd.value) return;

    // 计算滑动距离（适配不同尺寸）
    let moveLeftDistance = 0;
    if (moveBlockLeft.value) {
      moveLeftDistance = parseInt(moveBlockLeft.value.replace('px', ''));
    }
    moveLeftDistance = (moveLeftDistance * 310) / parseInt(setSize.imgWidth);

    // 构造验证参数
    const checkData = {
      captchaType: props.captchaType,
      pointJson: secretKey.value
        ? aesEncrypt(JSON.stringify({ x: moveLeftDistance, y: 5.0 }), secretKey.value)
        : JSON.stringify({ x: moveLeftDistance, y: 5.0 }),
      token: backToken.value,
    };

    // 发起验证请求
    reqCheck(checkData).then((res: any) => {
      if (res.repCode === '0000') {
        // 验证成功
        moveBlockBackgroundColor.value = '#5cb85c';
        leftBarBorderColor.value = '#5cb85c';
        iconColor.value = '#fff';
        iconClass.value = 'icon-check';
        showRefresh.value = false;
        isEnd.value = true;
        passFlag.value = true;
        tipWords.value = `${((endMovetime.value - startMoveTime.value) / 1000).toFixed(2)}s验证成功`;

        // 构造验证成功返回参数
        const captchaVerification = secretKey.value
          ? aesEncrypt(backToken.value + '---' + JSON.stringify({ x: moveLeftDistance, y: 5.0 }), secretKey.value)
          : backToken.value + '---' + JSON.stringify({ x: moveLeftDistance, y: 5.0 });

        // 弹窗模式下自动关闭并刷新
        if (props.mode === 'pop') {
          setTimeout(() => {
            refresh();
          }, 1500);
        }

        // 延迟触发成功事件并清空提示
        setTimeout(() => {
          tipWords.value = '';
          emit('success', { captchaVerification });
        }, 1000);
      } else {
        // 验证失败
        moveBlockBackgroundColor.value = '#d9534f';
        leftBarBorderColor.value = '#d9534f';
        iconColor.value = '#fff';
        iconClass.value = 'icon-close';
        passFlag.value = false;
        tipWords.value = '验证失败';
        emit('error', document.querySelector('.verify-img-out') as HTMLElement);

        // 延迟刷新组件并清空提示
        setTimeout(() => {
          refresh();
          tipWords.value = '';
        }, 1000);
      }
    });

    status.value = false;
  };

  /**
   * 刷新验证码（重置状态+重新请求图片）
   */
  const refresh = () => {
    showRefresh.value = true;
    finishText.value = '';
    transitionLeft.value = 'left .3s';
    moveBlockLeft.value = undefined;
    leftBarWidth.value = undefined;
    transitionWidth.value = 'width .3s';
    leftBarBorderColor.value = '#ddd';
    moveBlockBackgroundColor.value = '#fff';
    iconColor.value = '#000';
    iconClass.value = 'icon-right';
    isEnd.value = false;

    // 重新请求验证码图片
    getPictrue();

    // 延迟重置过渡样式和提示文字
    setTimeout(() => {
      transitionWidth.value = '';
      transitionLeft.value = '';
      text.value = props.explain;
    }, 300);
  };

  /**
   * 请求验证码背景图和滑块图
   */
  const getPictrue = () => {
    const reqData = {
      captchaType: props.captchaType,
    };

    reqGet(reqData).then((res: any) => {
      if (res.repCode === '0000') {
        backImgBase.value = res.repData.originalImageBase64;
        blockBackImgBase.value = res.repData.jigsawImageBase64;
        backToken.value = res.repData.token;
        secretKey.value = res.repData.secretKey;
      } else {
        tipWords.value = res.repMsg || '获取验证码失败';
      }
    });
  };

  // -------------------------- 6. 生命周期 & 监听 --------------------------
  // 监听 type 变化，重新初始化
  watch(
    () => props.type,
    () => {
      init();
    },
  );

  // 组件挂载后初始化
  onMounted(() => {
    init();
  });

  // -------------------------- 7. 暴露模板使用的变量/方法 --------------------------
  defineExpose({
    refresh, // 暴露刷新方法供父组件调用
  });
</script>

<style scoped>
  /* 可保留原有样式，此处仅为占位 */
  .verify-img-out {
    position: relative;
    margin-bottom: 8px;
  }
  .verify-img-panel {
    position: relative;
    overflow: hidden;
    border-radius: 4px;
    background: #f5f5f5;
  }
  .verify-refresh {
    position: absolute;
    top: 8px;
    right: 8px;
    width: 30px;
    height: 30px;
    line-height: 30px;
    text-align: center;
    cursor: pointer;
    background: rgba(255, 255, 255, 0.8);
    border-radius: 50%;
    z-index: 10;
  }
  .verify-tips {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    padding: 4px 12px;
    border-radius: 4px;
    color: #fff;
    font-size: 14px;
    z-index: 10;
  }
  .suc-bg {
    background: #5cb85c;
  }
  .err-bg {
    background: #d9534f;
  }
  .verify-bar-area {
    position: relative;
    background: #f8f8f8;
    border: 1px solid #ddd;
    border-radius: 4px;
    overflow: hidden;
    text-align: center;
    font-size: 14px;
    color: #666;
  }
  .verify-left-bar {
    position: absolute;
    top: 0;
    left: 0;
    background: #e6f4ff;
    border-right: 1px solid #337ab7;
    box-sizing: border-box;
    z-index: 5;
  }
  .verify-move-block {
    position: absolute;
    top: 0;
    cursor: pointer;
    background: #fff;
    border: 1px solid #ddd;
    box-sizing: border-box;
    z-index: 10;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .verify-icon {
    font-size: 18px;
  }
  .verify-sub-block {
    position: absolute;
    left: 0;
    overflow: hidden;
    z-index: 10;
  }
  .tips-enter-active,
  .tips-leave-active {
    transition: opacity 0.3s ease;
  }
  .tips-enter-from,
  .tips-leave-to {
    opacity: 0;
  }
</style>
