<template>
  <ClientOnly>
    <USelectMenu
      v-model:search-term="searchTerm"
      v-model:open="isOpen"
      :model-value="modelValue"
      :items="items"
      ignore-filter
      :value-key="valueKey"
      :label-key="labelKey"
      :placeholder="placeholder"
      :multiple="multiple"
      :ui="{ viewport: viewportId }"
      @update:model-value="$emit('update:modelValue', $event)"
    >
      <template v-if="props.multiple ? modelValue?.length : modelValue">
        <span class="truncate pointer-events-none">
          {{ cachedLabel }}
        </span>
      </template>

      <template v-if="slots['item-label']" #item-label="{ item }">
        <slot name="item-label" :item="item" />
      </template>

      <template #trailing>
        <UIcon v-if="isLoading" :name="appConfig.ui.icons.loading" class="shrink-0 text-dimmed size-5 animate-spin" />
        <template v-else>
          <UButton
            v-if="props.multiple ? modelValue?.length : modelValue"
            :icon="appConfig.ui.icons.close"
            variant="link"
            size="sm"
            color="neutral"
            class="px-0"
            @click="$emit('update:modelValue', void 0)"
          />
        </template>
      </template>
    </USelectMenu>

    <template #placeholder>
      <USkeleton class="w-full h-8" />
    </template>
  </ClientOnly>
</template>

<script>
import { useAppConfig, useAsyncData, useNuxtData } from "#imports";
import { refDebounced, useInfiniteScroll, useOffsetPagination } from "@vueuse/core";
import { computed, ref, shallowRef, toRaw, triggerRef, useId, watch } from "vue";
</script>

