<script>
import theme from "#build/b24ui/listbox";
</script>

<script setup>
import { computed, toRaw, toRef } from "vue";
import { ListboxRoot, ListboxContent, ListboxGroup, ListboxGroupLabel, ListboxVirtualizer, ListboxItem as RekaListboxItem, ListboxItemIndicator, ListboxFilter } from "reka-ui";
import { useForwardProps } from "../composables/useForwardProps";
import { createReusableTemplate, reactivePick } from "@vueuse/core";
import { defu } from "defu";
import { useAppConfig } from "#imports";
import { useComponentProps } from "../composables/useComponentProps";
import { useFilter } from "../composables/useFilter";
import { useFormField } from "../composables/useFormField";
import { useLocale } from "../composables/useLocale";
import { get, isArrayOfArray, looseToNumber } from "../utils";
import { getEstimateSize } from "../utils/virtualizer";
import { tv } from "../utils/tv";
import icons from "../dictionary/icons";
import B24Avatar from "./Avatar.vue";
import B24Chip from "./Chip.vue";
import B24Input from "./Input.vue";
defineOptions({ inheritAttrs: false });
const _props = defineProps({
  id: { type: String, required: false },
  as: { type: null, required: false },
  color: { type: null, required: false },
  size: { type: null, required: false },
  items: { type: null, required: false },
  modelValue: { type: null, required: false },
  modelModifiers: { type: null, required: false },
  defaultValue: { type: null, required: false },
  multiple: { type: Boolean, required: false },
  valueKey: { type: null, required: false },
  labelKey: { type: null, required: false, default: "label" },
  descriptionKey: { type: null, required: false, default: "description" },
  loading: { type: Boolean, required: false },
  loadingIcon: { type: [Function, Object], required: false },
  filter: { type: [Boolean, Object], required: false, default: false },
  filterFields: { type: Array, required: false },
  ignoreFilter: { type: Boolean, required: false },
  selectedIcon: { type: [Function, Object], required: false },
  virtualize: { type: [Boolean, Object], required: false, default: false },
  highlight: { type: Boolean, required: false },
  autofocus: { type: Boolean, required: false },
  autofocusDelay: { type: Number, required: false, default: 0 },
  class: { type: null, required: false },
  b24ui: { type: Object, required: false },
  by: { type: [String, Function], required: false },
  disabled: { type: Boolean, required: false },
  highlightOnHover: { type: Boolean, required: false, default: true },
  name: { type: String, required: false },
  orientation: { type: String, required: false },
  required: { type: Boolean, required: false },
  selectionBehavior: { type: String, required: false }
});
const emits = defineEmits(["entryFocus", "highlight", "leave", "change", "update:modelValue"]);
const slots = defineSlots();
const props = useComponentProps("listbox", _props);
const searchTerm = defineModel("searchTerm", { type: String, ...{ default: "" } });
const { t } = useLocale();
const appConfig = useAppConfig();
const { filterGroups } = useFilter();
const rootProps = useForwardProps(reactivePick(props, "as", "modelValue", "defaultValue", "multiple", "selectionBehavior", "highlightOnHover", "by", "orientation", "required"), emits);
const virtualizerProps = toRef(() => {
  if (!props.virtualize) return false;
  return defu(typeof props.virtualize === "boolean" ? {} : props.virtualize, {
    estimateSize: getEstimateSize(filteredItems.value, size.value || "md", props.descriptionKey, !!slots["item-description"])
  });
});
const inputProps = toRef(() => defu(typeof props.filter === "object" ? props.filter : {}, { placeholder: t("listbox.search"), variant: "none" }));
const { emitFormChange, emitFormInput, name, size: formFieldSize, id, highlight: formFieldHighlight, disabled: formFieldDisabled, ariaAttrs } = useFormField(_props, { bind: false });
const size = computed(() => formFieldSize.value ?? props.size);
const highlight = computed(() => formFieldHighlight.value ?? props.highlight);
const disabled = computed(() => formFieldDisabled.value ?? props.disabled);
const [DefineItemTemplate, ReuseItemTemplate] = createReusableTemplate({
  props: {
    item: {
      type: Object,
      required: true
    },
    index: {
      type: Number,
      required: false
    }
  }
});
const b24ui = computed(() => tv({ extend: theme, ...appConfig.b24ui?.listbox || {} })({
  // @todo error ts: fix this -> this for input ?? color
  // color: color.value ?? props.color,
  color: props.color,
  size: size.value,
  highlight: highlight.value,
  disabled: disabled.value,
  virtualize: !!props.virtualize
}));
function onUpdate(value) {
  if (toRaw(props.modelValue) === value) {
    return;
  }
  if (props.modelModifiers?.trim && (typeof value === "string" || value === null || value === void 0)) {
    value = value?.trim() ?? null;
  }
  if (props.modelModifiers?.number) {
    value = looseToNumber(value);
  }
  if (props.modelModifiers?.nullable) {
    value ??= null;
  }
  if (props.modelModifiers?.optional && !props.modelModifiers?.nullable && value !== null) {
    value ??= void 0;
  }
  const event = new Event("change", { target: { value } });
  emits("change", event);
  emitFormChange();
  emitFormInput();
}
function onSelect(e, item) {
  if (item.disabled) {
    e.preventDefault();
    return;
  }
  item.onSelect?.(e);
}
const groups = computed(
  () => props.items?.length ? isArrayOfArray(props.items) ? props.items : [props.items] : []
);
function isStructuralItem(item) {
  return !!item.type && ["label", "separator"].includes(item.type);
}
const filteredGroups = computed(() => {
  if (props.ignoreFilter || !searchTerm.value) {
    return groups.value;
  }
  const fields = Array.isArray(props.filterFields) ? props.filterFields : [props.labelKey];
  return filterGroups(groups.value, searchTerm.value, {
    fields,
    isStructural: isStructuralItem
  });
});
const filteredItems = computed(() => filteredGroups.value.flatMap((group) => group));
</script>

