<script setup lang="ts">
import { ref, computed, watch, onBeforeUnmount, nextTick } from 'vue'
import { Clock } from 'lucide-vue-next'
import { getFixedPositionMapping } from '../utils/dropdown'

interface Props {
  hour: number
  minute: number
  placeholder?: string
}

const props = withDefaults(defineProps<Props>(), {
  placeholder: 'Select time',
})

const emit = defineEmits<{
  'update:hour': [value: number]
  'update:minute': [value: number]
}>()

const isOpen = ref(false)
const triggerRef = ref<HTMLButtonElement | null>(null)
const panelRef = ref<HTMLDivElement | null>(null)
const panelStyle = ref<Record<string, string>>({})

// Reduced motion check
const scrollBehavior = window.matchMedia('(prefers-reduced-motion: reduce)').matches ? 'auto' as const : 'smooth' as const

// Time wheel constants
const WHEEL_ITEM_H = 36
const WHEEL_VISIBLE = 5
const WHEEL_HEIGHT = WHEEL_ITEM_H * WHEEL_VISIBLE
const WHEEL_PAD = WHEEL_ITEM_H * 2

const internalHour = ref(props.hour)
const internalMinute = ref(props.minute)
const hourWheelRef = ref<HTMLDivElement | null>(null)
const minuteWheelRef = ref<HTMLDivElement | null>(null)

watch(() => props.hour, (v) => { internalHour.value = v })
watch(() => props.minute, (v) => { internalMinute.value = v })

const displayText = computed(() => {
  const hh = String(props.hour).padStart(2, '0')
  const mm = String(props.minute).padStart(2, '0')
  return `${hh}:${mm}`
})

// Debounced scroll handlers
let hourScrollTimer: ReturnType<typeof setTimeout> | null = null
let minuteScrollTimer: ReturnType<typeof setTimeout> | null = null

function onHourScroll() {
  if (hourScrollTimer) clearTimeout(hourScrollTimer)
  hourScrollTimer = setTimeout(() => {
    if (!hourWheelRef.value) return
    const index = Math.round(hourWheelRef.value.scrollTop / WHEEL_ITEM_H)
    const clamped = Math.min(23, Math.max(0, index))
    if (internalHour.value !== clamped) {
      internalHour.value = clamped
      emit('update:hour', clamped)
    }
  }, 60)
}

function onMinuteScroll() {
  if (minuteScrollTimer) clearTimeout(minuteScrollTimer)
  minuteScrollTimer = setTimeout(() => {
    if (!minuteWheelRef.value) return
    const index = Math.round(minuteWheelRef.value.scrollTop / WHEEL_ITEM_H)
    const clamped = Math.min(59, Math.max(0, index))
    if (internalMinute.value !== clamped) {
      internalMinute.value = clamped
      emit('update:minute', clamped)
    }
  }, 60)
}

// Mouse wheel: one item per tick
function onHourWheel(e: WheelEvent) {
  e.preventDefault()
  if (!hourWheelRef.value) return
  const dir = e.deltaY > 0 ? 1 : -1
  const cur = Math.round(hourWheelRef.value.scrollTop / WHEEL_ITEM_H)
  const next = Math.min(23, Math.max(0, cur + dir))
  hourWheelRef.value.scrollTo({ top: next * WHEEL_ITEM_H, behavior: scrollBehavior })
}

function onMinuteWheel(e: WheelEvent) {
  e.preventDefault()
  if (!minuteWheelRef.value) return
  const dir = e.deltaY > 0 ? 1 : -1
  const cur = Math.round(minuteWheelRef.value.scrollTop / WHEEL_ITEM_H)
  const next = Math.min(59, Math.max(0, cur + dir))
  minuteWheelRef.value.scrollTo({ top: next * WHEEL_ITEM_H, behavior: scrollBehavior })
}

// Keyboard support
function onWheelKeydown(e: KeyboardEvent, type: 'hour' | 'minute') {
  if (e.key !== 'ArrowUp' && e.key !== 'ArrowDown') return
  e.preventDefault()
  const dir = e.key === 'ArrowUp' ? -1 : 1
  if (type === 'hour') {
    const next = Math.min(23, Math.max(0, internalHour.value + dir))
    internalHour.value = next
    emit('update:hour', next)
    if (hourWheelRef.value) {
      hourWheelRef.value.scrollTo({ top: next * WHEEL_ITEM_H, behavior: scrollBehavior })
    }
  } else {
    const next = Math.min(59, Math.max(0, internalMinute.value + dir))
    internalMinute.value = next
    emit('update:minute', next)
    if (minuteWheelRef.value) {
      minuteWheelRef.value.scrollTo({ top: next * WHEEL_ITEM_H, behavior: scrollBehavior })
    }
  }
}

