<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 { Badge } from '@/components/ui/badge'
import type { HTMLAttributes } from 'vue'

export interface Option {
  label: string
  value: string
  description?: string
}

interface Props {
  modelValue: string[]
  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) ||
      (option.description?.toLowerCase().includes(query) ?? false)
    )
  },
})

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

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

const selectedCount = computed(() => props.modelValue.length)

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)

function isSelected(value: string): boolean {
  return props.modelValue.includes(value)
}

function toggleOption(value: string) {
  if (isSelected(value)) {
    emit(
      'update:modelValue',
      props.modelValue.filter((v) => v !== value)
    )
  } else {
    emit('update:modelValue', [...props.modelValue, value])
  }
}

function removeOption(value: string) {
  emit(
    'update:modelValue',
    props.modelValue.filter((v) => v !== value)
  )
}

function getOptionLabel(value: string): string {
  const option = props.options.find((opt) => opt.value === value)
  return option?.label || value
}

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

function handleInputFocus() {
  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
  }

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

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()
      })
    })
  } else {
    search.value = ''
  }
})
</script>

<template>
  <div class="space-y-2">
    <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="open ? search : ''"
            :placeholder="selectedCount > 0 ? `${selectedCount} selected` : 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 && 'cursor-pointer',
                selectedCount > 0 && !open && 'placeholder:text-foreground'
              )
            "
            @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 max-h-60">
            <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
                v-for="option in filteredOptions"
                :key="option.value"
                :value="option.value"
                class="py-1.5 admin-select-item cursor-pointer"
                @select.stop="() => toggleOption(option.value)"
                @mousedown.prevent
              >
                <div class="flex items-center gap-2 flex-1">
                  <div
                    class="w-4 h-4 border rounded flex items-center justify-center flex-shrink-0 admin-checkbox"
                    :class="isSelected(option.value) ? 'checked' : ''"
                    :data-state="isSelected(option.value) ? 'checked' : 'unchecked'"
                  >
                    <i v-if="isSelected(option.value)" class="dku-icon-checkmark-12"></i>
                  </div>
                  <div class="flex-1 min-w-0">
                    <div class="truncate font-medium text-sm">{{ option.label }}</div>
                    <div v-if="option.description" class="text-xs text-muted-foreground truncate">
                      {{ option.description }}
                    </div>
                  </div>
                </div>
              </CommandItem>
            </CommandGroup>
          </CommandList>
        </Command>
      </PopoverContent>
    </Popover>

    <!-- Selected items as badges -->
    <div v-if="selectedCount > 0" class="flex flex-wrap gap-1">
      <Badge v-for="value in modelValue" :key="value" variant="secondary" class="text-xs group">
        <slot name="badge-icon">
          <i class="dku-icon-briefcase-12 mr-1"></i>
        </slot>
        {{ getOptionLabel(value) }}
        <i @click="removeOption(value)" class="dku-icon-dismiss-12 ml-1 cursor-pointer"></i>
      </Badge>
    </div>
  </div>
</template>
