<script setup>
import { ref, computed, watch, onMounted, provide, toRefs } from "vue";
import { useNamespace, useFormItem } from "@yh-ui/hooks";
import { useComponentTheme } from "@yh-ui/theme";
import { useConfig } from "@yh-ui/hooks";
import { sliderProps, sliderEmits } from "./slider";
import SliderButton from "./slider-button.vue";
import YhInputNumber from "../../input-number/src/input-number.vue";
defineOptions({
  name: "YhSlider"
});
const props = defineProps(sliderProps);
const { vertical } = toRefs(props);
const emit = defineEmits(sliderEmits);
const ns = useNamespace("slider");
const { form, formItem, validate: triggerValidate } = useFormItem();
const { themeStyle } = useComponentTheme(
  "slider",
  computed(() => props.themeOverrides)
);
const { globalSize } = useConfig();
const sliderRef = ref();
const firstValue = ref(0);
const secondValue = ref(0);
const mergedDisabled = computed(() => props.disabled || form?.disabled || false);
const mergedSize = computed(() => {
  const size = props.size || formItem?.size || form?.size || globalSize.value || "default";
  return size === "" ? "default" : size;
});
provide("slider", {
  sliderRef,
  props,
  emit
});
const initValues = () => {
  if (props.range && Array.isArray(props.modelValue)) {
    firstValue.value = Math.max(props.min, Math.min(props.max, props.modelValue[0] || 0));
    secondValue.value = Math.max(props.min, Math.min(props.max, props.modelValue[1] || 0));
  } else {
    const val = Array.isArray(props.modelValue) ? props.modelValue[0] : props.modelValue;
    firstValue.value = Math.max(props.min, Math.min(props.max, Number(val) || 0));
    secondValue.value = props.min;
  }
};
watch(
  () => props.modelValue,
  () => {
    initValues();
  },
  { deep: true }
);
initValues();
const minValue = computed(
  () => props.range ? Math.min(firstValue.value, secondValue.value) : props.min
);
const maxValue = computed(
  () => props.range ? Math.max(firstValue.value, secondValue.value) : firstValue.value
);
const barSize = computed(() => {
  return props.range ? `${100 * (maxValue.value - minValue.value) / (props.max - props.min)}%` : `${100 * (firstValue.value - props.min) / (props.max - props.min)}%`;
});
const barStart = computed(() => {
  return props.range ? `${100 * (minValue.value - props.min) / (props.max - props.min)}%` : "0%";
});
const barStyle = computed(() => {
  const style = props.vertical ? {
    height: barSize.value,
    bottom: barStart.value,
    top: "auto"
  } : {
    width: barSize.value,
    left: barStart.value,
    right: "auto"
  };
  if (props.color) {
    style.background = props.color;
  }
  return style;
});
const stops = computed(() => {
  if (!props.showStops || props.min >= props.max) return [];
  const stopCount = (props.max - props.min) / props.step;
  const stepWidth = 100 / stopCount;
  const result = [];
  for (let i = 1; i < stopCount; i++) {
    result.push(i * stepWidth);
  }
  if (props.range) {
    return result.filter((step) => {
      const val = props.min + step * (props.max - props.min) / 100;
      return val < minValue.value || val > maxValue.value;
    });
  } else {
    return result.filter(
      (step) => props.min + step * (props.max - props.min) / 100 > firstValue.value
    );
  }
});
const markList = computed(() => {
  if (!props.marks) return [];
  const marksKeys = Object.keys(props.marks).map(Number).filter((n) => !isNaN(n) && n >= props.min && n <= props.max);
  return marksKeys.map((key) => {
    const mark = props.marks?.[key];
    return {
      point: (key - props.min) / (props.max - props.min) * 100,
      label: typeof mark === "string" ? { label: mark, style: void 0 } : mark ?? { label: "", style: void 0 }
    };
  });
});
const sliderClasses = computed(
  () => [
    ns.b(),
    ns.m(mergedSize.value),
    vertical.value ? ns.is("vertical") : "",
    mergedDisabled.value ? ns.is("disabled") : "",
    props.showInput && !props.range ? ns.is("with-input") : ""
  ].filter(Boolean)
);
const sliderStyle = computed(() => {
  const style = {};
  if (props.color) {
    style["--yh-slider-main-color"] = props.color;
    style["--yh-slider-secondary-color"] = props.color;
  }
  if (props.vertical && props.height) {
    style.height = props.height;
  }
  return {
    ...themeStyle.value,
    ...style
  };
});
const emitValue = (val) => {
  emit("update:modelValue", val);
  emit("input", val);
  if (props.validateEvent) {
    triggerValidate("change");
  }
};
const updateValue = (isChange = false) => {
  const val = props.range ? [minValue.value, maxValue.value] : firstValue.value;
  emitValue(val);
  if (isChange) {
    emit("change", val);
  }
};
const handleButtonChange = () => updateValue(true);
const handleButtonInput = () => updateValue(false);
const onSliderClick = (event) => {
  if (mergedDisabled.value) return;
  const slider = sliderRef.value;
  if (!slider) return;
  const rect = slider.getBoundingClientRect();
  let percent = 0;
  if (props.vertical) {
    const clientY = event.clientY;
    percent = (rect.bottom - clientY) / rect.height * 100;
  } else {
    const clientX = event.clientX;
    percent = (clientX - rect.left) / rect.width * 100;
  }
  if (percent < 0) percent = 0;
  if (percent > 100) percent = 100;
  const lengthPerStep = 100 / ((props.max - props.min) / props.step);
  const steps = Math.round(percent / lengthPerStep);
  let value = steps * lengthPerStep * (props.max - props.min) * 0.01 + props.min;
  value = parseFloat(value.toFixed(getPrecision(props.step)));
  if (props.range) {
    if (Math.abs(value - firstValue.value) < Math.abs(value - secondValue.value)) {
      firstValue.value = value;
    } else {
      secondValue.value = value;
    }
  } else {
    firstValue.value = value;
  }
  updateValue();
};
const getPrecision = (step) => {
  const stepStr = step.toString();
  const dotIndex = stepStr.indexOf(".");
  return dotIndex > -1 ? stepStr.length - dotIndex - 1 : 0;
};
const handleInputChange = (val) => {
  if (val === void 0) return;
  firstValue.value = val;
  updateValue();
};
onMounted(() => {
  initValues();
});
defineExpose({
  sliderRef,
  firstValue,
  secondValue
});
</script>