// Click-and-drag support
let dragEl: HTMLElement | null = null
let dragStartY = 0
let dragStartScrollTop = 0

function onWheelPointerDown(e: PointerEvent) {
  const el = e.currentTarget as HTMLElement
  dragEl = el
  dragStartY = e.clientY
  dragStartScrollTop = el.scrollTop
  el.setPointerCapture(e.pointerId)
}

function onWheelPointerMove(e: PointerEvent) {
  if (!dragEl) return
  e.preventDefault()
  const delta = dragStartY - e.clientY
  dragEl.scrollTop = dragStartScrollTop + delta
}

function onWheelPointerUp(e: PointerEvent) {
  if (!dragEl) return
  const el = dragEl
  dragEl = null
  el.releasePointerCapture(e.pointerId)
  const index = Math.round(el.scrollTop / WHEEL_ITEM_H)
  el.scrollTo({ top: index * WHEEL_ITEM_H, behavior: scrollBehavior })
}

function scrollWheelsToTime() {
  if (hourWheelRef.value) {
    hourWheelRef.value.scrollTop = internalHour.value * WHEEL_ITEM_H
  }
  if (minuteWheelRef.value) {
    minuteWheelRef.value.scrollTop = internalMinute.value * WHEEL_ITEM_H
  }
}

// Positioning
function updatePosition() {
  if (!triggerRef.value) return
  const rect = triggerRef.value.getBoundingClientRect()
  const { scaleX, scaleY, offsetX, offsetY } = getFixedPositionMapping()
  const gap = 4

  const panelWidth = 120
  const estW = panelWidth * scaleX
  const vpW = window.innerWidth
  const vpH = window.innerHeight

  let leftVP = rect.left
  if (leftVP + estW > vpW - gap) {
    leftVP = vpW - estW - gap
  }
  if (leftVP < gap) leftVP = gap

  let topVP = rect.bottom + gap
  // Use offsetHeight (unaffected by CSS transition transforms)
  if (panelRef.value) {
    const panelH = panelRef.value.offsetHeight * scaleY
    if (topVP + panelH > vpH && rect.top - gap - panelH >= 0) {
      topVP = rect.top - gap - panelH
    }
  }

  panelStyle.value = {
    position: 'fixed',
    top: `${(topVP - offsetY) / scaleY}px`,
    left: `${(leftVP - offsetX) / scaleX}px`,
    zIndex: '9999',
  }
}

// Open / Close
function toggle() {
  if (isOpen.value) {
    close()
  } else {
    open()
  }
}

function open() {
  isOpen.value = true
  updatePosition()
  nextTick(() => {
    // Reposition with actual panel height (fixes above-flip offset)
    updatePosition()
    document.addEventListener('click', onClickOutside, true)
    document.addEventListener('scroll', onScrollOrResize, true)
    window.addEventListener('resize', onScrollOrResize)
    scrollWheelsToTime()
  })
}

function close() {
  isOpen.value = false
  document.removeEventListener('click', onClickOutside, true)
  document.removeEventListener('scroll', onScrollOrResize, true)
  window.removeEventListener('resize', onScrollOrResize)
}

function onClickOutside(e: MouseEvent) {
  const target = e.target as Node
  if (
    triggerRef.value?.contains(target) ||
    panelRef.value?.contains(target)
  ) {
    return
  }
  close()
}

function onScrollOrResize() {
  if (isOpen.value) {
    updatePosition()
  }
}

onBeforeUnmount(() => {
  document.removeEventListener('click', onClickOutside, true)
  document.removeEventListener('scroll', onScrollOrResize, true)
  window.removeEventListener('resize', onScrollOrResize)
})
</script>

