<template>
  <div ref="root" class="relative w-full">
    <div
      ref="trigger"
      class="w-full flex items-center rounded-md border border-neutral-300 px-2 text-sm text-neutral-900 focus-within:border-primary-500"
      :class="props.disabled ? 'cursor-not-allowed border-neutral-300 bg-neutral-200 text-neutral-500' : 'bg-white'"
    >
      <input
        ref="inputRef"
        v-model="inputValue"
        type="text"
        inputmode="numeric"
        :placeholder="placeholder"
        :disabled="props.disabled"
        class="h-9 w-full bg-transparent px-1 outline-none disabled:cursor-not-allowed disabled:bg-neutral-200 disabled:text-neutral-500"
        @focus="openMenu"
        @keydown.down.prevent="openMenuAndFocusFirst"
        @keydown.enter.prevent="commitInput"
        @keydown.esc.prevent="closeMenu"
        @input="onInput($event)"
        @blur="onInputBlur"
      />
      <button
        type="button"
        tabindex="-1"
        class="hidden lg:inline-flex h-8 w-8 items-center justify-center rounded text-neutral-600 hover:bg-tertiary-500 disabled:cursor-not-allowed disabled:bg-neutral-200 disabled:text-neutral-500"
        :disabled="props.disabled"
        @mousedown.prevent
        @click="toggleOpen"
      >
        <Icon name="mdi:chevron-down" />
      </button>
    </div>
  </div>
  <Teleport to="body">
    <div
      v-if="isOpen"
      ref="menu"
      class="fixed z-[120] overflow-y-auto rounded-md border border-neutral-300 bg-white shadow-sm"
      :style="menuStyle"
    >
      <button type="button" class="block w-full px-2 py-2 text-left text-sm hover:bg-tertiary-500" @click="selectValue('')">
        {{ placeholder }}
      </button>
      <button
        v-for="slot in filteredTimeSlots"
        :key="slot"
        type="button"
        class="block w-full px-2 py-2 text-left text-sm hover:bg-tertiary-500"
        @click="selectValue(slot)"
      >
        {{ slot }}
      </button>
      <p v-if="filteredTimeSlots.length === 0" class="px-2 py-2 text-sm text-neutral-500">
        Aucun résultat
      </p>
    </div>
  </Teleport>
</template>

<script setup lang="ts">
const props = withDefaults(defineProps<{
  modelValue: string
  placeholder?: string
  disabled?: boolean
}>(), {
  placeholder: '--',
  disabled: false
})

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

const root = ref<HTMLElement | null>(null)
const trigger = ref<HTMLElement | null>(null)
const menu = ref<HTMLElement | null>(null)
const inputRef = ref<HTMLInputElement | null>(null)
const isOpen = ref(false)
const inputValue = ref('')
const menuStyle = ref<Record<string, string>>({
  top: '0px',
  left: '0px',
  width: '0px',
  maxHeight: '224px'
})

const timeSlots = computed(() => {
  const slots: string[] = []
  for (let hour = 0; hour < 24; hour++) {
    for (let minute = 0; minute < 60; minute += 15) {
      slots.push(`${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}`)
    }
  }
  return slots
})

const filteredTimeSlots = computed(() => {
  const query = inputValue.value.trim()
  if (!query) return timeSlots.value
  return timeSlots.value.filter((slot) => slot.includes(query))
})

const applyTimeMask = (value: string): string => {
  const digits = value.replace(/\D/g, '').slice(0, 4)
  if (digits.length <= 2) return digits
  return `${digits.slice(0, 2)}:${digits.slice(2)}`
}

