<template>
  <AutoComplete
    :model-value="modelValue"
    :suggestions="suggestions"
    :option-label="optionLabel"
    :data-key="optionKey"
    :loading="loading"
    :min-length="minChars"
    :delay="debounceMs"
    :placeholder="placeholder"
    :disabled="disabled"
    :force-selection="true"
    :aria-label="ariaLabel"
    class="w-full async-autocomplete"
    @complete="onComplete"
    @update:model-value="onUpdate"
    @clear="onClear"
    @keydown="onKeydown"
  >
    <template #option="slotProps">
      <!-- eslint-disable-next-line vue/no-v-html -->
      <span class="async-ac-option" v-html="highlight(getLabel(slotProps.option))"></span>
    </template>

    <template #empty>
      <div class="async-ac-empty">
        <div class="async-ac-empty-text">Niciun rezultat găsit.</div>
        <Button
          v-if="emptyActionLabel"
          :label="emptyActionLabel"
          icon="pi pi-plus"
          size="small"
          text
          class="async-ac-empty-action"
          @click="$emit('emptyAction', lastQuery)"
        />
      </div>
    </template>
  </AutoComplete>
</template>

<script setup>
import { ref } from 'vue'
import AutoComplete from 'primevue/autocomplete'
import Button from 'primevue/button'

const props = defineProps({
  modelValue: { type: [Object, null], default: null },
  searchFn: { type: Function, required: true },
  optionLabel: { type: String, default: 'denumire' },
  optionKey: { type: String, default: 'id' },
  placeholder: { type: String, default: 'Caută...' },
  minChars: { type: Number, default: 2 },
  debounceMs: { type: Number, default: 300 },
  disabled: { type: Boolean, default: false },
  emptyActionLabel: { type: String, default: '' },
  ariaLabel: { type: String, default: '' },
})

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

const suggestions = ref([])
const loading = ref(false)
const lastQuery = ref('')

function getLabel(item) {
  if (item == null) return ''
  return String(item[props.optionLabel] ?? '')
}

function escapeHtml(s) {
  return s.replace(/[&<>"']/g, (c) => ({
    '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;',
  }[c]))
}

function escapeRegExp(s) {
  return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}

function highlight(label) {
  const safe = escapeHtml(label)
  const q = lastQuery.value.trim()
  if (!q) return safe
  const re = new RegExp(`(${escapeRegExp(escapeHtml(q))})`, 'ig')
  return safe.replace(re, '<strong>$1</strong>')
}

async function onComplete(event) {
  const q = (event?.query ?? '').trim()
  lastQuery.value = q
  emit('search', q)
  if (q.length < props.minChars) {
    suggestions.value = []
    return
  }
  loading.value = true
  try {
    const result = await props.searchFn(q)
    suggestions.value = Array.isArray(result) ? result : []
  } catch {
    suggestions.value = []
  } finally {
    loading.value = false
  }
}

function onUpdate(val) {
  // PrimeVue emits object (after select) or string (during typing with force-selection false).
  // With force-selection=true, committed value is always an object/null. Pass-through only
  // objects or null to the parent v-model.
  if (val && typeof val === 'object') {
    emit('update:modelValue', val)
  } else if (val == null || val === '') {
    emit('update:modelValue', null)
  }
  // During typing (string), do not emit — AutoComplete manages the input text internally.
}

function onClear() {
  suggestions.value = []
  lastQuery.value = ''
  emit('update:modelValue', null)
}

function onKeydown(event) {
  if (event.key === 'Escape') {
    onClear()
    return
  }
  if (event.key === 'Enter' && suggestions.value.length > 0 && !props.modelValue) {
    event.preventDefault()
    const first = suggestions.value[0]
    emit('update:modelValue', first)
  }
}
</script>

<style scoped>
.async-autocomplete {
  width: 100%;
}

.async-ac-option {
  display: block;
  font-size: var(--text-sm);
  color: var(--text-color);
  padding: var(--space-xs) 0;
}

.async-ac-option :deep(strong) {
  font-weight: var(--font-semibold);
  color: var(--text-color);
}

.async-ac-empty {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  padding: var(--space-sm) var(--space-md);
}

.async-ac-empty-text {
  font-size: var(--text-sm);
  color: var(--text-color-secondary);
}

.async-ac-empty-action {
  align-self: flex-start;
}
</style>