<template>
  <div :class="sliderClasses" :style="sliderStyle">
    <div :class="ns.e('input')" v-if="showInput && !range">
      <yh-input-number
        :model-value="firstValue"
        :min="min"
        :max="max"
        :step="step"
        :disabled="mergedDisabled"
        :size="inputSize || mergedSize"
        :controls="showInputControls"
        @change="handleInputChange"
      />
    </div>

    <div ref="sliderRef" :class="ns.e('runway')" @mousedown="onSliderClick">
      <div :class="ns.e('bar')" :style="barStyle"></div>

      <slider-button
        v-model="firstValue"
        :vertical="vertical"
        :disabled="mergedDisabled"
        :min="min"
        :max="max"
        :step="step"
        :show-tooltip="showTooltip"
        :format-tooltip="formatTooltip"
        :tooltip-class="tooltipClass"
        :placement="placement"
        @change="handleButtonChange"
        @input="handleButtonInput"
      >
        <template v-if="$slots.thumb" #thumb="scope">
          <slot name="thumb" v-bind="scope"></slot>
        </template>
      </slider-button>

      <slider-button
        v-if="range"
        v-model="secondValue"
        :vertical="vertical"
        :disabled="mergedDisabled"
        :min="min"
        :max="max"
        :step="step"
        :show-tooltip="showTooltip"
        :format-tooltip="formatTooltip"
        :tooltip-class="tooltipClass"
        :placement="placement"
        @change="handleButtonChange"
        @input="handleButtonInput"
      >
        <template v-if="$slots.thumb" #thumb="scope">
          <slot name="thumb" v-bind="scope"></slot>
        </template>
      </slider-button>

      <template v-if="showStops">
        <div
          v-for="(stop, index) in stops"
          :key="index"
          :class="ns.e('stop')"
          :style="vertical ? {
  bottom: stop + '%'
} : {
  left: stop + '%'
}"
        ></div>
      </template>

      <template v-if="markList.length > 0">
        <div>
          <div
            v-for="(item, index) in markList"
            :key="index"
            :class="ns.e('stop')"
            :style="vertical ? {
  bottom: item.point + '%'
} : {
  left: item.point + '%'
}"
          ></div>
        </div>
        <div :class="ns.e('marks')">
          <div
            v-for="(item, index) in markList"
            :key="index"
            :class="ns.e('mark-text')"
            :style="vertical ? {
  bottom: item.point + '%'
} : {
  left: item.point + '%'
}"
          >
            <slot name="mark" :mark="item.label.label">
              <div :style="item.label.style">{{ item.label.label }}</div>
            </slot>
          </div>
        </div>
      </template>
    </div>
    <slot></slot>
  </div>
