<script>
import theme from "#build/b24ui/select-menu";
</script>

<script setup>
import { useTemplateRef, computed, ref, onMounted, toRef, toRaw, watch, nextTick } from "vue";
import { Primitive, ComboboxRoot, ComboboxArrow, ComboboxAnchor, ComboboxInput, ComboboxTrigger, ComboboxCancel, ComboboxPortal, ComboboxContent, ComboboxEmpty, ComboboxGroup, ComboboxVirtualizer, ComboboxLabel, ComboboxSeparator, ComboboxItem, ComboboxItemIndicator, FocusScope } from "reka-ui";
import { defu } from "defu";
import { reactivePick, createReusableTemplate } from "@vueuse/core";
import { useAppConfig } from "#imports";
import { useComponentProps } from "../composables/useComponentProps";
import { useForwardProps } from "../composables/useForwardProps";
import { useFieldGroup, FieldGroupReset } from "../composables/useFieldGroup";
import { useComponentIcons } from "../composables/useComponentIcons";
import { useFormField } from "../composables/useFormField";
import { useFilter } from "../composables/useFilter";
import { useLocale } from "../composables/useLocale";
import { usePortal } from "../composables/usePortal";
import { compare, get, getDisplayValue, isArrayOfArray, looseToNumber } from "../utils";
import { getEstimateSize } from "../utils/virtualizer";
import { tv } from "../utils/tv";
import icons from "../dictionary/icons";
import B24Badge from "./Badge.vue";
import B24Avatar from "./Avatar.vue";
import B24Button from "./Button.vue";
import B24Chip from "./Chip.vue";
import B24Input from "./Input.vue";
defineOptions({ inheritAttrs: false });
const _props = defineProps({
  id: { type: String, required: false },
  placeholder: { type: String, required: false },
  searchInput: { type: [Boolean, Object], required: false, default: true },
  color: { type: null, required: false },
  size: { type: null, required: false },
  noPadding: { type: Boolean, required: false },
  noBorder: { type: Boolean, required: false },
  underline: { type: Boolean, required: false },
  rounded: { type: Boolean, required: false },
  tag: { type: String, required: false },
  tagColor: { type: null, required: false },
  required: { type: Boolean, required: false },
  trailingIcon: { type: [Function, Object], required: false },
  selectedIcon: { type: [Function, Object], required: false },
  clear: { type: [Boolean, Object], required: false },
  clearIcon: { type: [Function, Object], required: false },
  content: { type: Object, required: false },
  arrow: { type: [Boolean, Object], required: false },
  portal: { type: [Boolean, String], required: false, skipCheck: true, default: true },
  virtualize: { type: [Boolean, Object], required: false, default: false },
  valueKey: { type: null, required: false },
  labelKey: { type: null, required: false, default: "label" },
  descriptionKey: { type: null, required: false, default: "description" },
  items: { type: null, required: false },
  defaultValue: { type: null, required: false },
  modelValue: { type: null, required: false },
  modelModifiers: { type: null, required: false },
  multiple: { type: Boolean, required: false },
  highlight: { type: Boolean, required: false },
  createItem: { type: [Boolean, String, Object], required: false },
  filterFields: { type: Array, required: false },
  ignoreFilter: { 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 },
  open: { type: Boolean, required: false },
  defaultOpen: { type: Boolean, required: false },
  disabled: { type: Boolean, required: false },
  name: { type: String, required: false },
  resetSearchTermOnBlur: { type: Boolean, required: false, default: true },
  resetSearchTermOnSelect: { type: Boolean, required: false, default: true },
  resetModelValueOnClear: { type: Boolean, required: false, default: true },
  highlightOnHover: { type: Boolean, required: false },
  by: { type: [String, Function], required: false },
  icon: { type: [Function, Object], required: false },
  avatar: { type: Object, required: false },
  loading: { type: Boolean, required: false },
  trailing: { type: Boolean, required: false }
});
const emits = defineEmits(["change", "blur", "focus", "create", "clear", "highlight", "update:modelValue", "update:open"]);
const slots = defineSlots();
const props = useComponentProps("selectMenu", _props);
const searchTerm = defineModel("searchTerm", { type: String, ...{ default: "" } });
const { t } = useLocale();
const appConfig = useAppConfig();
const { filterGroups } = useFilter();
const rootProps = useForwardProps(reactivePick(props, "modelValue", "defaultValue", "open", "defaultOpen", "required", "multiple", "resetSearchTermOnBlur", "resetSearchTermOnSelect", "resetModelValueOnClear", "highlightOnHover", "by"), emits);
const portalProps = usePortal(toRef(() => props.portal));
const contentProps = toRef(() => defu(props.content, { side: "bottom", sideOffset: 8, collisionPadding: 8, position: "popper" }));
const arrowProps = toRef(() => defu(typeof props.arrow === "boolean" ? {} : props.arrow, { width: 20, height: 10 }));
const clearProps = computed(() => typeof props.clear === "object" ? props.clear : {});
const virtualizerProps = toRef(() => {
  if (!props.virtualize) return false;
  return defu(typeof props.virtualize === "boolean" ? {} : props.virtualize, {
    estimateSize: getEstimateSize(filteredItems.value, selectSize.value || "md", props.descriptionKey, !!slots["item-description"])
  });
});
const searchInputProps = toRef(() => defu(props.searchInput, { placeholder: t("selectMenu.search"), type: "text", size: "md" }));
const { emitFormBlur, emitFormFocus, emitFormInput, emitFormChange, size: formFieldSize, color: formFieldColor, id, name, highlight: formFieldHighlight, disabled: formFieldDisabled, ariaAttrs } = useFormField(_props);
const { orientation, size: fieldGroupSize } = useFieldGroup(_props);
const color = computed(() => formFieldColor.value ?? props.color);
const highlight = computed(() => formFieldHighlight.value ?? props.highlight);
const disabled = computed(() => formFieldDisabled.value ?? props.disabled);
const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(computed(() => ({
  icon: props.icon,
  avatar: props.avatar,
  trailing: props.trailing,
  trailingIcon: props.trailingIcon ?? icons.chevronDown,
  loading: props.loading
})));
const selectSize = computed(() => fieldGroupSize.value ?? formFieldSize.value ?? props.size);
const [DefineCreateItemTemplate, ReuseCreateItemTemplate] = createReusableTemplate();
const [DefineItemTemplate, ReuseItemTemplate] = createReusableTemplate({
  props: {
    item: {
      type: [Object, String, Number, Boolean],
      required: true
    },
    index: {
      type: Number,
      required: false
    }
  }
});
const isTag = computed(() => {
  return props.tag;
});
const b24ui = computed(() => tv({ extend: theme, ...appConfig.b24ui?.selectMenu || {} })({
  color: color.value,
  size: selectSize.value,
  loading: props.loading,
  rounded: Boolean(props.rounded),
  noPadding: Boolean(props.noPadding),
  noBorder: Boolean(props.noBorder),
  underline: Boolean(props.underline),
  highlight: highlight.value,
  leading: Boolean(isLeading.value || !!props.avatar || !!slots.leading),
  trailing: Boolean(isTrailing.value || !!slots.trailing),
  fieldGroup: orientation.value,
  virtualize: !!props.virtualize,
  multiple: props.multiple
}));
function displayValue(value) {
  if (props.multiple && Array.isArray(value)) {
    const displayedValues = value.map((item) => getDisplayValue(items.value, item, {
      labelKey: props.labelKey,
      valueKey: props.valueKey,
      by: props.by
    })).filter((v) => v != null && v !== "");
    return displayedValues.length > 0 ? displayedValues.join(", ") : void 0;
  }
  return getDisplayValue(items.value, value, {
    labelKey: props.labelKey,
    valueKey: props.valueKey,
    by: props.by
  });
}
const groups = computed(
  () => props.items?.length ? isArrayOfArray(props.items) ? props.items : [props.items] : []
);
const items = computed(() => groups.value.flatMap((group) => group));
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: (item) => isSelectItem(item) && !!item.type && ["label", "separator"].includes(item.type)
  });
});
const filteredItems = computed(() => filteredGroups.value.flatMap((group) => group));
const createItem = computed(() => {
  if (!props.createItem || !searchTerm.value) {
    return false;
  }
  const newItem = props.valueKey ? { [props.valueKey]: searchTerm.value } : searchTerm.value;
  if (typeof props.createItem === "object" && props.createItem.when === "always" || props.createItem === "always") {
    return !filteredItems.value.find((item) => compare(item, newItem, props.by ?? props.valueKey));
  }
  return !filteredItems.value.length;
});
const createItemPosition = computed(() => typeof props.createItem === "object" ? props.createItem.position : "bottom");
const triggerRef = useTemplateRef("triggerRef");
function autoFocus() {
  if (props.autofocus) {
    triggerRef.value?.$el?.focus({
      focusVisible: true
    });
  }
}
onMounted(() => {
  setTimeout(() => {
    autoFocus();
  }, props.autofocusDelay);
});
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();
  if (props.resetSearchTermOnSelect) {
    searchTerm.value = "";
  }
}
const isOpen = ref(false);
function onUpdateOpen(value) {
  isOpen.value = value;
  let timeoutId;
  if (!value) {
    const event = new FocusEvent("blur");
    emits("blur", event);
    emitFormBlur();
    if (props.resetSearchTermOnBlur) {
      const STATE_ANIMATION_DELAY_MS = 100;
      timeoutId = setTimeout(() => {
        searchTerm.value = "";
      }, STATE_ANIMATION_DELAY_MS);
    }
  } else {
    const event = new FocusEvent("focus");
    emits("focus", event);
    emitFormFocus();
    clearTimeout(timeoutId);
  }
}
function onCreate(e) {
  e.preventDefault();
  e.stopPropagation();
  emits("create", searchTerm.value);
}
function onSelect(e, item) {
  if (!isSelectItem(item)) {
    return;
  }
  if (item.disabled) {
    e.preventDefault();
    return;
  }
  item.onSelect?.(e);
}
function isSelectItem(item) {
  return typeof item === "object" && item !== null;
}
function isModelValueEmpty(modelValue) {
  if (props.multiple && Array.isArray(modelValue)) {
    return modelValue.length === 0;
  }
  return modelValue === void 0 || modelValue === null || modelValue === "";
}
function onClear() {
  emits("clear");
}
const viewportRef = useTemplateRef("viewportRef");
const comboboxRootRef = useTemplateRef("comboboxRootRef");
watch(() => props.items, async () => {
  if (!isOpen.value || !props.createItem) {
    return;
  }
  await nextTick();
  comboboxRootRef.value?.highlightFirstItem?.();
}, { flush: "post" });
defineExpose({
  triggerRef: toRef(() => triggerRef.value?.$el),
  viewportRef: toRef(() => viewportRef.value)
});
</script>