const normalizeTypedTime = (value: string): string | null => {
  const trimmed = value.trim()
  if (trimmed === '') return ''

  // Accepte HH:MM ou H:MM puis normalise en HH:MM.
  const match = trimmed.match(/^(\d{1,2}):(\d{2})$/)
  if (!match) return null
  const hours = Number(match[1])
  const minutes = Number(match[2])
  if (!Number.isInteger(hours) || !Number.isInteger(minutes)) return null
  if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59) return null
  return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`
}

const updateMenuPosition = () => {
  const triggerEl = trigger.value
  if (!triggerEl) return

  const rect = triggerEl.getBoundingClientRect()
  const menuHeight = 224
  const belowTop = rect.bottom + 4
  const aboveTop = Math.max(8, rect.top - menuHeight - 4)
  const canOpenBelow = belowTop + menuHeight <= window.innerHeight - 8
  const top = canOpenBelow ? belowTop : aboveTop

  menuStyle.value = {
    top: `${top}px`,
    left: `${rect.left}px`,
    width: `${rect.width}px`,
    maxHeight: `${menuHeight}px`
  }
}

const toggleOpen = () => {
  if (props.disabled) return
  const next = !isOpen.value
  isOpen.value = next
  if (next) {
    nextTick(updateMenuPosition)
  }
}

const isMobile = () => window.innerWidth < 1024

const openMenu = () => {
  if (props.disabled) return
  if (isMobile()) return
  if (!isOpen.value) {
    isOpen.value = true
    nextTick(updateMenuPosition)
  }
}

const openMenuAndFocusFirst = () => {
  openMenu()
}

const closeMenu = () => {
  isOpen.value = false
}

const snapToNearest15 = (time: string): string => {
  const [h, m] = time.split(':').map(Number)
  const snapped = Math.round(m / 15) * 15
  if (snapped === 60) {
    const newH = h + 1
    if (newH > 23) return '23:45'
    return `${String(newH).padStart(2, '0')}:00`
  }
  return `${String(h).padStart(2, '0')}:${String(snapped).padStart(2, '0')}`
}

const commitInput = () => {
  let value = inputValue.value
  if (isMobile()) {
    value = clampTime(value)
    const normalized = normalizeTypedTime(value)
    if (normalized !== null && normalized !== '') {
      value = snapToNearest15(normalized)
    }
    inputValue.value = value
  }
  const normalized = normalizeTypedTime(value)
  if (normalized === null || (normalized !== '' && !timeSlots.value.includes(normalized))) {
    emit('update:modelValue', '')
    inputValue.value = ''
    closeMenu()
    return
  }
  emit('update:modelValue', normalized)
  inputValue.value = normalized
  closeMenu()
}

const onInput = (event: Event) => {
  const target = event.target as HTMLInputElement
  const masked = applyTimeMask(target.value)
  if (masked !== inputValue.value) {
    inputValue.value = masked
  }
  if (!isMobile()) {
    openMenu()
  }
}

const clampTime = (value: string): string => {
  const normalized = normalizeTypedTime(value)
  if (normalized === null || normalized === '') return value
  const [h, m] = normalized.split(':').map(Number)
  if (h > 23 || (h === 23 && m > 45)) return '23:45'
  return normalized
}

const onInputBlur = () => {
  // Laisse le temps au click menu de passer avant fermeture.
  setTimeout(() => {
    if (menu.value?.contains(document.activeElement)) return
    if (isMobile()) {
      inputValue.value = clampTime(inputValue.value)
    }
    commitInput()
  }, 50)
}

const selectValue = (value: string) => {
  if (props.disabled) return
  emit('update:modelValue', value)
  inputValue.value = value
  isOpen.value = false
  nextTick(() => inputRef.value?.focus())
}

const onDocumentClick = (event: MouseEvent) => {
  const target = event.target as Node | null
  if (!target) return
  if (root.value?.contains(target) || menu.value?.contains(target)) return

  isOpen.value = false
}

const onWindowChange = () => {
  if (!isOpen.value) return
  updateMenuPosition()
}

watch(isOpen, (open) => {
  if (open) {
    window.addEventListener('resize', onWindowChange)
    window.addEventListener('scroll', onWindowChange, true)
    nextTick(updateMenuPosition)
  } else {
    window.removeEventListener('resize', onWindowChange)
    window.removeEventListener('scroll', onWindowChange, true)
  }
})

watch(() => props.disabled, (disabled) => {
  if (disabled) {
    isOpen.value = false
  }
})

watch(
  () => props.modelValue,
  (value) => {
    inputValue.value = value
  },
  { immediate: true }
)

onMounted(() => {
  document.addEventListener('click', onDocumentClick)
})

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