<template>
  <div
    class="ivyforms-slider"
    :class="{
      'is-disabled': disabled,
      'is-readonly': readonly,
      'has-grid': showGrid,
    }"
  >
    <!-- Slider track container -->
    <div
      class="ivyforms-slider__track-container"
      @mouseenter="isHovering = true"
      @mouseleave="isHovering = false"
    >
      <!-- Inner rail: thumb/progress/input % map 1:1 to visible track (inset by thumb radius) -->
      <div class="ivyforms-slider__rail">
        <!-- Background track -->
        <div class="ivyforms-slider__track-bg" />

        <!-- Grid marks (if showGrid is enabled) -->
        <div v-if="showGrid && gridTickMarks.length > 0" class="ivyforms-slider__grid">
          <div
            v-for="mark in gridTickMarks"
            :key="mark.value"
            class="ivyforms-slider__grid-mark"
            :style="{ left: mark.position + '%' }"
          />
        </div>

        <!-- Progress bar (filled portion) -->
        <div class="ivyforms-slider__progress" :style="{ width: progressPercentage + '%' }" />

        <!-- Actual input range element (invisible thumb still needs hit area at min/max) -->
        <input
          ref="sliderInput"
          type="range"
          :value="displayValue"
          :min="min"
          :max="max"
          :step="step"
          :disabled="disabled || readonly"
          class="ivyforms-slider__input-range"
          :aria-label="ariaLabel"
          @input="handleSliderChange"
          @focus="handleFocus"
          @blur="handleBlur"
        />

        <!-- Custom circle thumb -->
        <div
          class="ivyforms-slider__thumb"
          :style="{ left: progressPercentage + '%' }"
          :class="{
            'is-active': isFocused || props.alwaysShowTooltip,
            'is-hovering': isHovering,
          }"
        />

        <!-- Tooltip for value display -->
        <div
          v-if="showTooltip && (props.alwaysShowTooltip || isFocused || isHovering)"
          class="ivyforms-slider__tooltip"
          :style="{ left: progressPercentage + '%' }"
        >
          {{ displayValue }}
        </div>
      </div>
    </div>

    <!-- Min/Max labels with grid values (inner width matches __rail for alignment) -->
    <div v-if="showLabels" class="ivyforms-slider__labels">
      <div class="ivyforms-slider__labels-inner">
        <template v-if="showGrid && gridLabelMarks.length > 0">
          <span
            v-for="mark in gridLabelMarks"
            :key="mark.value"
            class="ivyforms-slider__label"
            :class="{ 'ivyforms-slider__label--grid-end': mark.position >= 99.999 }"
            :style="{ left: mark.position + '%' }"
          >
            {{ mark.value }}
          </span>
        </template>
        <template v-else>
          <span class="ivyforms-slider__label ivyforms-slider__label--min">{{ min }}</span>
          <span class="ivyforms-slider__label ivyforms-slider__label--max">{{ max }}</span>
        </template>
      </div>
    </div>
  </div>
</template>

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

interface Props {
  /** The current value of the slider */
  modelValue: number
  /** Minimum value */
  min?: number
  /** Maximum value */
  max?: number
  /** Step value for increments */
  step?: number
  /** Whether the slider is disabled */
  disabled?: boolean
  /** Whether the slider is readonly */
  readonly?: boolean
  /** Show input field for direct value entry */
  showInput?: boolean
  /** Show tooltip with current value */
  showTooltip?: boolean
  /** Keep tooltip visible (e.g. style sample preview) */
  alwaysShowTooltip?: boolean
  /** Show min/max labels */
  showLabels?: boolean
  /** Show grid marks on slider */
  showGrid?: boolean
  /** Aria label for accessibility */
  ariaLabel?: string
}

const props = withDefaults(defineProps<Props>(), {
  min: 0,
  max: 100,
  step: 1,
  disabled: false,
  readonly: false,
  showInput: false,
  showTooltip: true,
  alwaysShowTooltip: false,
  showLabels: false,
  showGrid: false,
  ariaLabel: 'Slider',
})

