<template>
  <div ref="root" class="relative w-full">
    <button
      ref="trigger"
      type="button"
      class="w-full flex justify-between rounded-md border border-neutral-300 bg-white px-3 py-2 text-left text-sm text-neutral-900 focus:outline-none focus:border-primary-500 disabled:cursor-not-allowed disabled:bg-neutral-100 disabled:text-neutral-500"
      :disabled="props.disabled"
      @click="toggleOpen"
    >
      {{ displayValue }}
        <Icon name="mdi:chevron-down" class="self-center"/>
    </button>
  </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 timeSlots"
        :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>
    </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 isOpen = ref(false)
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 displayValue = computed(() => props.modelValue || props.placeholder)

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 selectValue = (value: string) => {
  if (props.disabled) return
  emit('update:modelValue', value)
  isOpen.value = false
}

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
  }
})

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

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