<template>
  <DefineCreateItemTemplate>
    <ComboboxItem
      data-slot="item"
      :class="b24ui.item({ addNew: true, class: props.b24ui?.item })"
      :value="searchTerm"
      @select="onCreate"
    >
      <span data-slot="itemLabel" :class="b24ui.itemLabel({ addNew: true, class: props.b24ui?.itemLabel })">
        <slot name="create-item-label" :item="searchTerm">
          <Component
            :is="icons.plus"
            data-slot="itemLeadingIcon"
            :class="b24ui.itemLeadingIcon({ addNew: true, class: props.b24ui?.itemLeadingIcon })"
          />
          {{ t("selectMenu.create", { label: searchTerm }) }}
        </slot>
      </span>
    </ComboboxItem>
  </DefineCreateItemTemplate>

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

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

    <ComboboxItem
      v-else
      data-slot="item"
      :class="b24ui.item({ class: [props.b24ui?.item, isSelectItem(item) && item.b24ui?.item, isSelectItem(item) && item.class], colorItem: isSelectItem(item) && item?.color || void 0 })"
      :disabled="isSelectItem(item) && item.disabled"
      :value="props.valueKey && isSelectItem(item) ? get(item, props.valueKey) : item"
      @select="onSelect($event, item)"
    >
      <slot name="item" :item="item" :index="index" :b24ui="b24ui">
        <slot name="item-leading" :item="item" :index="index" :b24ui="b24ui">
          <B24Avatar
            v-if="isSelectItem(item) && 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], colorItem: item?.color })"
          />
        </slot>

        <span data-slot="itemWrapper" :class="b24ui.itemWrapper({ class: [props.b24ui?.itemWrapper, isSelectItem(item) && item.b24ui?.itemWrapper] })">
          <span data-slot="itemLabel" :class="b24ui.itemLabel({ class: [props.b24ui?.itemLabel, isSelectItem(item) && item.b24ui?.itemLabel] })">
            <slot name="item-label" :item="item" :index="index">
              {{ isSelectItem(item) ? get(item, props.labelKey) : item }}
            </slot>
          </span>

          <span
            v-if="isSelectItem(item) && (get(item, props.descriptionKey) || !!slots['item-description'])"
            data-slot="itemDescription"
            :class="b24ui.itemDescription({ class: [props.b24ui?.itemDescription, isSelectItem(item) && 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, isSelectItem(item) && item.b24ui?.itemTrailing], colorItem: isSelectItem(item) && item?.color || void 0 })"
        >
          <ComboboxItemIndicator as-child>
            <Component
              :is="props.selectedIcon || icons.check"
              data-slot="itemTrailingIcon"
              :class="b24ui.itemTrailingIcon({ class: [props.b24ui?.itemTrailingIcon, isSelectItem(item) && item.b24ui?.itemTrailingIcon], colorItem: isSelectItem(item) && item?.color || void 0 })"
            />
          </ComboboxItemIndicator>

          <slot name="item-trailing" :item="item" :index="index" :b24ui="b24ui">
            <Component
              :is="item.icon"
              v-if="isSelectItem(item) && item.icon"
              data-slot="itemLeadingIcon"
              :class="b24ui.itemLeadingIcon({ class: [props.b24ui?.itemLeadingIcon, item.b24ui?.itemLeadingIcon], colorItem: item?.color })"
            />
            <B24Chip
              v-else-if="isSelectItem(item) && 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], colorItem: item?.color })"
            />
          </slot>
        </span>
      </slot>
    </ComboboxItem>
  </DefineItemTemplate>

  <Primitive as="div" data-slot="root" :class="b24ui.root({ addNew: true, class: [props.b24ui?.root] })">
    <ComboboxRoot
      ref="comboboxRootRef"
      v-slot="{ modelValue, open }"
      v-bind="rootProps"
      ignore-filter
      as-child
      :name="name"
      :disabled="disabled"
      @update:model-value="onUpdate"
      @update:open="onUpdateOpen"
    >
      <ComboboxAnchor as-child>
        <ComboboxTrigger
          :id="id"
          ref="triggerRef"
          data-slot="base"
          :class="b24ui.base({ class: [props.b24ui?.base, props.class] })"
          tabindex="0"
          v-bind="{ ...$attrs, ...ariaAttrs }"
        >
          <B24Badge
            v-if="isTag"
            data-slot="tag"
            :class="b24ui.tag({ class: props.b24ui?.tag })"
            :color="props.tagColor"
            :label="props.tag"
            size="xs"
          />
          <span v-if="isLeading || !!props.avatar || !!slots.leading" data-slot="leading" :class="b24ui.leading({ class: props.b24ui?.leading })">
            <slot name="leading" :model-value="modelValue" :open="open" :b24ui="b24ui">
              <Component
                :is="leadingIconName"
                v-if="isLeading && leadingIconName"
                data-slot="leadingIcon"
                :class="b24ui.leadingIcon({ class: props.b24ui?.leadingIcon })"
              />
              <B24Avatar
                v-else-if="!!props.avatar"
                :size="props.b24ui?.leadingAvatarSize || b24ui.leadingAvatarSize()"
                v-bind="props.avatar"
                data-slot="leadingAvatar"
                :class="b24ui.leadingAvatar({ class: props.b24ui?.leadingAvatar })"
              />
            </slot>
          </span>

          <slot :model-value="modelValue" :open="open" :b24ui="b24ui">
            <template v-for="displayedModelValue in [displayValue(modelValue)]" :key="displayedModelValue">
              <span
                v-if="displayedModelValue !== void 0 && displayedModelValue !== null"
                data-slot="value"
                :class="b24ui.value({ class: props.b24ui?.value })"
              >
                {{ displayedModelValue }}
              </span>
              <span
                v-else
                data-slot="placeholder"
                :class="b24ui.placeholder({ class: props.b24ui?.placeholder })"
              >
                {{ props.placeholder ?? "\xA0" }}
              </span>
            </template>
          </slot>

          <span v-if="isTrailing || !!slots.trailing || !!props.clear" data-slot="trailing" :class="b24ui.trailing({ class: props.b24ui?.trailing })">
            <slot name="trailing" :model-value="modelValue" :open="open" :b24ui="b24ui">
              <ComboboxCancel v-if="!!props.clear && !isModelValueEmpty(modelValue)" as-child>
                <B24Button
                  as="span"
                  :icon="props.clearIcon || icons.close"
                  :size="selectSize"
                  color="air-tertiary-no-accent"
                  tabindex="-1"
                  v-bind="clearProps"
                  data-slot="trailingClear"
                  :class="b24ui.trailingClear({ class: props.b24ui?.trailingClear })"
                  @click.stop="onClear"
                />
              </ComboboxCancel>

              <Component
                :is="trailingIconName"
                v-else-if="trailingIconName"
                data-slot="trailingIcon"
                :class="b24ui.trailingIcon({ class: props.b24ui?.trailingIcon })"
              />
            </slot>
          </span>
        </ComboboxTrigger>
      </ComboboxAnchor>

      <ComboboxPortal v-bind="portalProps">
        <FieldGroupReset>
          <ComboboxContent data-slot="content" :class="b24ui.content({ class: props.b24ui?.content })" v-bind="contentProps">
            <FocusScope trapped data-slot="focusScope" :class="b24ui.focusScope({ class: props.b24ui?.focusScope })">
              <slot name="content-top" />

              <ComboboxInput
                v-if="!!props.searchInput"
                v-model="searchTerm"
                :display-value="() => searchTerm"
                as-child
              >
                <B24Input
                  no-border
                  autofocus
                  autocomplete="off"
                  :size="selectSize"
                  v-bind="searchInputProps"
                  :model-modifiers="{ trim: props.modelModifiers?.trim }"
                  data-slot="input"
                  :class="b24ui.input({ class: props.b24ui?.input })"
                  @change.stop
                />
              </ComboboxInput>

              <ComboboxEmpty data-slot="empty" :class="b24ui.empty({ class: props.b24ui?.empty })">
                <slot name="empty" :search-term="searchTerm">
                  {{ searchTerm ? t("selectMenu.noMatch", { searchTerm }) : t("selectMenu.noData") }}
                </slot>
              </ComboboxEmpty>

              <div
                ref="viewportRef"
                role="presentation"
                data-slot="viewport"
                :class="b24ui.viewport({ class: props.b24ui?.viewport })"
              >
                <template v-if="!!props.virtualize">
                  <ReuseCreateItemTemplate v-if="createItem && createItemPosition === 'top'" />

                  <ComboboxVirtualizer
                    v-slot="{ option: item, virtualItem }"
                    :options="filteredItems"
                    :text-content="(item2) => isSelectItem(item2) ? get(item2, props.labelKey) : String(item2)"
                    v-bind="virtualizerProps"
                  >
                    <ReuseItemTemplate :item="item" :index="virtualItem.index" />
                  </ComboboxVirtualizer>

                  <ReuseCreateItemTemplate v-if="createItem && createItemPosition === 'bottom'" />
                </template>

                <template v-else>
                  <ComboboxGroup v-if="createItem && createItemPosition === 'top'" data-slot="group" :class="b24ui.group({ class: props.b24ui?.group })">
                    <ReuseCreateItemTemplate />
                  </ComboboxGroup>

                  <ComboboxGroup 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" />
                  </ComboboxGroup>

                  <ComboboxGroup v-if="createItem && createItemPosition === 'bottom'" data-slot="group" :class="b24ui.group({ class: props.b24ui?.group })">
                    <ReuseCreateItemTemplate />
                  </ComboboxGroup>
                </template>
              </div>

              <slot name="content-bottom" />
            </FocusScope>

            <ComboboxArrow v-if="!!props.arrow" v-bind="arrowProps" data-slot="arrow" :class="b24ui.arrow({ class: props.b24ui?.arrow })" />
          </ComboboxContent>
        </FieldGroupReset>
      </ComboboxPortal>
    </ComboboxRoot>
  </Primitive>
</template>
