<template>
  <div class="space-y-1 search-select">
    <label v-if="$slots.label" class="label">
      <span class="label-text">
        <slot name="label" />
      </span>
    </label>
    <div class="relative">
      <input
        ref="inputRef"
        v-model="searchTerm"
        type="text"
        :placeholder="placeholder"
        :class="inputClasses"
        @focus="handleFocus"
        @keydown.down.prevent="highlightNext"
        @keydown.up.prevent="highlightPrevious"
        @keydown.enter.prevent="selectHighlighted"
        @input="handleInput"
      >
      <button
        v-if="clearable && modelValue"
        type="button"
        class="absolute top-1/2 -translate-y-1/2 right-8 btn btn-ghost btn-xs"
        aria-label="Effacer la sélection"
        @click.stop="clearSelection"
      >
        <IconLucideX class="w-3 h-3" aria-hidden="true" />
      </button>
      <button
        type="button"
        :class="toggleButtonClasses"
        @click="toggleDropdown"
        aria-label="Afficher les options"
      >
        <IconLucideChevronsUpDown class="w-4 h-4" aria-hidden="true" />
      </button>

      <transition name="fade">
        <div
          v-if="openDropdown"
          class="absolute z-30 mt-1 w-full max-h-60 overflow-y-auto bg-base-100 border border-base-200 rounded-box shadow-lg"
        >
          <div v-if="loading" class="flex items-center gap-2 px-3 py-2 text-xs text-base-content/50">
            <span class="loading loading-spinner loading-xs" />
            Recherche en cours…
          </div>
          <div v-else-if="displayedOptions.length === 0" class="px-3 py-2 text-xs text-base-content/50">
            {{ emptyText }}
          </div>
          <ul v-else class="flex flex-col">
            <li
              v-for="(option, index) in displayedOptions"
              :key="resolveValue(option) ?? index"
            >
              <button
                type="button"
                class="flex w-full flex-col items-start gap-1 px-3 py-2 text-left hover:bg-base-200 focus:bg-base-200 focus:outline-none"
                :class="{
                  'bg-base-200': isOptionSelected(option),
                  'bg-base-300/60': highlightedIndex === index
                }"
                @mouseenter="highlightedIndex = index"
                @mouseleave="highlightedIndex = -1"
                @click="selectOption(option)"
              >
                <span class="font-medium text-sm">
                  <slot name="option-label" :option="option">
                    {{ resolveLabel(option) }}
                  </slot>
                </span>
                <span v-if="$slots['option-description'] || resolveDescription(option)" class="text-xs text-base-content/50">
                  <slot name="option-description" :option="option">
                    {{ resolveDescription(option) }}
                  </slot>
                </span>
              </button>
            </li>
          </ul>
          <button
            v-if="creatableSuggestion"
            type="button"
            class="w-full text-left px-3 py-2 hover:bg-base-200 focus:bg-base-200 focus:outline-none text-xs text-base-content/70 border-t border-base-200 flex items-center gap-2"
            @click="confirmCreatable"
          >
            <IconLucidePlus class="w-3 h-3" aria-hidden="true" />
            Créer « {{ creatableSuggestion }} »
          </button>
        </div>
      </transition>
    </div>
  </div>
</template>

<script setup>
import { ref, computed, watch, onMounted, onBeforeUnmount } from 'vue'
import IconLucideChevronsUpDown from '~icons/lucide/chevrons-up-down'
import IconLucideX from '~icons/lucide/x'
import IconLucidePlus from '~icons/lucide/plus'

const props = defineProps({
  modelValue: {
    type: [String, Number],
    default: ''
  },
  options: {
    type: Array,
    default: () => []
  },
  placeholder: {
    type: String,
    default: 'Rechercher…'
  },
  emptyText: {
    type: String,
    default: 'Aucun résultat'
  },
  loading: {
    type: Boolean,
    default: false
  },
  optionValue: {
    type: [String, Function],
    default: 'id'
  },
  optionLabel: {
    type: [String, Function],
    default: 'name'
  },
  optionDescription: {
    type: [String, Function],
    default: null
  },
  clearable: {
    type: Boolean,
    default: false
  },
  size: {
    type: String,
    default: 'md',
    validator: (value) => ['xs', 'sm', 'md', 'lg'].includes(value)
  },
  maxVisible: {
    type: Number,
    default: 50
  },
  serverSearch: {
    type: Boolean,
    default: false
  },
  creatable: {
    type: Boolean,
    default: false
  }
})

const emit = defineEmits(['update:modelValue', 'search', 'focus'])

const searchTerm = ref('')
const openDropdown = ref(false)
const highlightedIndex = ref(-1)
const inputRef = ref(null)

const baseOptions = computed(() => Array.isArray(props.options) ? props.options : [])

const selectedOption = computed(() => {
  return baseOptions.value.find(option => isEqualValue(resolveValue(option), props.modelValue)) || null
})

const displayedOptions = computed(() => {
  const items = baseOptions.value.slice()

  const filtered = (!props.serverSearch && searchTerm.value.trim())
    ? items.filter((option) => {
      const term = searchTerm.value.trim().toLowerCase()
      const label = resolveLabel(option).toLowerCase()
      const description = resolveDescription(option)?.toLowerCase() || ''
      return label.includes(term) || description.includes(term)
    })
    : items

  if (props.maxVisible && filtered.length > props.maxVisible) {
    return filtered.slice(0, props.maxVisible)
  }

  return filtered
})

