import { computed, ref, shallowRef, watch } from "vue";
import { watchDebounced } from "@vueuse/core";
import { union, isEmpty, isArray, sortBy, castArray } from "lodash-es";
import { useFuzzy } from "./utils/fuzzy.js";
import { useApiModelOperation } from "./apiModelOperation.js";
export function useApiLookupList(props, emit, selectedItems) {
  const modelItems = shallowRef([]);
  const items = shallowRef([]);
  const selectedItemsObject = shallowRef([]);
  const searchData = ref("");
  const isLoading = ref(false);
  const isErrorLoading = ref(false);
  const statusField = computed(() => props.statusField ?? "statusCode");
  const activeValue = computed(() => props.activeValue ?? "active");
  const hideInactiveInList = computed(() => props.hideInactiveInList ?? false);
  const queryFields = computed(() => {
    let fieldsArray = [props.itemTitle, props.itemValue];
    if (props.fields) fieldsArray = union(fieldsArray, props.fields);
    if (hideInactiveInList.value && statusField.value) {
      fieldsArray = union(fieldsArray, [statusField.value]);
    }
    return fieldsArray;
  });
  const computedFilterKeys = computed(() => {
    if (props.filterKeys) return props.filterKeys;
    return ["title", "raw." + props.itemValue];
  });
  const computedPlaceholder = computed(() => {
    if (!props.serverSearch) return props.placeholder;
    return isLoading.value ? props.serverSearchLoadingText : props.serverSearchText;
  });
  const computedNoDataText = computed(() => {
    if (!props.serverSearch) return props.noDataText;
    if (isLoading.value) return props.serverSearchLoadingText;
    if (!searchData.value) return props.serverSearchText;
    return props.noDataText;
  });
  let requestId = 0;
  async function loadItems() {
    if (!props.modelName) {
      modelItems.value = [];
      items.value = [];
      return;
    }
    const id = ++requestId;
    isLoading.value = true;
    const variables = { ...props.modelBy || {} };
    if (props.serverSearch && props.serverSearchKey) {
      variables[props.serverSearchKey] = searchData.value;
    }
    try {
      const ops = useApiModelOperation(props.modelName);
      const result = await ops.read(queryFields.value, variables, props.cache);
      if (id !== requestId) return;
      if (isArray(result)) {
        modelItems.value = result;
        items.value = result;
        isErrorLoading.value = false;
      } else {
        isErrorLoading.value = true;
        modelItems.value = [];
        items.value = [];
      }
    } catch {
      if (id !== requestId) return;
      isErrorLoading.value = true;
      modelItems.value = [];
      items.value = [];
    } finally {
      if (id === requestId) isLoading.value = false;
    }
  }
  function applyFuzzy() {
    if (!props.fuzzy) return;
    if (isEmpty(searchData.value)) {
      items.value = modelItems.value;
      return;
    }
    const results = useFuzzy(searchData.value, modelItems.value, queryFields.value);
    const output = [];
    if (results.value?.length) {
      for (let index = 0; index < results.value.length; index++) {
        const result = results.value[index];
        if (result?.item) output.push(result.item);
      }
    }
    items.value = output;
  }
  const itemMap = computed(() => {
    const m = /* @__PURE__ */ new Map();
    for (const it of modelItems.value) {
      m.set(it?.[props.itemValue], it);
    }
    return m;
  });
  async function syncSelectedFromModelValue() {
    const modelValueData = selectedItems.value;
    const values = castArray(modelValueData).filter(
      (value) => value !== void 0 && value !== null && value !== ""
    );
    if (!values.length) {
      emit("update:selectedObject", props.multiple ? [] : void 0);
      selectedItemsObject.value = [];
      return;
    }
    const alreadyInObject = selectedItemsObject.value?.filter((it) => values.includes(it?.[props.itemValue])) || [];
    const haveSet = new Set(alreadyInObject.map((it) => it?.[props.itemValue]));
    const missingValues = values.filter((v) => !haveSet.has(v));
    const stillMissing = [];
    for (const v of missingValues) {
      const localHit = itemMap.value.get(v);
      if (localHit) {
        alreadyInObject.push(localHit);
        haveSet.add(v);
      } else {
        stillMissing.push(v);
      }
    }
    if (stillMissing.length && props.modelSelectedItem) {
      try {
        const key = props.modelSelectedItemKey || "id";
        const variables = { ...props.modelSelectedItemBy || {} };
        variables[key] = stillMissing;
        const ops = useApiModelOperation(props.modelSelectedItem);
        const result = await ops.read(queryFields.value, variables, props.cache);
        const resolved = castArray(result);
        for (const obj of resolved) {
          const v = obj?.[props.itemValue];
          if (!haveSet.has(v)) {
            alreadyInObject.push(obj);
            haveSet.add(v);
          }
        }
      } catch {
      }
    }
    selectedItemsObject.value = alreadyInObject;
    emit("update:selectedObject", props.multiple ? alreadyInObject : alreadyInObject[0]);
  }
  watchDebounced(
    () => [
      props.modelName,
      props.serverSearch,
      props.serverSearchKey,
      props.cache,
      props.modelBy,
      queryFields.value
    ],
    () => loadItems(),
    { immediate: true, deep: true, debounce: 30, maxWait: 200 }
  );
  watchDebounced(
    searchData,
    () => {
      if (props.serverSearch) loadItems();
      else applyFuzzy();
    },
    { debounce: props.serverSearch ? props.serverSearchDebounce ?? 500 : 300, maxWait: 1500 }
  );
  watch(
    () => modelItems.value,
    () => {
      if (!props.serverSearch) applyFuzzy();
    }
  );
  watch(
    () => selectedItems.value,
    () => {
      syncSelectedFromModelValue();
    },
    { immediate: true }
  );
  const computedItems = computed(() => {
    const sortByField = !props.sortBy || typeof props.sortBy === "string" ? [props.sortBy || (props.showCode ? props.itemValue : props.itemTitle)] : props.sortBy;
    const baseSource = (props.fuzzy || props.serverSearch && !props.searchSearchSort) && !isEmpty(searchData.value) ? items.value : sortBy(items.value, sortByField);
    let baseItems = [...baseSource];
    if (hideInactiveInList.value) {
      baseItems = baseItems.filter((it) => it?.[statusField.value] === activeValue.value);
    }
    for (const selectedItem of selectedItemsObject.value || []) {
      const v = selectedItem?.[props.itemValue];
      if (!baseItems.find((x) => x?.[props.itemValue] === v)) {
        baseItems.push(selectedItem);
      }
    }
    return baseItems;
  });
  function isSelectableValue(value) {
    if (!hideInactiveInList.value) return true;
    const local = itemMap.value.get(value);
    const obj = local ?? selectedItemsObject.value.find((it) => it?.[props.itemValue] === value) ?? modelItems.value.find((it) => it?.[props.itemValue] === value);
    if (!obj) return true;
    return obj?.[statusField.value] === activeValue.value;
  }
  function setModelValueGuarded(val) {
    if (!(props.preventSelectingInactive ?? false)) {
      selectedItems.value = val;
      return;
    }
    if (!props.multiple) {
      if (val != null && !isSelectableValue(val)) return;
      selectedItems.value = val;
      return;
    }
    const arr = Array.isArray(val) ? val : [];
    selectedItems.value = arr.filter((v) => isSelectableValue(v));
  }
  const onUpdateModelValue = (val) => setModelValueGuarded(val);
  return {
    // state
    modelItems,
    items,
    searchData,
    isLoading,
    isErrorLoading,
    // derived
    queryFields,
    computedFilterKeys,
    computedPlaceholder,
    computedNoDataText,
    computedItems,
    // selection
    selectedItemsObject,
    syncSelectedFromModelValue,
    // selection policy helper
    isSelectableValue,
    // guarded setter for v-model updates
    setModelValueGuarded,
    onUpdateModelValue,
    // actions
    loadItems,
    applyFuzzy
  };
}
