<template>
  <RawSelect
    v-bind="$props"
    :options="filteredOptions"
    :model-value="modelValue"
    :open="isDatalistOpen"
    @select="onSelect"
    @click="onSelectClick"
  >
    <template #input>
      <div class="tags">
        <BmsTag
          v-for="tag in selectedItems"
          :small="small"
          active
          @dismiss="removeOption(tag.value)"
        >
          <component v-if="tag?.icon" :is="tag.icon" />
          {{ tag.label }}
        </BmsTag>
        <input
          type="text"
          ref="inputElement"
          v-model="searching"
          class="search"
          :disabled="disabled"
          @input="onInput"
          @keyup.down="openDatalist"
        />
      </div>

      <span class="icon-container">
        <template v-if="modelValue && modelValue?.length">
          <X class="icon icon-clear" @click.stop="clearInput" />
        </template>
        <template v-else>
          <ChevronUp v-if="isDatalistOpen" class="icon icon-toggle-button" />
          <ChevronDown v-else class="icon icon-toggle-button" />
        </template>
      </span>
    </template>
    <template #option="{ option }: { option: InputOption }">
      <BmsTag active :can-be-dismissed="false">
        <component v-if="option?.icon" :is="option.icon" />
        {{ option.label }}
      </BmsTag>
    </template>
  </RawSelect>
</template>

<script lang="ts" setup>
import { computed, Ref, ref } from 'vue';
import { ChevronDown, ChevronUp, X } from 'lucide-vue-next';
import BmsTag from './BmsTag.vue';
import { InputOption } from '@/models';
import { searchString } from '@/helpers';
import { FieldComponentProps } from '@/plugins/field/field-component.model';
import RawSelect from './RawSelect.vue';
import { onClickOutside, onKeyDown, onKeyUp } from '@vueuse/core';

export interface Props extends FieldComponentProps {
  options: InputOption[] | string[];
  placeholder?: string;
}

const props = withDefaults(defineProps<Props>(), {
  label: '',
  disabled: false,
  required: false,
});

const emits = defineEmits<{
  select: [option: InputOption | string];
  input: [e: InputEvent];
}>();

const modelValue = defineModel<string[] | null>('modelValue', { default: [] });

const inputElement: Ref<HTMLElement | null> = ref(null);
const isDatalistOpen = ref(false);
const searching = ref('');

const closeDatalist = () => (isDatalistOpen.value = false);
const openDatalist = () => {
  if (!props.disabled) {
    isDatalistOpen.value = true;
  }
};

const onBlur = () => {
  closeDatalist();
};

const onBackspace = () => {
  if (
    searching.value.length === 0 &&
    modelValue.value &&
    modelValue.value.length > 0
  )
    modelValue.value.splice(-1);
};
onClickOutside(inputElement, closeDatalist, {
  ignore: ['.datalist-option', '.icon-toggle-button'],
});
onKeyUp('Escape', closeDatalist);
onKeyUp('Tab', closeDatalist);

onKeyDown('Backspace', onBackspace);

const onSelectClick = () => {
  if (!props.disabled) {
    isDatalistOpen.value = !isDatalistOpen.value;
  }
};
const onInput = (e: InputEvent) => {
  emits('input', e);
  openDatalist();
};

const setFocus = () => {
  if (inputElement.value) {
    (inputElement.value as any).focus();
  }
};

const onSelect = (option: InputOption | string) => {
  if (typeof option === 'string') {
    modelValue.value = (modelValue.value ?? []).concat(option);
  } else if (option.value) {
    modelValue.value = (modelValue.value ?? []).concat(option.value);
  }

  searching.value = '';
  emits('select', option);
  setFocus();
};

const removeOption = (value: string) => {
  modelValue.value = (modelValue.value ?? []).filter((o) => o !== value);
};

const clearInput = () => {
  modelValue.value = [];
  searching.value = '';
};

const selectedItems = computed<InputOption[]>(() => {
  if (!modelValue.value) return [];

  const items: InputOption[] = [];
  modelValue.value.forEach((selectedValue) => {
    const item = optionsLabelValue.value.find(
      (option) => option.value === selectedValue,
    );
    if (item) items.push(item);
  });
  return items;
});

const optionsLabelValue = computed(() =>
  Array.isArray(props.options) &&
  !!props.options.length &&
  typeof props.options[0] === 'string'
    ? props.options.map((o) => ({ label: o, value: o }) as InputOption)
    : (props.options as InputOption[]),
);

const filteredOptions = computed(() =>
  optionsLabelValue.value
    .filter((o) => !modelValue.value?.includes(o.value))
    .filter((o) => searchString(o.label, searching.value)),
);
</script>

<style lang="scss" scoped>
.tags {
  display: flex;
  gap: 0.5em;
  padding: var(--field-margin);
  flex-wrap: wrap;
  width: 100%;

  .search {
    outline: none;
    border: none;
    background-color: transparent;
    flex-grow: 1;
    flex-basis: 40px;
    width: 100%;
  }
}
.icon-container {
  height: 100%;
  display: flex;
  align-items: center;

  &:hover {
    cursor: pointer;
  }

  .icon {
    display: block;
    width: 1em;
    height: 1em;
    margin: 0 var(--field-padding);
  }
}
</style>