const creatableSuggestion = computed(() => {
  if (!props.creatable) return null
  const term = searchTerm.value.trim()
  if (!term) return null
  // Show "Créer ..." only if no option matches exactly (case-insensitive)
  const exists = baseOptions.value.some(option => {
    const label = resolveLabel(option).toLowerCase()
    return label === term.toLowerCase()
  })
  return exists ? null : term
})

const inputClasses = computed(() => {
  const pr = props.clearable && props.modelValue ? 'pr-16' : 'pr-10'
  const base = ['input', 'input-bordered', 'w-full', pr]
  if (props.size === 'xs') base.push('input-xs')
  if (props.size === 'sm') base.push('input-sm')
  if (props.size === 'lg') base.push('input-lg')
  return base.join(' ')
})

const toggleButtonClasses = computed(() => {
  const base = ['absolute', 'top-1/2', '-translate-y-1/2', 'right-2', 'btn', 'btn-ghost']
  if (props.size === 'xs' || props.size === 'sm') {
    base.push('btn-xs')
  } else {
    base.push('btn-sm')
  }
  return base.join(' ')
})

watch(
  () => props.modelValue,
  () => {
    if (props.creatable) {
      if (searchTerm.value !== props.modelValue) {
        searchTerm.value = String(props.modelValue ?? '')
      }
      return
    }
    if (!openDropdown.value) {
      searchTerm.value = selectedOption.value ? resolveLabel(selectedOption.value) : ''
    }
  },
  { immediate: true }
)

watch(
  baseOptions,
  (_newOptions) => {
    if (!openDropdown.value && selectedOption.value) {
      searchTerm.value = resolveLabel(selectedOption.value)
    }
  },
  { deep: true }
)

watch(openDropdown, (isOpen) => {
  if (isOpen) {
    highlightedIndex.value = -1
  }
})

function resolveValue (option) {
  if (!option) {
    return null
  }
  if (typeof props.optionValue === 'function') {
    return props.optionValue(option)
  }
  return option[props.optionValue]
}

function resolveLabel (option) {
  if (!option) {
    return ''
  }
  if (typeof props.optionLabel === 'function') {
    return props.optionLabel(option) || ''
  }
  return option[props.optionLabel] || ''
}

function resolveDescription (option) {
  if (!option || !props.optionDescription) {
    return ''
  }
  if (typeof props.optionDescription === 'function') {
    return props.optionDescription(option) || ''
  }
  return option[props.optionDescription] || ''
}

function isEqualValue (a, b) {
  if (a === b) {
    return true
  }
  return String(a ?? '') === String(b ?? '')
}

function isOptionSelected (option) {
  return isEqualValue(resolveValue(option), props.modelValue)
}

function selectOption (option) {
  emit('update:modelValue', resolveValue(option) ?? '')
  searchTerm.value = resolveLabel(option)
  openDropdown.value = false
}

function handleFocus () {
  openDropdown.value = true
  if (searchTerm.value === '' && selectedOption.value) {
    searchTerm.value = resolveLabel(selectedOption.value)
  }
  emit('focus')
}

function toggleDropdown () {
  openDropdown.value = !openDropdown.value
  if (openDropdown.value && selectedOption.value) {
    searchTerm.value = resolveLabel(selectedOption.value)
  }
  if (openDropdown.value && inputRef.value) {
    inputRef.value.focus()
  }
}

function handleInput () {
  if (!openDropdown.value) {
    openDropdown.value = true
  }
  if (props.creatable) {
    emit('update:modelValue', searchTerm.value)
  }
  emit('search', searchTerm.value)
}

function clearSelection () {
  emit('update:modelValue', '')
  searchTerm.value = ''
  openDropdown.value = false
}

function confirmCreatable () {
  if (creatableSuggestion.value) {
    emit('update:modelValue', creatableSuggestion.value)
  }
  openDropdown.value = false
}

function closeDropdown () {
  openDropdown.value = false
  if (props.creatable) {
    return // keep the typed text as-is
  }
  if (searchTerm.value.trim() === '' && selectedOption.value) {
    emit('update:modelValue', '')
  } else if (selectedOption.value) {
    searchTerm.value = resolveLabel(selectedOption.value)
  }
}

function highlightNext () {
  if (!openDropdown.value || displayedOptions.value.length === 0) {
    return
  }
  highlightedIndex.value = (highlightedIndex.value + 1) % displayedOptions.value.length
}

function highlightPrevious () {
  if (!openDropdown.value || displayedOptions.value.length === 0) {
    return
  }
  highlightedIndex.value =
    highlightedIndex.value <= 0
      ? displayedOptions.value.length - 1
      : highlightedIndex.value - 1
}

function selectHighlighted () {
  if (!openDropdown.value) {
    return
  }
  if (highlightedIndex.value >= 0 && highlightedIndex.value < displayedOptions.value.length) {
    selectOption(displayedOptions.value[highlightedIndex.value])
  }
}

const handleGlobalClick = (event) => {
  if (!openDropdown.value) {
    return
  }
  const target = event.target
  if (target?.closest?.('.search-select')) {
    return
  }
  closeDropdown()
}

onMounted(() => {
  window.addEventListener('click', handleGlobalClick)
  if (props.creatable) {
    searchTerm.value = String(props.modelValue ?? '')
  } else {
    searchTerm.value = selectedOption.value ? resolveLabel(selectedOption.value) : ''
  }
})

onBeforeUnmount(() => {
  window.removeEventListener('click', handleGlobalClick)
})
</script>

<style scoped>
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.12s ease;
}
.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
</style>
