<template>
  <div
    ref="rootEl"
    class="mini-stepper drag-mode"
    :class="[extraClass, { dragging }]"
    :aria-label="ariaLabel"
    role="spinbutton"
    :aria-valuemin="minValue"
    :aria-valuemax="maxValue"
    :aria-valuenow="isPrefix(current) ? undefined : current"
    :aria-valuetext="display"
    tabindex="0"
    @pointerdown="onPointerDown"
    @keydown="onKey"
    @wheel.prevent="onWheel"
  >
    <span class="value" :title="String(current)">{{ display }}</span>
  </div>
</template>

<script setup>
import { computed, ref } from 'vue'

const model = defineModel({ type: Number, default: 0 })

const props = defineProps({
  min: { type: Number, default: 0 },
  max: { type: Number, default: 999 },
  step: { type: Number, default: 1 },
  prefixValues: {
    type: Array,
    default: () => [],
    validator: (arr) =>
      arr.every((item) => typeof item === 'object' && 'value' in item && 'display' in item),
  },
  numberPrefix: { type: String, default: '' },
  numberPostfix: { type: String, default: '' },
  clamp: { type: Boolean, default: true },
  pixelsPerStep: { type: Number, default: 16 },
  // Movement now restricted to horizontal (x). Prop retained for compatibility but ignored.
  axis: { type: String, default: 'x' },
  ariaLabel: { type: String, default: '' },
  extraClass: { type: String, default: '' },
})

const minValue = computed(() => props.min)
const maxValue = computed(() => props.max)

// Helper to check if a value is in the prefix values
const isPrefix = (value) => {
  return props.prefixValues.some((prefix) => prefix.value === value)
}

// Helper to get the display for a prefix value
const getPrefixDisplay = (value) => {
  const prefix = props.prefixValues.find((p) => p.value === value)
  return prefix ? prefix.display : null
}

// Get all valid values in order: prefixValues, then min to max
const allValidValues = computed(() => {
  const prefixVals = props.prefixValues.map((p) => p.value)
  const numericVals = []
  for (let i = props.min; i <= props.max; i += props.step) {
    numericVals.push(i)
  }
  return [...prefixVals, ...numericVals]
})

const current = computed({
  get() {
    return model.value
  },
  set(v) {
    if (props.clamp) {
      // If it's a prefix value, allow it
      if (isPrefix(v)) {
        model.value = v
        return
      }
      // Otherwise clamp to numeric range
      if (v < props.min) v = props.min
      if (v > props.max) v = props.max
    }
    model.value = v
  },
})

const display = computed(() => {
  const prefixDisplay = getPrefixDisplay(current.value)
  if (prefixDisplay !== null) {
    // For prefix values, show only the display text without number prefix/postfix
    return prefixDisplay
  }
  // For numeric values, include prefix and postfix
  const numericValue = String(current.value)
  return `${props.numberPrefix}${numericValue}${props.numberPostfix}`
})

// Drag handling
const dragging = ref(false)
const rootEl = ref(null)
let startX = 0
let startY = 0
let startVal = 0

function onPointerDown(e) {
  e.preventDefault()
  startX = e.clientX
  startY = e.clientY
  startVal = current.value
  dragging.value = true
  try {
    e.currentTarget.setPointerCapture(e.pointerId)
  } catch {}
  rootEl.value?.addEventListener('pointermove', onPointerMove)
  rootEl.value?.addEventListener('pointerup', onPointerUp, { once: true })
  rootEl.value?.addEventListener('pointercancel', onPointerCancel, { once: true })
}
function onPointerMove(e) {
  if (!dragging.value) return
  // Prevent page scroll on touch while dragging
  if (e.pointerType === 'touch') e.preventDefault()
  const primary = e.clientX - startX // horizontal only
  const steps = Math.trunc(primary / props.pixelsPerStep)

  // Find current value index in all valid values
  const currentIndex = allValidValues.value.indexOf(startVal)
  if (currentIndex === -1) return // shouldn't happen

  const newIndex = currentIndex + steps
  if (props.clamp) {
    const clampedIndex = Math.max(0, Math.min(newIndex, allValidValues.value.length - 1))
    const next = allValidValues.value[clampedIndex]
    if (next !== current.value) current.value = next
  } else {
    if (newIndex >= 0 && newIndex < allValidValues.value.length) {
      const next = allValidValues.value[newIndex]
      if (next !== current.value) current.value = next
    }
  }
}
function endDragListeners() {
  rootEl.value?.removeEventListener('pointermove', onPointerMove)
}
function onPointerUp() {
  dragging.value = false
  endDragListeners()
}
function onPointerCancel() {
  dragging.value = false
  endDragListeners()
}