</template>

<style>
@charset "UTF-8";
/**
 * YH-UI Sass Variables
 * 原具体的 CSS 变量输出规则 (:root, html.dark 等) 已移至 root.scss，以防止各组件样式编译时产生重复的全局变量 CSS 冗余。
 */
.yh-slider {
  --yh-slider-main-color: var(--yh-color-primary);
  --yh-slider-main-color-rgb: 64, 158, 255;
  --yh-slider-secondary-color: var(--yh-color-primary);
  --yh-slider-bg-color: var(--yh-border-color-light);
  --yh-slider-hover-bg-color: var(--yh-border-color-dark);
  --yh-slider-button-size: 16px;
  --yh-slider-button-border: 2px;
  --yh-slider-runway-height: 6px;
  --yh-slider-transition: var(--yh-transition-base);
  display: inline-flex;
  align-items: center;
  width: 100%;
  height: var(--yh-slider-height, 32px);
  position: relative;
  user-select: none;
  box-sizing: border-box;
}
.yh-slider.is-vertical {
  display: inline-flex;
  width: auto;
  height: 100%;
  flex-direction: column;
  padding: 0;
  margin: 0 12px;
  align-items: center;
}
.yh-slider.is-vertical .yh-slider__runway {
  width: var(--yh-slider-runway-height);
  height: 100%;
  flex: 1;
  margin: 0;
  position: relative;
}

.yh-slider.is-vertical .yh-slider__button-wrapper {
  left: 50%;
  transform: translate(-50%, 50%);
  top: auto;
  z-index: 10;
}

.yh-slider.is-vertical .yh-slider__bar {
  width: 100%;
  height: auto;
  left: 0;
  bottom: 0;
  top: auto;
  background: linear-gradient(to top, var(--yh-slider-main-color), var(--yh-slider-secondary-color));
}

.yh-slider.is-vertical .yh-slider__stop {
  left: 50%;
  top: auto;
  transform: translate(-50%, 50%);
}

.yh-slider.is-vertical .yh-slider__marks {
  position: absolute;
  left: 18px;
  top: 0;
  bottom: 0;
  width: 0;
}

.yh-slider.is-vertical .yh-slider__mark-text {
  left: 0;
  top: auto;
  transform: translateY(50%);
  white-space: nowrap;
}

.yh-slider.is-vertical .yh-slider__input {
  margin-right: 0;
  margin-top: 20px;
  width: auto;
}

.yh-slider__input {
  flex-shrink: 0;
  margin-right: 20px;
  width: auto;
}

.yh-slider__runway {
  flex: 1;
  height: var(--yh-slider-runway-height);
  background-color: var(--yh-slider-bg-color);
  border-radius: 3px;
  position: relative;
  cursor: pointer;
  vertical-align: middle;
}
.yh-slider__runway:hover {
  background-color: var(--yh-slider-hover-bg-color);
}

