<template>
  <div>
    <Combobox v-model="value">
      <ComboboxLabel class="label">
        <span class="label-text">{{ label }}</span>
      </ComboboxLabel>
      <div class="relative">
        <ComboboxInput
          :display-value="i => extractDisplay(i as SupportValues)"
          class="w-full input input-bordered"
          @change="search = $event.target.value"
        />
        <button
          v-if="!!value"
          type="button"
          class="absolute inset-y-0 right-6 flex items-center rounded-r-md px-2 focus:outline-none"
          @click="clear"
        >
          <Icon name="mdi-close" class="w-5 h-5" />
        </button>
        <ComboboxButton class="absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none">
          <Icon name="mdi-chevron-down" class="w-5 h-5" />
        </ComboboxButton>
        <ComboboxOptions
          v-if="computedItems.length > 0"
          class="absolute dropdown-content z-10 mt-2 max-h-60 w-full overflow-auto rounded-md card bg-base-100 border border-gray-400"
        >
          <ComboboxOption
            v-for="item in computedItems"
            :key="item.id"
            v-slot="{ active, selected }"
            :value="item.value"
            as="template"
          >
            <li
              :class="[
                'relative cursor-default select-none py-2 pl-3 pr-9 duration-75 ease-in-out transition-colors',
                active ? 'bg-primary text-primary-content' : 'text-base-content',
              ]"
            >
              <slot name="display" v-bind="{ item: item, selected, active }">
                <span :class="['block truncate', selected && 'font-semibold']">
                  {{ item.display }}
                </span>
                <span
                  v-if="selected"
                  :class="[
                    'absolute inset-y-0 right-0 flex text-primary items-center pr-4',
                    active ? 'text-primary-content' : 'bg-primary',
                  ]"
                >
                  <Icon name="mdi-check" class="h-5 w-5" aria-hidden="true" />
                </span>
              </slot>
            </li>
          </ComboboxOption>
        </ComboboxOptions>
      </div>
    </Combobox>
  </div>
</template>

<script setup lang="ts">
  import {
    Combobox,
    ComboboxInput,
    ComboboxOptions,
    ComboboxOption,
    ComboboxButton,
    ComboboxLabel,
  } from "@headlessui/vue";

  type SupportValues = string | { [key: string]: any };

  type ComboItem = {
    display: string;
    value: SupportValues;
    id: number;
  };

  type Props = {
    label: string;
    modelValue: SupportValues | null | undefined;
    items: string[] | object[];
    display?: string;
    multiple?: boolean;
  };

  const emit = defineEmits(["update:modelValue", "update:search"]);
  const props = withDefaults(defineProps<Props>(), {
    label: "",
    modelValue: "",
    display: "text",
    multiple: false,
  });

  function clear() {
    emit("update:modelValue", null);
  }

  const search = ref("");
  const value = useVModel(props, "modelValue", emit);

  function extractDisplay(item?: SupportValues): string {
    if (!item) {
      return "";
    }

    if (typeof item === "string") {
      return item;
    }

    if (props.display in item) {
      return item[props.display] as string;
    }

    // Try these options as well
    const fallback = ["name", "title", "display", "value"];
    for (let i = 0; i < fallback.length; i++) {
      const key = fallback[i];
      if (key in item) {
        return item[key] as string;
      }
    }

    return "";
  }

  const computedItems = computed<ComboItem[]>(() => {
    const list: ComboItem[] = [];

    for (let i = 0; i < props.items.length; i++) {
      const item = props.items[i];

      const out: Partial<ComboItem> = {
        id: i,
        value: item,
      };

      switch (typeof item) {
        case "string":
          out.display = item;
          break;
        case "object":
          // @ts-ignore - up to the user to provide a valid display key
          out.display = item[props.display] as string;
          break;
        default:
          out.display = "";
          break;
      }

      if (search.value && out.display) {
        const foldSearch = search.value.toLowerCase();
        const foldDisplay = out.display.toLowerCase();

        if (foldDisplay.startsWith(foldSearch)) {
          list.push(out as ComboItem);
        }

        continue;
      }

      list.push(out as ComboItem);
    }

    return list;
  });
</script>