const emit = defineEmits<{
  (e: 'update:modelValue', value: number): void
  (e: 'change', value: number): void
}>()

const sliderInput = ref<HTMLInputElement>()
const isFocused = ref(false)
const isHovering = ref(false)

/**
 * Clamp model value to a safe number within range.
 */
const displayValue = computed(() => {
  const num = Number(props.modelValue)
  const safeValue = Number.isNaN(num) ? props.min : num
  return Math.max(props.min, Math.min(props.max, safeValue))
})

/**
 * Calculate progress percentage for styling
 */
const progressPercentage = computed(() => {
  const range = props.max - props.min
  if (range <= 0) {
    return 0
  }
  const progress = displayValue.value - props.min
  const pct = (progress / range) * 100
  return Math.max(0, Math.min(100, pct))
})

const GRID_LABEL_MAX_COUNT = 11

function getStepDecimalPlaces(step: number): number {
  const stepText = String(step)
  const dotIndex = stepText.indexOf('.')
  return dotIndex === -1 ? 0 : stepText.length - dotIndex - 1
}

function roundToStepPrecision(value: number, precision: number): number {
  if (precision <= 0) {
    return Math.round(value)
  }
  const factor = 10 ** precision
  return Math.round(value * factor) / factor
}

/**
 * Calculate grid marks for the slider track ticks
 */
const gridMarks = computed(() => {
  if (!props.showGrid) {
    return []
  }

  const marks = []
  const range = props.max - props.min

  if (range <= 0) {
    return marks
  }

  const markStep = Number.isFinite(props.step) && props.step > 0 ? props.step : 1
  const precision = getStepDecimalPlaces(markStep)
  const stepCount = Math.round(range / markStep)

  for (let index = 0; index <= stepCount; index++) {
    const value =
      index === stepCount
        ? props.max
        : roundToStepPrecision(props.min + index * markStep, precision)
    const position = ((value - props.min) / range) * 100
    marks.push({
      value,
      position: Math.max(0, Math.min(100, position)),
    })
  }

  return marks
})

/**
 * Subset of grid marks for value labels (avoids unreadable overlap on wide ranges).
 */
const gridLabelMarks = computed(() => {
  const marks = gridMarks.value
  if (marks.length === 0) {
    return []
  }
  if (marks.length <= GRID_LABEL_MAX_COUNT) {
    return marks
  }

  const labels: typeof marks = []
  const seen = new Set<number>()
  const lastIndex = marks.length - 1

  for (let i = 0; i < GRID_LABEL_MAX_COUNT; i++) {
    const index = Math.round((i / (GRID_LABEL_MAX_COUNT - 1)) * lastIndex)
    const mark = marks[index]
    if (!seen.has(mark.value)) {
      seen.add(mark.value)
      labels.push(mark)
    }
  }

  return labels
})

/**
 * Grid ticks on the track (subsampled when range is wide).
 */
const gridTickMarks = computed(() => {
  const marks = gridMarks.value
  if (marks.length === 0) {
    return []
  }
  if (marks.length <= GRID_LABEL_MAX_COUNT) {
    return marks
  }
  return gridLabelMarks.value
})

/**
 * Handle slider input change
 */
const handleSliderChange = (event: Event) => {
  const newValue = Number((event.target as HTMLInputElement).value)
  emit('update:modelValue', newValue)
  emit('change', newValue)
}

/**
 * Handle slider focus
 */
const handleFocus = () => {
  isFocused.value = true
}

/**
 * Handle slider blur
 */
const handleBlur = () => {
  isFocused.value = false
}
</script>