function onKey(e) {
  const key = e.key
  let handled = false
  let newValue = null

  // Find current value index in all valid values
  const currentIndex = allValidValues.value.indexOf(current.value)

  switch (key) {
    case 'ArrowRight':
    case 'ArrowUp':
      if (currentIndex !== -1 && currentIndex < allValidValues.value.length - 1) {
        newValue = allValidValues.value[currentIndex + 1]
      } else if (currentIndex === -1) {
        // Current value not in list, try to increment normally
        newValue = current.value + props.step
      }
      handled = true
      break
    case 'ArrowLeft':
    case 'ArrowDown':
      if (currentIndex !== -1 && currentIndex > 0) {
        newValue = allValidValues.value[currentIndex - 1]
      } else if (currentIndex === -1) {
        // Current value not in list, try to decrement normally
        newValue = current.value - props.step
      }
      handled = true
      break
    case 'PageUp':
      if (currentIndex !== -1) {
        const newIndex = Math.min(currentIndex + 10, allValidValues.value.length - 1)
        newValue = allValidValues.value[newIndex]
      } else {
        newValue = current.value + props.step * 10
      }
      handled = true
      break
    case 'PageDown':
      if (currentIndex !== -1) {
        const newIndex = Math.max(currentIndex - 10, 0)
        newValue = allValidValues.value[newIndex]
      } else {
        newValue = current.value - props.step * 10
      }
      handled = true
      break
    case 'Home':
      newValue = allValidValues.value[0] || props.min
      handled = true
      break
    case 'End':
      newValue = allValidValues.value[allValidValues.value.length - 1] || props.max
      handled = true
      break
  }

  if (newValue !== null) {
    current.value = newValue
  }

  if (handled) {
    e.preventDefault()
    e.stopPropagation()
  }
}

function onWheel(e) {
  // Inverted: deltaY < 0 => decrement, > 0 => increment
  const direction = e.deltaY < 0 ? -1 : e.deltaY > 0 ? 1 : 0
  if (direction === 0) return
  // Use current index in allValidValues list (prefix values included)
  const idx = allValidValues.value.indexOf(current.value)
  if (idx !== -1) {
    const nextIdx = idx + direction
    if (nextIdx >= 0 && nextIdx < allValidValues.value.length) {
      current.value = allValidValues.value[nextIdx]
    }
  } else {
    // Fallback numeric adjustment if not found
    current.value = current.value + direction * props.step
  }
}
</script>

<style scoped>
.mini-stepper.drag-mode {
  cursor: ew-resize;
  user-select: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 0.4rem;
  gap: 0.25rem;
  border: 1px solid transparent;
  background: transparent;
  border-radius: 0.4rem;
  min-height: 1.8rem;
  font-variant-numeric: tabular-nums;
  touch-action: none;
}
.mini-stepper.drag-mode:focus-visible {
  border-color: var(--input-border, var(--muted));
  box-shadow: 0 0 0 2px var(--input-focus, #2563eb);
  outline: none;
}
.mini-stepper.drag-mode .value {
  font-weight: 600;
  min-width: 1.6rem;
  text-align: center;
  pointer-events: none;
}
.mini-stepper.drag-mode.dragging {
  cursor: grabbing;
}
</style>
