<template>
  <div class="vertical-slider-container" :class="customClass">
    <div
      class="slider-track"
      ref="sliderTrack"
      @mousedown="startSliding"
      @touchstart.prevent="startTouchSliding"
      @click="handleClick"
    >
      <div class="slider-fill" :style="{ height: `${displayPercentage}%` }"></div>
      <div
        class="slider-thumb"
        :style="{
          bottom: `${displayPercentage}%`,
          transform: `translateX(0) translateY(8px) scale(${thumbScale})`,
        }"
      ></div>
      <div
        v-for="(preset, index) in presets"
        :key="`preset-${index}`"
        class="slider-notch"
        :class="{ active: isActivePreset(preset) }"
        :style="{ bottom: `${calculatePresetPosition(preset)}%` }"
        @click.stop="setValue(preset)"
      ></div>

      <div
        v-for="(preset, index) in memorizedValues"
        :key="`preset-${index}`"
        class="slider-notch"
        :class="{ active: isActivePreset(preset) }"
        :style="{ bottom: `${calculatePresetPosition(preset)}%` }"
        @click.stop="setValue(preset)"
      ></div>
    </div>

    <!-- 提示框 -->
    <transition name="fade">
      <div class="slider-tooltip" v-if="tooltipVisible" ref="tooltip">
        <slot name="tooltip-content" :value="modelValue"></slot>
      </div>
    </transition>
  </div>
</template>

<script setup>
import { ref, computed, onMounted, onBeforeUnmount, watch } from "vue";

const props = defineProps({
  modelValue: {
    type: Number,
    required: true,
  },
  min: {
    type: Number,
    default: 0,
  },
  max: {
    type: Number,
    default: 100,
  },
  presets: {
    type: Array,
    default: () => [],
  },
  memorizedValues: {
    type: Array,
    default: () => [],
  },
  snapThreshold: {
    type: Number,
    default: 5,
  },
  // 用于判断当前值与预设值是否匹配的阈值
  activeThreshold: {
    type: Number,
    default: 0.05,
  },
  // 是否将预设值位置按百分比计算
  isPercentage: {
    type: Boolean,
    default: false,
  },
  customClass: {
    type: String,
    default: "",
  },
  // 添加步进属性，控制数值的步长
  step: {
    type: Number,
    default: 1,
  },
  // 增加外部控制tooltip显示的属性
  showTooltip: {
    type: Boolean,
    default: false,
  },
  // 是否启用点击外部关闭tooltip
  closeOnOutsideClick: {
    type: Boolean,
    default: true,
  },
});

const emit = defineEmits([
  "update:modelValue",
  "slide-start",
  "slide-end",
  "click",
  "update:showTooltip", // 新增emit事件用于双向绑定tooltip状态
]);

const sliderTrack = ref(null);
const tooltip = ref(null);
const isSliding = ref(false);
const internalShowTooltip = ref(false);
const slideMoved = ref(false); // 用于跟踪是否发生了滑动移动
const hideTooltipTimer = ref(null); // 用于存储隐藏tooltip的计时器

// 在script setup顶部添加新的ref
const isSnapping = ref(false);
const snapLockValue = ref(null);
const snapLockTimeout = ref(null);
const thumbScale = ref(1); // 新增：用于控制滑块的缩放效果

// 定义一个统一的延迟时间常量
const TOOLTIP_HIDE_DELAY = 1500; // 1.5秒，可根据需要调整

// 开始新的隐藏tooltip计时器
function startHideTooltipTimer() {
  // 先清除已有的计时器
  clearHideTooltipTimer();

  // 创建新计时器
  hideTooltipTimer.value = setTimeout(() => {
    // 只有在用户不在滑动时才隐藏tooltip
    if (!isSliding.value) {
      updateTooltipVisibility(false);
    }
    hideTooltipTimer.value = null;
  }, TOOLTIP_HIDE_DELAY);
}

// 清除隐藏tooltip的计时器
function clearHideTooltipTimer() {
  if (hideTooltipTimer.value) {
    clearTimeout(hideTooltipTimer.value);
    hideTooltipTimer.value = null;
  }
}

// 计算tooltip的可见性，优先使用props中的showTooltip，否则使用内部状态
const tooltipVisible = computed(() => {
  return props.showTooltip !== undefined ? props.showTooltip : internalShowTooltip.value;
});

// 更新tooltip状态的方法
function updateTooltipVisibility(visible) {
  if (props.showTooltip !== undefined) {
    // 如果父组件提供了showTooltip属性，通过emit更新
    emit("update:showTooltip", visible);
  } else {
    // 否则更新内部状态
    internalShowTooltip.value = visible;
  }
}

// 计算显示百分比
const displayPercentage = computed(() => {
  const range = props.max - props.min;
  return ((props.modelValue - props.min) / range) * 100;
});