<style lang="scss" scoped>
.ivyforms-slider {
  --ivy-slider-primary: var(--map-base-dusk-symbol-1);
  --ivy-slider-track-bg: var(--map-base-dusk-stroke--2);
  --ivy-slider-track-height: 4px;
  --ivy-slider-thumb-size: 14px;
  --ivy-slider-thumb-bg: var(--map-ground-level-1-foreground);
  --ivy-slider-thumb-border: 3px;
  --ivy-slider-thumb-border-color: var(--ivy-slider-primary);
  --ivy-slider-thumb-hover-size: 18px;
  --ivy-slider-text: var(--map-base-dusk-symbol-1);
  --ivy-slider-tooltip-bg: var(--map-base-text-0);
  --ivy-slider-tooltip-text: var(--map-ground-level-1-foreground);
  --ivy-slider-hit-height: 32px;
  /* Extra room under grid ticks so captions (e.g. caps) in IvyFormItem do not collide */
  --ivy-slider-grid-labels-padding-bottom: 14px;

  display: flex;
  flex-direction: column;
  gap: 0;
  width: 99%;

  &.is-disabled {
    opacity: 0.5;
    pointer-events: none;
    cursor: not-allowed;
  }

  &.is-readonly {
    pointer-events: none;
  }

  /**
   * Track container - vertical hit band; horizontal inset lives on __rail
   */
  &__track-container {
    position: relative;
    width: 100%;
    height: var(--ivy-slider-hit-height);
    display: flex;
    align-items: center;
    overflow: visible;
  }

  /**
   * Rail inset by half hover-thumb so value 0/100 align with track ends
   */
  &__rail {
    position: absolute;
    top: 0;
    right: calc(var(--ivy-slider-thumb-hover-size) / 2);
    bottom: 0;
    left: calc(var(--ivy-slider-thumb-hover-size) / 2);
  }

  /**
   * Background track
   */
  &__track-bg {
    position: absolute;
    top: 50%;
    left: 0;
    right: 0;
    width: auto;
    height: var(--ivy-slider-track-height);
    background: var(--ivy-slider-track-bg);
    border-radius: 999px;
    transform: translateY(-50%);
    pointer-events: none;
  }

  /**
   * Progress bar (filled portion of track)
   */
  &__progress {
    background: var(--ivy-slider-primary);
    position: absolute;
    top: 50%;
    left: 0;
    height: var(--ivy-slider-track-height);
    border-radius: 999px;
    transform: translateY(-50%);
    pointer-events: none;
    transition: width 0.15s ease-out;
    z-index: 2;
  }

  /**
   * Grid container and marks (ticks sit below the track so the rail stays solid)
   */
  &__grid {
    position: absolute;
    top: 50%;
    left: 0;
    right: 0;
    width: auto;
    height: 0;
    transform: translateY(-50%);
    pointer-events: none;
    z-index: 4;
  }

  &__grid-mark {
    position: absolute;
    top: calc(var(--ivy-slider-track-height) / 2);
    width: 1px;
    height: 6px;
    background: var(--ivy-slider-text);
    opacity: 0.35;
    transform: translate(-50%, 0);
  }

  /**
   * Invisible range input (provides interaction)
   */
  /* stylelint-disable selector-pseudo-element-no-unknown */
  &__input-range {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    padding: 0;
    background: transparent;
    -webkit-appearance: none;
    appearance: none;
    cursor: pointer;
    outline: none;
    z-index: 5;

    /* Webkit browsers (Chrome, Safari, Edge) */
    &::-webkit-slider-track {
      background: transparent;
      height: var(--ivy-slider-track-height);
      border-radius: 999px;
      border: none;
    }

    /* Non-zero invisible thumb: 0×0 thumbs have no hit target at min/max */
    &::-webkit-slider-thumb {
      -webkit-appearance: none;
      appearance: none;
      width: var(--ivy-slider-thumb-hover-size);
      height: var(--ivy-slider-thumb-hover-size);
      margin-top: calc((var(--ivy-slider-track-height) - var(--ivy-slider-thumb-hover-size)) / 2);
      background: transparent;
      cursor: pointer;
      border: none;
      box-shadow: none;
      opacity: 0;
    }

    /* Firefox */
    &::-moz-range-track {
      background: transparent;
      height: var(--ivy-slider-track-height);
      border-radius: 999px;
      border: none;
    }

    &::-moz-range-thumb {
      width: var(--ivy-slider-thumb-hover-size);
      height: var(--ivy-slider-thumb-hover-size);
      background: transparent;
      cursor: pointer;
      border: none;
      box-shadow: none;
      opacity: 0;
      transform: translateY(
        calc((var(--ivy-slider-track-height) - var(--ivy-slider-thumb-hover-size)) / 2)
      );
    }

    &:disabled {
      cursor: not-allowed;
    }
  }
  /* stylelint-enable selector-pseudo-element-no-unknown */

  /**
   * Custom circular thumb
   */
  &__thumb {
    position: absolute;
    top: 50%;
    width: var(--ivy-slider-thumb-size);
    height: var(--ivy-slider-thumb-size);
    background: var(--ivy-slider-thumb-bg);
    border: var(--ivy-slider-thumb-border) solid var(--ivy-slider-thumb-border-color);
    border-radius: 50%;
    transform: translate(-50%, -50%);
    pointer-events: none;
    z-index: 10;
    transition: all 0.2s ease-out;
    box-shadow:
      0 1px 3px 0 rgba(0, 0, 0, 0.12),
      0 1px 2px 0 rgba(0, 0, 0, 0.06);

    /* Hover state */
    &.is-hovering {
      width: var(--ivy-slider-thumb-hover-size);
      height: var(--ivy-slider-thumb-hover-size);
      box-shadow:
        0 2px 4px 0 rgba(0, 0, 0, 0.14),
        0 3px 6px 0 rgba(0, 0, 0, 0.1);
    }

    /* Focus/Active state */
    &.is-active {
      width: var(--ivy-slider-thumb-hover-size);
      height: var(--ivy-slider-thumb-hover-size);
      box-shadow:
        0 3px 6px 0 rgba(0, 0, 0, 0.16),
        0 4px 8px 0 rgba(0, 0, 0, 0.12);
    }
  }

  /**
   * Tooltip for value display (appears on hover/focus)
   */
  &__tooltip {
    position: absolute;
    bottom: calc(100% + 8px);
    background: var(--ivy-slider-tooltip-bg);
    color: var(--ivy-slider-tooltip-text);
    padding: 4px 8px;
    border-radius: 4px;
    font-size: 12px;
    font-weight: 500;
    line-height: 1.4;
    white-space: nowrap;
    pointer-events: none;
    transform: translateX(-50%);
    transition: opacity 0.2s ease-out;
    z-index: 20;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);

    &::after {
      content: '';
      position: absolute;
      top: 100%;
      left: 50%;
      transform: translateX(-50%);
      width: 0;
      height: 0;
      border-left: 4px solid transparent;
      border-right: 4px solid transparent;
      border-top: 4px solid var(--ivy-slider-tooltip-bg);
    }
  }

  /**
   * Min/Max labels below slider
   */
  &__labels {
    position: relative;
    font-size: 12px;
    line-height: 16px;
    color: var(--ivy-slider-text);
    user-select: none;
  }

  &__labels-inner {
    position: relative;
    margin: 0 calc(var(--ivy-slider-thumb-hover-size) / 2);
    min-height: 16px;
  }

  &.has-grid &__labels-inner {
    /* Absolute ticks do not contribute height; reserve one line + gap before following copy */
    min-height: 1.35em;
    padding-bottom: var(--ivy-slider-grid-labels-padding-bottom);
  }

  &__label {
    &--min {
      position: absolute;
      left: 0;
      top: 0;
      text-align: left;
    }

    &--max {
      position: absolute;
      right: 0;
      top: 0;
      text-align: right;
    }
  }

  &.has-grid &__label {
    position: absolute;
    top: 0;
    transform: translateX(-50%);
    text-align: center;
    line-height: 1.25;
  }

  /* Right-align max tick so its numeral edge matches the rail end (vs centered past the line) */
  &.has-grid &__label--grid-end {
    transform: translateX(-100%);
    text-align: right;
  }
}
</style>