<script setup>
const props = defineProps({
  modelValue: { type: null, required: false },
  cacheKey: { type: String, required: true },
  handler: { type: Function, required: true },
  valueKey: { type: null, required: false, default: "id" },
  labelKey: { type: null, required: false, default: "name" },
  multiple: { type: Boolean, required: false },
  placeholder: { type: String, required: false },
  initSelected: { type: Boolean, required: false }
});
defineEmits(["update:modelValue"]);
const slots = defineSlots();
const appConfig = useAppConfig();
const autocompleteSelectKey = `autocomplete-select:${props.cacheKey}`;
const autocompleteSelectId = `${autocompleteSelectKey}:${useId()}`;
const viewportId = `autocomplete-select-viewport-${useId()}`;
const {
  initialPagination,
  initialData,
  initialStatus,
  initSelected,
  initSelectedStatus
} = useInitialData();
const { fetchPage, nextPage, isLastPage, pageDataStatus, allPagesData } = usePageData({
  pagination: initialPagination,
  total: () => initialData.value?.pagination.total ?? 0
});
const { searchTerm, searchData, searchStatus } = useSearch({ pagination: initialPagination });
const { isOpen } = useInfiniteScrollData({ nextPage, isLastPage, fetchPage, searchTerm });
const isLoading = computed(() => [
  initialStatus.value,
  pageDataStatus.value,
  searchStatus.value,
  initSelectedStatus.value
].includes("pending"));
const items = computed(() => {
  if (searchTerm.value) {
    return searchData.value?.items ?? [];
  }
  return [
    ...initialData.value?.items ?? [],
    ...allPagesData.value
  ];
});
const { cachedLabel } = useAutocompleteCache({ items, initSelected });
function useInitialData() {
  const initialPagination2 = { page: "1", perPage: "25" };
  const { data: initialData2, status: initialStatus2 } = useAsyncData(
    autocompleteSelectKey,
    () => props.handler({ ...initialPagination2, search: "" }),
    {
      lazy: true,
      getCachedData: (key) => useNuxtData(key).data.value,
      dedupe: "defer"
    }
  );
  const { data: initSelected2, status: initSelectedStatus2 } = useAsyncData(
    `${autocompleteSelectId}:initial-value`,
    async () => {
      const result = await props.handler({
        ...initialPagination2,
        search: "",
        ids: Array.isArray(props.modelValue) ? props.modelValue : [props.modelValue]
      });
      return result.items;
    },
    {
      lazy: true,
      immediate: props.initSelected && (Array.isArray(props.modelValue) ? props.modelValue.length > 0 : !!props.modelValue),
      default: () => []
    }
  );
  return {
    initialPagination: initialPagination2,
    initialData: initialData2,
    initialStatus: initialStatus2,
    initSelected: initSelected2,
    initSelectedStatus: initSelectedStatus2
  };
}
function usePageData(options) {
  const { currentPage, currentPageSize, next, isLastPage: isLastPage2 } = useOffsetPagination({
    total: options.total,
    pageSize: Number(options.pagination.perPage),
    page: Number(options.pagination.page)
  });
  const { execute: fetchPage2, data: pageData, status: pageDataStatus2 } = useAsyncData(
    `${autocompleteSelectId}:page-data`,
    () => {
      return props.handler({
        page: currentPage.value.toString(),
        perPage: currentPageSize.value.toString(),
        search: ""
      });
    },
    { immediate: false }
  );
  const allPagesData2 = shallowRef([]);
  watch(pageData, (value) => {
    if (value?.items) {
      allPagesData2.value.push(...value.items);
      triggerRef(allPagesData2);
    }
  });
  return { fetchPage: fetchPage2, pageData, nextPage: next, isLastPage: isLastPage2, pageDataStatus: pageDataStatus2, allPagesData: allPagesData2 };
}
function useInfiniteScrollData(options) {
  const isOpen2 = ref(false);
  const contentDiv = shallowRef(null);
  watch(isOpen2, (value) => {
    if (value) {
      contentDiv.value = document.querySelector(`.${viewportId}`) ?? null;
    }
  }, { flush: "post" });
  useInfiniteScroll(contentDiv, () => {
    options.nextPage();
    return fetchPage();
  }, { distance: 10, canLoadMore: () => !searchTerm.value && !isLastPage.value });
  return { isOpen: isOpen2 };
}
function useSearch(options) {
  const searchKey = `${autocompleteSelectId}:search`;
  const searchTerm2 = ref("");
  const searchTermDebounced = refDebounced(searchTerm2, 500);
  const { data: searchData2, status: searchStatus2, execute: search } = useAsyncData(searchKey, (_, { signal }) => {
    return props.handler({
      ...options.pagination,
      search: searchTermDebounced.value
    }, signal);
  }, { lazy: true, immediate: false });
  watch(searchTermDebounced, (value) => {
    value && search();
  });
  return { searchData: searchData2, searchStatus: searchStatus2, searchTerm: searchTerm2 };
}
function useAutocompleteCache(options) {
  const selectedItemCache = shallowRef(/* @__PURE__ */ new Map());
  const updateCache = (value) => {
    if (!value || selectedItemCache.value.has(value)) {
      return;
    }
    const item = options.initSelected.value.find((item2) => item2[props.valueKey] === value) || options.items.value.find((item2) => item2[props.valueKey] === value);
    if (item) {
      selectedItemCache.value.set(value, structuredClone(toRaw(item)));
    }
  };
  watch(
    () => [props.modelValue, options.items.value, options.initSelected.value],
    ([value]) => {
      const cacheSize = selectedItemCache.value.size;
      if (Array.isArray(value)) {
        value.forEach(updateCache);
      } else {
        updateCache(value);
      }
      cacheSize !== selectedItemCache.value.size && triggerRef(selectedItemCache);
    },
    { immediate: true }
  );
  const getCachedLabel = (value) => {
    if (Array.isArray(value)) {
      return value.map(getCachedLabel).join(", ");
    }
    return selectedItemCache.value.get(value)?.[props.labelKey] ?? value;
  };
  const cachedLabel2 = computed(() => {
    return props.modelValue && getCachedLabel(props.modelValue);
  });
  return { selectedItemCache, getCachedLabel, cachedLabel: cachedLabel2 };
}
</script>
