<template>
  <q-select
    ref="selectRef"
    v-model="modelValueLocal"
    :options="filteredOptions"
    :option-label="optionLabel"
    :option-value="optionValue"
    :use-input="search"
    input-debounce="200"
    @filter="filterFn"
    @update:model-value="emitValue"
    v-bind="$attrs"
    ><template v-slot:append
      ><q-btn
        size="xs"
        flat
        dense
        round
        icon="search"
        :color="search ? 'primary' : 'grey'"
        @click="searchBox"
      >
      </q-btn
    ></template>
  </q-select>
</template>

<script setup lang="ts" generic="T extends Record<string, string | number | object>">
import type { PropType } from 'vue';
import { ref, watch } from 'vue';

const search = ref(false);
const selectRef = ref();

defineOptions({ inheritAttrs: false });

const props = defineProps({
  modelValue: {
    type: [String, Number, Object] as PropType<string | number | object | null>,
    default: null,
  },
  options: {
    type: Array as PropType<T[]>,
    required: true,
  },
  optionLabel: {
    type: [Function, String] as PropType<((option: T) => string) | string | undefined>,
    required: true,
  },
  optionValue: {
    type: String,
    required: true,
  },
});

const emit = defineEmits<{
  (e: 'update:modelValue', value: string | number | null): void;
}>();

const optionLabel = props.optionLabel;
const optionValue = props.optionValue;

const modelValueLocal = ref<string | number | object | null | undefined>(props.modelValue);
const filteredOptions = ref<T[]>([...(props.options || [])]);

function searchBox() {
  if (search.value) {
    selectRef.value.updateInputValue('');
  }
  search.value = !search.value;
}

watch(
  () => props.modelValue,
  (v) => (modelValueLocal.value = v),
);
watch(
  () => props.options,
  (o) => (filteredOptions.value = [...o]),
);

function filterFn(val: string, update: (fn: () => void) => void) {
  update(() => {
    if (!val) {
      filteredOptions.value = [...props.options];
      return;
    }
    const needle = val.toLowerCase();
    filteredOptions.value = props.options.filter((opt) => {
      let field = null;
      if (typeof optionLabel === 'function') {
        field = optionLabel(opt);
      } else {
        field = opt[String(optionLabel)];
      }

      if (typeof field !== 'string' && typeof field !== 'number') return false;
      return String(field).toLowerCase().includes(needle);
    });

    // Remove duplicates by optionValue
    const seen = new Set();
    filteredOptions.value = filteredOptions.value.filter((opt) => {
      const value = opt[optionValue];
      if (seen.has(value)) return false;
      seen.add(value);
      return true;
    });
  });
}

function emitValue(val: string | number | null) {
  emit('update:modelValue', val);
}
</script>
