<script setup lang="ts">
import { computed, nextTick, ref, watch } from 'vue'
import { ChevronDown } from 'lucide-vue-next'
import { cn } from '@/lib/utils'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import { Command, CommandGroup, CommandItem, CommandList } from '@/components/ui/command'
import { Input } from '@/components/ui/input'
import type { HTMLAttributes } from 'vue'

export interface Option {
  label: string
  value: string
}

interface Props {
  modelValue?: string | null
  options: Option[]
  placeholder?: string
  class?: HTMLAttributes['class']
  size?: 'sm' | 'default'
  disabled?: boolean
  filterFn?: (option: Option, search: string) => boolean
  helpMessage?: string
}

const props = withDefaults(defineProps<Props>(), {
  placeholder: 'Select...',
  size: 'default',
  disabled: false,
  filterFn: (option: Option, search: string) => {
    const query = search.toLowerCase().trim()
    return option.label.toLowerCase().includes(query) || option.value.toLowerCase().includes(query)
  },
})

const emit = defineEmits<{
  'update:modelValue': [value: string | null]
  search: [query: string]
}>()

const open = ref(false)
const search = ref('')
const inputRef = ref<HTMLInputElement>()
const commandRef = ref<HTMLElement>()
const hasUserInteracted = ref(false)

const selectedOption = computed(() => {
  if (!props.modelValue) return null
  return props.options.find((opt) => opt.value === props.modelValue) || null
})

const filteredOptions = computed(() => {
  if (!search.value.trim()) {
    return props.options
  }
  return props.options.filter((option) => props.filterFn(option, search.value))
})

const hasNoOptions = computed(() => props.options.length === 0)
const hasNoFilteredOptions = computed(() => filteredOptions.value.length === 0)

const displayValue = computed(() => {
  return open.value ? search.value : selectedOption.value?.label || ''
})

// Helper to check if element is inside popover
function isInPopover(element: HTMLElement | null): boolean {
  if (!element) return false
  return (
    commandRef.value?.contains(element) ||
    !!element.closest('[data-slot="popover-content"]') ||
    !!element.closest('[data-slot="command"]')
  )
}

// Open popover and focus input
function openAndFocus() {
  if (props.disabled) return
  open.value = true
  search.value = ''
  nextTick(() => {
    requestAnimationFrame(() => {
      inputRef.value?.focus()
      inputRef.value?.select()
    })
  })
}

let isSelecting = false

function selectOption(option: Option) {
  if (isSelecting) return
  isSelecting = true

  emit('update:modelValue', option.value)
  search.value = ''
  setTimeout(() => {
    open.value = false
    isSelecting = false
  }, 0)
}

function handleInputFocus() {
  // Only open on focus if it was triggered by user interaction (click/keyboard)
  // This prevents auto-focus from opening the popover on mount
  if (!props.disabled && hasUserInteracted.value) {
    open.value = true
    search.value = ''
  }
}

function handleInputBlur(event: FocusEvent) {
  const relatedTarget = event.relatedTarget as HTMLElement | null
  if (isInPopover(relatedTarget)) {
    return
  }

  // Delay closing to allow click events on items to process
  setTimeout(() => {
    if (!open.value) return
    if (isInPopover(document.activeElement as HTMLElement)) {
      return
    }
    search.value = ''
    open.value = false
  }, 200)
}

function handleInputKeyDown(event: KeyboardEvent) {
  if (event.key === 'Escape') {
    open.value = false
    search.value = ''
  } else {
    // Mark as user interaction for keyboard navigation (Tab, etc.)
    hasUserInteracted.value = true
  }
}

function handlePointerDownOutside(event: any) {
  const target = event.target as HTMLElement
  if (target?.closest('[data-slot="popover-trigger"]')) {
    event.preventDefault()
  }
}

function handleInputMouseDown(event: MouseEvent) {
  if (props.disabled) return
  event.stopPropagation()
  hasUserInteracted.value = true
  if (!open.value) {
    openAndFocus()
  } else {
    inputRef.value?.focus()
    inputRef.value?.select()
  }
}

function handleWrapperClick(event: MouseEvent) {
  const target = event.target as HTMLElement
  if (target !== inputRef.value && !inputRef.value?.contains(target)) {
    hasUserInteracted.value = true
    openAndFocus()
  }
}

watch(search, (newValue) => {
  emit('search', newValue)
})

watch(open, (isOpen) => {
  if (isOpen) {
    search.value = ''
    nextTick(() => {
      requestAnimationFrame(() => {
        inputRef.value?.focus()
        inputRef.value?.select()
      })
    })
  } else {
    search.value = ''
  }
})
</script>

<template>
  <Popover v-model:open="open">
    <PopoverTrigger as-child>
      <div
        :class="
          cn(
            'border-input data-[placeholder]:text-muted-foreground aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-full items-center justify-between gap-2 rounded-md border bg-transparent px-3 text-sm shadow-xs transition-[color,box-shadow] outline-none disabled:opacity-50 cursor-pointer',
            size === 'default' ? 'h-9' : 'h-8',
            props.class
          )
        "
        @click="handleWrapperClick"
      >
        <Input
          ref="inputRef"
          :model-value="displayValue"
          :placeholder="selectedOption && !open ? undefined : placeholder"
          :disabled="disabled"
          :class="
            cn(
              'border-0 p-0 shadow-none focus-visible:ring-0 focus-visible:ring-offset-0 bg-transparent flex-1 min-w-0 text-left cursor-text h-full',
              !open && selectedOption && 'cursor-pointer'
            )
          "
          @update:model-value="
            (val) => {
              if (open) search = String(val)
            }
          "
          @focus="handleInputFocus"
          @blur="handleInputBlur"
          @keydown="handleInputKeyDown"
          @mousedown="handleInputMouseDown"
          @click.stop
        />
        <div class="flex items-center gap-1 shrink-0">
          <ChevronDown class="size-4 opacity-50 shrink-0 pointer-events-none" />
        </div>
      </div>
    </PopoverTrigger>
    <PopoverContent
      class="w-full max-w-[550px] p-0 admin-select-content"
      align="start"
      @pointer-down-outside="handlePointerDownOutside"
    >
      <div
        v-if="helpMessage"
        class="sticky top-0 z-10 px-3 py-1 border-b border-border admin-field-description"
      >
        {{ helpMessage }}
      </div>
      <Command ref="commandRef" :should-filter="false">
        <CommandList class="max-w-full">
          <div v-if="hasNoFilteredOptions" class="px-2 py-6 text-center text-sm">
            {{ hasNoOptions ? 'No options available.' : 'No options found.' }}
          </div>
          <CommandGroup v-else>
            <CommandItem
              :data-selected="selectedOption?.value === option.value"
              v-for="option in filteredOptions"
              :key="option.value"
              :value="option.value"
              class="py-1 admin-select-item"
              @select.stop="() => selectOption(option)"
              @mousedown.prevent
            >
              <span>{{ option.label }}</span>
            </CommandItem>
          </CommandGroup>
        </CommandList>
      </Command>
    </PopoverContent>
  </Popover>
</template>