<template>
  <DefineItemTemplate v-slot="{ item, index }">
    <ListboxGroupLabel v-if="item.type === 'label'" data-slot="label" :class="b24ui.label({ class: [props.b24ui?.label, item.b24ui?.label, item.class] })">
      {{ get(item, props.labelKey) }}
    </ListboxGroupLabel>

    <div v-else-if="item.type === 'separator'" role="separator" data-slot="separator" :class="b24ui.separator({ class: [props.b24ui?.separator, item.b24ui?.separator, item.class] })" />

    <RekaListboxItem
      v-else
      :value="props.valueKey ? get(item, props.valueKey) : item"
      :disabled="item.disabled"
      data-slot="item"
      :class="b24ui.item({ class: [props.b24ui?.item, item.b24ui?.item, item.class] })"
      @select="onSelect($event, item)"
    >
      <slot name="item" :item="item" :index="index" :b24ui="b24ui">
        <slot name="item-leading" :item="item" :index="index" :b24ui="b24ui">
          <Component
            :is="item.icon"
            v-if="item.icon"
            data-slot="itemLeadingIcon"
            :class="b24ui.itemLeadingIcon({ class: props.b24ui?.itemLeadingIcon })"
          />
          <B24Avatar v-else-if="item.avatar" :size="item.b24ui?.itemLeadingAvatarSize || props.b24ui?.itemLeadingAvatarSize || b24ui.itemLeadingAvatarSize()" v-bind="item.avatar" data-slot="itemLeadingAvatar" :class="b24ui.itemLeadingAvatar({ class: [props.b24ui?.itemLeadingAvatar, item.b24ui?.itemLeadingAvatar] })" />
          <B24Chip
            v-else-if="item.chip"
            :size="item.b24ui?.itemLeadingChipSize || props.b24ui?.itemLeadingChipSize || b24ui.itemLeadingChipSize()"
            inset
            standalone
            v-bind="item.chip"
            data-slot="itemLeadingChip"
            :class="b24ui.itemLeadingChip({ class: [props.b24ui?.itemLeadingChip, item.b24ui?.itemLeadingChip] })"
          />
        </slot>

        <span v-if="get(item, props.labelKey) || get(item, props.descriptionKey) || !!slots['item-label'] || !!slots['item-description']" data-slot="itemWrapper" :class="b24ui.itemWrapper({ class: [props.b24ui?.itemWrapper, item.b24ui?.itemWrapper] })">
          <span v-if="get(item, props.labelKey) || !!slots['item-label']" data-slot="itemLabel" :class="b24ui.itemLabel({ class: [props.b24ui?.itemLabel, item.b24ui?.itemLabel] })">
            <slot name="item-label" :item="item" :index="index">
              {{ get(item, props.labelKey) }}
            </slot>
          </span>

          <span v-if="get(item, props.descriptionKey) || !!slots['item-description']" data-slot="itemDescription" :class="b24ui.itemDescription({ class: [props.b24ui?.itemDescription, item.b24ui?.itemDescription] })">
            <slot name="item-description" :item="item" :index="index">
              {{ get(item, props.descriptionKey) }}
            </slot>
          </span>
        </span>

        <span data-slot="itemTrailing" :class="b24ui.itemTrailing({ class: [props.b24ui?.itemTrailing, item.b24ui?.itemTrailing] })">
          <slot name="item-trailing" :item="item" :index="index" :b24ui="b24ui" />

          <ListboxItemIndicator as-child>
            <Component :is="props.selectedIcon || icons.check" data-slot="itemTrailingIcon" :class="b24ui.itemTrailingIcon({ class: [props.b24ui?.itemTrailingIcon, item.b24ui?.itemTrailingIcon] })" />
          </ListboxItemIndicator>
        </span>
      </slot>
    </RekaListboxItem>
  </DefineItemTemplate>

  <ListboxRoot
    :id="id"
    data-slot="root"
    dd-gg="1"
    v-bind="{ ...rootProps, ...$attrs, ...ariaAttrs }"
    :disabled="disabled"
    :name="name"
    :class="b24ui.root({ class: [props.b24ui?.root, props.class] })"
    @update:model-value="onUpdate"
  >
    <ListboxFilter v-if="props.filter" v-model="searchTerm" as-child>
      <B24Input
        :autofocus="props.autofocus"
        :autofocus-delay="props.autofocusDelay"
        :size="size"
        no-border
        v-bind="inputProps"
        data-slot="input"
        :class="b24ui.input({ class: props.b24ui?.input })"
      />
    </ListboxFilter>

    <ListboxContent data-slot="content" :class="b24ui.content({ class: props.b24ui?.content })">
      <div v-if="props.loading" data-slot="loading" :class="b24ui.loading({ class: props.b24ui?.loading })">
        <slot name="loading">
          <Component
            :is="props.loadingIcon || icons.loading"
            data-slot="loadingIcon"
            :class="b24ui.loadingIcon({ class: props.b24ui?.loadingIcon })"
          />
        </slot>
      </div>
      <div v-else-if="!filteredItems.length" data-slot="empty" :class="b24ui.empty({ class: props.b24ui?.empty })">
        <slot name="empty" :search-term="searchTerm">
          {{ searchTerm ? t("listbox.noMatch", { searchTerm }) : t("listbox.noData") }}
        </slot>
      </div>

      <ListboxVirtualizer
        v-else-if="!!props.virtualize"
        v-slot="{ option: item, virtualItem }"
        :options="filteredItems"
        :text-content="(item2) => get(item2, props.labelKey)"
        v-bind="virtualizerProps"
      >
        <ReuseItemTemplate :item="item" :index="virtualItem.index" />
      </ListboxVirtualizer>

      <template v-else>
        <ListboxGroup v-for="(group, groupIndex) in filteredGroups" :key="`group-${groupIndex}`" data-slot="group" :class="b24ui.group({ class: props.b24ui?.group })">
          <ReuseItemTemplate v-for="(item, index) in group" :key="`group-${groupIndex}-${index}`" :item="item" :index="index" />
        </ListboxGroup>
      </template>
    </ListboxContent>
  </ListboxRoot>
</template>