// 判断是否是活动预设值
function isActivePreset(preset) {
  return Math.abs(props.modelValue - preset) < props.activeThreshold;
}

// 计算预设值的位置
function calculatePresetPosition(preset) {
  if (props.isPercentage) {
    return preset * 100;
  } else {
    const range = props.max - props.min;
    return ((preset - props.min) / range) * 100;
  }
}

// 将值舍入到最接近的步进值
function roundToStep(value) {
  if (!props.step || props.step <= 0) return value;

  const numSteps = Math.round((value - props.min) / props.step);
  return props.min + numSteps * props.step;
}

// 鼠标滑动处理
function startSliding(event) {
  isSliding.value = true;
  slideMoved.value = false; // 重置滑动状态
  updateTooltipVisibility(true);
  clearHideTooltipTimer(); // 清除任何现有的隐藏计时器
  updateValueFromMousePosition(event);

  emit("slide-start");

  window.addEventListener("mousemove", handleMouseMove);
  window.addEventListener("mouseup", stopSliding);
}

function handleMouseMove(event) {
  if (isSliding.value) {
    slideMoved.value = true; // 标记已经发生了滑动
    updateValueFromMousePosition(event);
  }
}

function updateValueFromMousePosition(event) {
  const rect = sliderTrack.value.getBoundingClientRect();
  const trackHeight = rect.height;
  const posY = event.clientY - rect.top;

  // 计算相对位置并转换为min-max范围的值
  let newValue = props.max - (posY / trackHeight) * (props.max - props.min);

  // 边界检查
  newValue = Math.max(props.min, Math.min(props.max, newValue));

  // 应用步进
  if (props.step > 0) {
    newValue = roundToStep(newValue);
  }

  // 如果当前处于吸附锁定状态，并且移动不超过锁定阈值，则保持当前值不变
  if (isSnapping.value && snapLockValue.value !== null) {
    // 增加吸附力度：扩大保持吸附的阈值范围
    if (
      Math.abs(newValue - snapLockValue.value) <
      (props.snapThreshold / trackHeight) * (props.max - props.min) * 1.2
    ) {
      newValue = snapLockValue.value;
    } else {
      // 移动距离超过阈值，解除吸附锁定
      clearSnapLock();
    }
  } else {
    // 检查是否可以进入吸附状态
    const snapPercentage = (props.snapThreshold / trackHeight) * (props.max - props.min);

    // 检查是否接近预设值，增加吸附判定范围
    for (const preset of props.presets) {
      if (Math.abs(newValue - preset) < snapPercentage * 0.4) {
        // 增加判定范围
        // 进入吸附状态
        setSnapLock(preset);
        newValue = preset;
        break;
      }
    }

    // 检查是否接近记忆值
    if (!isSnapping.value) {
      for (const memValue of props.memorizedValues) {
        if (Math.abs(newValue - memValue) < snapPercentage * 0.4) {
          // 增加判定范围
          // 进入吸附状态
          setSnapLock(memValue);
          newValue = memValue;
          break;
        }
      }
    }
  }

  setValue(newValue);
}

// 设置吸附锁定
function setSnapLock(value) {
  if (!isSnapping.value) {
    isSnapping.value = true;
    snapLockValue.value = value;

    // 添加视觉反馈：放大滑块
    thumbScale.value = 1.3;

    // 触感反馈：在支持的设备上触发振动
    if (navigator.vibrate) {
      navigator.vibrate(15); // 短暂振动15ms
    }

    // 延长锁定时间，增强吸附感
    clearTimeout(snapLockTimeout.value);
    snapLockTimeout.value = setTimeout(() => {
      // 恢复滑块大小
      thumbScale.value = 1;
      clearSnapLock();
    }, 500); // 500ms的吸附感觉，比原来的300ms更强
  }
}

// 清除吸附锁定
function clearSnapLock() {
  isSnapping.value = false;
  snapLockValue.value = null;
  clearTimeout(snapLockTimeout.value);
  snapLockTimeout.value = null;
  thumbScale.value = 1; // 确保滑块恢复正常大小
}

function setValue(value) {
  // 应用步进，确保即使通过点击预设值也会遵循步进
  if (props.step > 0) {
    value = roundToStep(value);
  }
  emit("update:modelValue", value);
}

function stopSliding() {
  // 清除吸附锁定
  clearSnapLock();

  if (isSliding.value) {
    // 如果确实进行了滑动，则发送滑动结束事件
    if (slideMoved.value) {
      emit("slide-end", { isSlide: true });

      // 只有在滑动操作后才启动隐藏计时器
      startHideTooltipTimer();
    } else {
      // 只是点击，不是滑动，不自动隐藏tooltip
      emit("slide-end", { isSlide: false });
    }
  }

  isSliding.value = false;
  window.removeEventListener("mousemove", handleMouseMove);
  window.removeEventListener("mouseup", stopSliding);
}