.yh-slider.is-disabled .yh-slider__runway {
  cursor: not-allowed;
  background-color: var(--yh-fill-color-light);
}
.yh-slider.is-disabled .yh-slider__runway:hover {
  background-color: var(--yh-fill-color-light);
}

.yh-slider.is-disabled .yh-slider__bar {
  background: var(--yh-text-color-placeholder);
  box-shadow: none;
}

.yh-slider.is-disabled .yh-slider__button {
  border-color: var(--yh-text-color-placeholder);
  background-color: var(--yh-bg-color);
  cursor: not-allowed;
}
.yh-slider.is-disabled .yh-slider__button:hover {
  transform: none;
  box-shadow: none;
}

.yh-slider.is-disabled .yh-slider__button-wrapper {
  cursor: not-allowed;
}

.yh-slider__bar {
  position: absolute;
  height: 100%;
  left: 0;
  top: 0;
  border-radius: inherit;
  background: linear-gradient(90deg, var(--yh-slider-main-color), var(--yh-slider-secondary-color));
  box-shadow: 0 4px 12px rgba(var(--yh-slider-main-color-rgb, 64, 158, 255), 0.2);
  transition: width var(--yh-duration-base), left var(--yh-duration-base), height var(--yh-duration-base), bottom var(--yh-duration-base);
}

.yh-slider__button-wrapper {
  position: absolute;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 24px;
  height: 24px;
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 10;
  cursor: grab;
  transition: left var(--yh-duration-base), bottom var(--yh-duration-base);
}
.yh-slider__button-wrapper:active {
  cursor: grabbing;
}
.yh-slider__button-wrapper.is-dragging {
  z-index: 11;
}

.yh-slider__button {
  width: var(--yh-slider-button-size);
  height: var(--yh-slider-button-size);
  border: var(--yh-slider-button-border) solid var(--yh-slider-main-color);
  background-color: var(--yh-bg-color);
  border-radius: 50%;
  transition: var(--yh-slider-transition);
  box-shadow: var(--yh-shadow-sm);
}
.yh-slider__button.is-hover {
  transform: scale(1.2);
  box-shadow: 0 0 0 6px rgba(var(--yh-slider-main-color-rgb, 64, 158, 255), 0.15);
}

.yh-slider__button.is-dragging {
  transform: scale(1.2);
  box-shadow: 0 0 0 6px rgba(var(--yh-slider-main-color-rgb, 64, 158, 255), 0.15);
}

.yh-slider__stop {
  position: absolute;
  width: 4px;
  height: 4px;
  background-color: var(--yh-bg-color);
  border-radius: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 1;
}

.yh-slider__mark-text {
  position: absolute;
  top: 15px;
  transform: translateX(-50%);
  font-size: var(--yh-font-size-base);
  color: var(--yh-text-color-secondary);
  margin-top: 10px;
}

.yh-slider__tooltip {
  position: absolute;
  bottom: calc(100% + 10px);
  left: 50%;
  transform: translateX(-50%);
  padding: 6px 10px;
  background: var(--yh-text-color-primary);
  backdrop-filter: blur(4px);
  color: var(--yh-bg-color);
  border-radius: var(--yh-radius-base);
  font-size: var(--yh-font-size-xs);
  white-space: nowrap;
  z-index: 20;
  box-shadow: var(--yh-shadow-md);
}

.yh-slider__tooltip-arrow {
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  border: 5px solid transparent;
  border-top-color: var(--yh-text-color-primary);
}

.yh-slider--large {
  --yh-slider-height: 48px;
  --yh-slider-runway-height: 10px;
  --yh-slider-button-size: 24px;
}

.yh-slider--small {
  --yh-slider-height: 24px;
  --yh-slider-runway-height: 4px;
  --yh-slider-button-size: 12px;
}

.yh-fade-enter-active,
.yh-fade-leave-active {
  transition: opacity 0.2s, transform 0.2s;
}

.yh-fade-enter-from,
.yh-fade-leave-to {
  opacity: 0;
  transform: translateX(-50%) translateY(5px);
}
</style>