<template>
  <div class="relative">
    <!-- Trigger button -->
    <button
      ref="triggerRef"
      type="button"
      @click="toggle"
      :aria-expanded="isOpen"
      aria-haspopup="dialog"
      class="w-full flex items-center justify-between gap-2 px-3 py-2 bg-bg-inset border border-border-subtle rounded-xl text-[0.8125rem] text-left cursor-pointer transition-colors"
      :style="
        isOpen
          ? {
              borderColor: 'var(--color-accent)',
              boxShadow: '0 0 0 2px var(--color-accent-muted)',
              outline: 'none',
            }
          : {}
      "
    >
      <span class="text-text-primary font-mono">
        {{ displayText }}
      </span>
      <Clock
        aria-hidden="true"
        class="w-4 h-4 text-text-secondary shrink-0"
        :stroke-width="2"
      />
    </button>

    <!-- Time picker popover -->
    <Teleport to="#app">
      <Transition name="dropdown">
      <div
        v-if="isOpen"
        ref="panelRef"
        :style="panelStyle"
        role="dialog"
        aria-modal="true"
        aria-label="Time picker"
        @keydown.escape.prevent="close"
        class="bg-bg-surface border border-border-visible rounded-xl shadow-[0_4px_24px_rgba(0,0,0,0.4)] overflow-hidden p-3"
      >
        <div class="flex items-center gap-1.5">
          <!-- Hour wheel -->
          <div
            class="relative overflow-hidden rounded-lg"
            :style="{ height: WHEEL_HEIGHT + 'px', width: '42px' }"
          >
            <div
              class="absolute inset-x-0 rounded-lg pointer-events-none bg-bg-elevated border border-border-subtle"
              :style="{ top: WHEEL_PAD + 'px', height: WHEEL_ITEM_H + 'px' }"
            />
            <div
              ref="hourWheelRef"
              tabindex="0"
              role="spinbutton"
              :aria-valuenow="internalHour"
              aria-valuemin="0"
              aria-valuemax="23"
              aria-label="Hour"
              class="absolute inset-0 overflow-y-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden cursor-grab active:cursor-grabbing"
              style="touch-action: none; -webkit-mask-image: linear-gradient(to bottom, transparent 0%, black 25%, black 75%, transparent 100%); mask-image: linear-gradient(to bottom, transparent 0%, black 25%, black 75%, transparent 100%);"
              @scroll="onHourScroll"
              @wheel.prevent="onHourWheel"
              @keydown="onWheelKeydown($event, 'hour')"
              @pointerdown.prevent="onWheelPointerDown"
              @pointermove="onWheelPointerMove"
              @pointerup="onWheelPointerUp"
            >
              <div :style="{ height: WHEEL_PAD + 'px' }" class="shrink-0" />
              <div
                v-for="h in 24"
                :key="h"
                class="shrink-0 flex items-center justify-center text-[0.875rem] font-mono select-none"
                :style="{ height: WHEEL_ITEM_H + 'px' }"
                :class="internalHour === h - 1 ? 'text-text-primary font-semibold' : 'text-text-tertiary'"
              >
                {{ String(h - 1).padStart(2, '0') }}
              </div>
              <div :style="{ height: WHEEL_PAD + 'px' }" class="shrink-0" />
            </div>
          </div>

          <span class="text-text-secondary text-sm font-mono font-semibold select-none">:</span>

          <!-- Minute wheel -->
          <div
            class="relative overflow-hidden rounded-lg"
            :style="{ height: WHEEL_HEIGHT + 'px', width: '42px' }"
          >
            <div
              class="absolute inset-x-0 rounded-lg pointer-events-none bg-bg-elevated border border-border-subtle"
              :style="{ top: WHEEL_PAD + 'px', height: WHEEL_ITEM_H + 'px' }"
            />
            <div
              ref="minuteWheelRef"
              tabindex="0"
              role="spinbutton"
              :aria-valuenow="internalMinute"
              aria-valuemin="0"
              aria-valuemax="59"
              aria-label="Minute"
              class="absolute inset-0 overflow-y-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden cursor-grab active:cursor-grabbing"
              style="touch-action: none; -webkit-mask-image: linear-gradient(to bottom, transparent 0%, black 25%, black 75%, transparent 100%); mask-image: linear-gradient(to bottom, transparent 0%, black 25%, black 75%, transparent 100%);"
              @scroll="onMinuteScroll"
              @wheel.prevent="onMinuteWheel"
              @keydown="onWheelKeydown($event, 'minute')"
              @pointerdown.prevent="onWheelPointerDown"
              @pointermove="onWheelPointerMove"
              @pointerup="onWheelPointerUp"
            >
              <div :style="{ height: WHEEL_PAD + 'px' }" class="shrink-0" />
              <div
                v-for="m in 60"
                :key="m"
                class="shrink-0 flex items-center justify-center text-[0.875rem] font-mono select-none"
                :style="{ height: WHEEL_ITEM_H + 'px' }"
                :class="internalMinute === m - 1 ? 'text-text-primary font-semibold' : 'text-text-tertiary'"
              >
                {{ String(m - 1).padStart(2, '0') }}
              </div>
              <div :style="{ height: WHEEL_PAD + 'px' }" class="shrink-0" />
            </div>
          </div>
        </div>
      </div>
      </Transition>
    </Teleport>
  </div>
</template>