// 处理点击事件
function handleClick(event) {
  // 对于纯点击操作，发出click事件
  if (!slideMoved.value) {
    clearHideTooltipTimer(); // 清除任何现有的隐藏计时器
    updateTooltipVisibility(true); // 确保tooltip在点击时显示
    emit("click");
    // 点击不启动隐藏计时器
  }
}

// 触摸事件处理
function startTouchSliding(event) {
  isSliding.value = true;
  slideMoved.value = false; // 重置滑动状态
  updateTooltipVisibility(true);
  updateValueFromTouchPosition(event);

  // 滑动开始的触感反馈
  if (navigator.vibrate) {
    navigator.vibrate(10); // 更轻微的振动反馈
  }

  emit("slide-start");

  window.addEventListener("touchmove", handleTouchMove, { passive: false });
  window.addEventListener("touchend", stopTouchSliding);
}

function handleTouchMove(event) {
  if (isSliding.value) {
    slideMoved.value = true; // 标记已经发生了滑动
    event.preventDefault(); // 阻止页面滚动
    updateValueFromTouchPosition(event);
  }
}

function updateValueFromTouchPosition(event) {
  if (!event.touches || event.touches.length === 0) return;

  const touch = event.touches[0];
  const rect = sliderTrack.value.getBoundingClientRect();
  const trackHeight = rect.height;
  const posY = touch.clientY - rect.top;

  // 计算相对位置并转换为min-max范围的值
  let newValue = props.max - (posY / trackHeight) * (props.max - props.min);

  // 边界检查
  newValue = Math.max(props.min, Math.min(props.max, newValue));

  // 应用步进
  if (props.step > 0) {
    newValue = roundToStep(newValue);
  }

  // 如果当前处于吸附锁定状态，并且移动不超过锁定阈值，则保持当前值不变
  if (isSnapping.value && snapLockValue.value !== null) {
    // 增加吸附力度：扩大保持吸附的阈值范围
    if (
      Math.abs(newValue - snapLockValue.value) <
      (props.snapThreshold / trackHeight) * (props.max - props.min) * 1.5
    ) {
      newValue = snapLockValue.value;
    } else {
      // 移动距离超过阈值，解除吸附锁定，并提供反馈
      clearSnapLock();
      if (navigator.vibrate) {
        navigator.vibrate(8); // 解除吸附的轻微振动
      }
    }
  } else {
    // 检查是否可以进入吸附状态
    const snapPercentage = (props.snapThreshold / trackHeight) * (props.max - props.min);

    // 检查是否接近预设值，增加吸附判定范围
    for (const preset of props.presets) {
      if (Math.abs(newValue - preset) < snapPercentage * 0.4) {
        // 增加判定范围
        // 进入吸附状态
        setSnapLock(preset);
        newValue = preset;
        break;
      }
    }

    // 检查是否接近记忆值
    if (!isSnapping.value) {
      for (const memValue of props.memorizedValues) {
        if (Math.abs(newValue - memValue) < snapPercentage * 0.4) {
          // 增加判定范围
          // 进入吸附状态
          setSnapLock(memValue);
          newValue = memValue;
          break;
        }
      }
    }
  }

  setValue(newValue);
}

function stopTouchSliding() {
  // 滑动结束时的反馈
  if (slideMoved.value && navigator.vibrate) {
    navigator.vibrate(12); // 滑动结束的振动反馈
  }

  // 清除吸附锁定
  clearSnapLock();

  if (isSliding.value) {
    // 如果确实进行了滑动，则发送滑动结束事件
    if (slideMoved.value) {
      emit("slide-end", { isSlide: true });

      // 只有在滑动操作后才启动隐藏计时器
      startHideTooltipTimer();
    } else {
      // 只是点击，不是滑动，不自动隐藏tooltip
      emit("slide-end", { isSlide: false });
    }
  }

  isSliding.value = false;
  window.removeEventListener("touchmove", handleTouchMove);
  window.removeEventListener("touchend", stopTouchSliding);
}

// 添加点击外部关闭tooltip的处理函数
function handleOutsideClick(event) {
  if (!props.closeOnOutsideClick || !tooltipVisible.value) return;

  // 如果正在滑动，不处理外部点击事件
  if (isSliding.value) return;

  // 检查点击是否在slider组件外部
  const containerEl = sliderTrack.value?.parentElement;
  const tooltipEl = tooltip.value;

  if (
    containerEl &&
    tooltipEl &&
    !containerEl.contains(event.target) &&
    !tooltipEl.contains(event.target)
  ) {
    updateTooltipVisibility(false);
  }
}

