<template>
  <div class="root-container">
    <div class="slider-component">
      <div class="slide-container">
        <div class="snap-points-wrapper">
          <div class="snap-points">
            <div
              v-for="snapPoint in snapPoints"
              :key="snapPoint"
              class="snap-point"
              :class="{ green: snapPoint <= currentValue }"
              :style="{ left: ((snapPoint - min) / (max - min)) * 100 + '%' }"
            ></div>
          </div>
        </div>
        <input
          ref="input"
          v-model="currentValue"
          type="range"
          :min="min"
          :max="max"
          :step="step"
          class="slider"
          :class="{
            disabled: disabled,
          }"
          :disabled="disabled"
          :style="{
            '--current-value': currentValue,
            '--min-value': min,
            '--max-value': max,
          }"
          @input="onInputWithSnap(($event.target as HTMLInputElement).value)"
        />
        <div class="slider-range">
          <span> {{ min }} {{ unit }} </span>
          <span> {{ max }} {{ unit }} </span>
        </div>
      </div>
    </div>
    <input
      ref="value"
      :value="currentValue"
      type="text"
      class="slider-input"
      :disabled="disabled"
      @change="onInput(($event.target as HTMLInputElement).value)"
    />
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'

let emit = defineEmits<{ 'update:modelValue': [number] }>()

interface Props {
  modelValue?: number
  min: number
  max: number
  step?: number
  forceStep?: boolean
  snapPoints?: number[]
  snapRange?: number
  disabled?: boolean
  unit?: string
}

const props = withDefaults(defineProps<Props>(), {
  modelValue: 0,
  min: 0,
  max: 100,
  step: 10,
  forceStep: true,
  snapPoints: () => [],
  snapRange: 100,
  disabled: false,
  unit: '',
})

let currentValue = ref(Math.max(props.min, props.modelValue))

const inputValueValid = (newValue: number) => {
  if (newValue < props.min) {
    currentValue.value = props.min
  } else if (newValue > props.max) {
    currentValue.value = props.max
  } else if (!newValue) {
    currentValue.value = props.min
  } else {
    currentValue.value = newValue - (props.forceStep ? newValue % props.step : 0)
  }

  emit('update:modelValue', currentValue.value)
}

const onInputWithSnap = (value: string) => {
  let parsedValue = parseInt(value)

  for (let snapPoint of props.snapPoints) {
    const distance = Math.abs(snapPoint - parsedValue)

    if (distance < props.snapRange) {
      parsedValue = snapPoint
    }
  }

  inputValueValid(parsedValue)
}

const onInput = (value: string) => {
  inputValueValid(parseInt(value))
}
</script>

<style lang="scss" scoped>
.root-container {
  --transition-speed: 0.2s;

  @media (prefers-reduced-motion) {
    --transition-speed: 0s;
  }

  display: flex;
  flex-direction: row;
  align-items: center;
  width: 100%;
}

.slider-component,
.slide-container {
  width: 100%;

  position: relative;
}

.slider-component .slide-container .slider {
  -webkit-appearance: none;
  appearance: none;
  position: relative;

  border-radius: var(--radius-sm);
  height: 0.25rem;
  width: 100%;

  background: linear-gradient(
    to right,
    var(--color-brand),
    var(--color-brand)
      calc((var(--current-value) - var(--min-value)) / (var(--max-value) - var(--min-value)) * 100%),
    var(--color-base)
      calc((var(--current-value) - var(--min-value)) / (var(--max-value) - var(--min-value)) * 100%),
    var(--color-base) 100%
  );
  background-size: 100% 100%;
  outline: none;
  vertical-align: middle;
}

.slider-component .slide-container .slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 0.75rem;
  height: 0.75rem;
  background: var(--color-brand);
  cursor: pointer;
  border-radius: 50%;
  transition: var(--transition-speed);
}

.slider-component .slide-container .slider::-moz-range-thumb {
  border: none;
  width: 0.75rem;
  height: 0.75rem;
  background: var(--color-brand);
  cursor: pointer;
  border-radius: 50%;
  transition: var(--transition-speed);
}

.slider-component .slide-container .slider:hover::-webkit-slider-thumb:not(.disabled) {
  width: 1rem;
  height: 1rem;
  transition: var(--transition-speed);
}

.slider-component .slide-container .slider:hover::-moz-range-thumb:not(.disabled) {
  width: 1rem;
  height: 1rem;
  transition: var(--transition-speed);
}

.slider-component .slide-container .snap-points-wrapper {
  position: absolute;
  height: 50%;
  width: 100%;

  .snap-points {
    position: relative;
    display: inline-block;

    vertical-align: middle;

    width: calc(100% - 0.75rem);
    height: 0.75rem;

    left: calc(0.75rem / 2);

    .snap-point {
      position: absolute;
      display: inline-block;

      width: 0.25rem;
      height: 100%;
      border-radius: var(--radius-sm);

      background-color: var(--color-base);

      transform: translateX(calc(-0.25rem / 2));

      &.green {
        background-color: var(--color-brand);
      }
    }
  }
}

.slider-input {
  width: 6rem;
  margin-left: 0.75rem;
}

.slider-range {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  font-size: 0.75rem;
  margin: 0;
}

.disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
</style>