onMounted(() => {
  // 添加全局点击事件监听
  if (props.closeOnOutsideClick) {
    // 使用 setTimeout 确保点击事件在其他处理程序之后执行
    window.addEventListener("click", (e) => setTimeout(() => handleOutsideClick(e), 0));
  }
});

onBeforeUnmount(() => {
  // 清理事件监听器
  window.removeEventListener("mousemove", handleMouseMove);
  window.removeEventListener("mouseup", stopSliding);
  window.removeEventListener("touchmove", handleTouchMove);
  window.removeEventListener("touchend", stopTouchSliding);
  window.removeEventListener("click", handleOutsideClick);

  // 清除任何剩余的计时器
  clearHideTooltipTimer();
  clearSnapLock();
});
</script>

<style scoped lang="less">
.vertical-slider-container {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1.5rem;
  height: 15rem;
  position: relative;
  //   margin-top: 8px;
  //   margin-bottom: 0px;
}

.slider-track {
  position: relative;
  width: 3.2rem;
  height: 100%;
  background: rgba(0, 0, 0, 0.1);
  border-radius: 4px;
  cursor: pointer;
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1);
}

.slider-fill {
  position: absolute;
  bottom: 0;
  width: 100%;
  //   background: linear-gradient(to top, #2196f3, #64b5f6);
  border-radius: 4px;
}

.slider-thumb {
  position: absolute;
  width: 100%;
  height: 1.6rem;
  background: #fff;
  //   border: 1px solid #2196f3;
  border-radius: 3px;
  cursor: grab;
  box-shadow: 0 0px 4px rgba(0, 0, 0, 0.4);
  transition: transform 0.15s cubic-bezier(0.175, 0.885, 0.32, 1.275); // 更平滑的动画效果

  &:active {
    cursor: grabbing;
  }
}

// 添加iPad和移动设备的专有样式
@media (pointer: coarse) {
  .slider-thumb {
    height: 2rem; // 在触摸设备上增加滑块尺寸，更容易点击
    border-radius: .4rem;
    box-shadow: 0 0 .6rem rgba(0, 0, 0, 0.5); // 更明显的阴影
  }

  .slider-track {
    width: 4rem; // 在触摸设备上增加宽度
  }

  .slider-notch {
    width: 60%; // 增加刻度线宽度
    height: 3px; // 增加刻度线高度
  }
}

// 当设备支持悬停时的效果 (通常是桌面设备)
@media (hover: hover) {
  .slider-thumb:hover {
    box-shadow: 0 0 5px rgba(33, 150, 243, 0.4);
  }
}

.slider-notch {
  position: absolute;
  left: 0;
  width: 50%;
  height: 2px;
  background: #999;
  border-radius: 2px;
  transform: translateY(1px) translateX(50%);
  transition: all 0.2s ease;

  &.active {
    background: #2196f3;
  }

  &:hover {
    background: #333;
  }
}

.slider-tooltip {
  position: absolute;
  left: calc(100% + 1.5rem);
  background: rgba(255, 255, 255, 0.95);
  border-radius: 10px;
  padding: 1rem;
  box-shadow:
    0 3px 12px rgba(0, 0, 0, 0.1),
    0 0 0 1px rgba(0, 0, 0, 0.05);
  min-width: 12rem;
  z-index: 10;

  &::before {
    content: "";
    position: absolute;
    left: -.8rem;
    top: 50%;
    transform: translateY(-50%);
    border-width: .8rem .8rem .8rem 0;
    border-style: solid;
    border-color: transparent rgba(255, 255, 255, 0.95) transparent transparent;
  }
}

// 自定义滑块颜色
// .size-slider {
//   .slider-fill {
//     // background: linear-gradient(to top, #2196f3, #64b5f6);
//   }

//   .slider-thumb {
//     border-color: #2196f3;
//   }

//   .slider-notch.active {
//     background: #2196f3;
//   }
// }

// .opacity-slider {
//   .slider-fill {
//     // background: linear-gradient(to top, #ff9800, #ffb74d);
//   }

//   .slider-thumb {
//     border-color: #ff9800;
//   }

//   .slider-notch.active {
//     background: #ff9800;
//   }
// }

// 淡入淡出动画
.fade-enter-active,
.fade-leave-active {
  transition:
    opacity 0.3s,
    transform 0.3s;
}
.fade-enter-from,
.fade-leave-to {
  opacity: 0;
  transform: translateX(-10px);
}

// 响应式调整
@media (max-height: 600px) {
  .vertical-slider-container {
    height: 120px;
  }
}

@media (max-width: 768px) {
  .slider-tooltip {
    left: calc(100% + 10px);
    min-width: 100px;
  }
}
</style>
