<template>
  <field v-bind="$props">
    <RawInputText
      ref="rawInput"
      v-model="inputText"
      :type="InputType.TEXT"
      :disabled="disabled"
      :class="classes"
      :placeholder="placeholder"
      :required="required"
      :small="small"
      @input="onInput"
      @click="onClick"
      @keyup.down="openDatalist"
    >
      <template #icon-start>
        <slot name="icon-start"></slot>
      </template>
      <template #icon-end>
        <slot name="icon-end">
          <X v-if="inputText.length" class="icon" @click.stop="clearInput" />
          <template v-else>
            <ChevronUp v-if="isDatalistOpen" class="icon" @click="onClick" />
            <ChevronDown v-else class="icon" @click="onClick" />
          </template>
        </slot>
      </template>
    </RawInputText>
    <template #datalist>
      <FieldDatalist
        v-show="isDatalistOpen"
        data-testid="autocomplete-menu"
        :is-input-focused="isDatalistOpen"
        :can-add-new-option="canAddNewOption"
        :new-option="inputText"
        :options="filteredMenuItems"
        :small="small"
        @select="selectItem"
        @add-new-option="(option: string) => emits('addNewOption', option)"
        @blur="closeDatalist"
      >
        <template #option="{ option }: { option: any }">
          <slot name="option" :option="option">{{ option }}</slot>
        </template>
      </FieldDatalist>
    </template>
  </field>
</template>

<script setup lang="ts">
import { searchString } from '@/helpers/string.helper';
import FieldDatalist from '@/plugins/field/FieldDatalist.vue';
import { computed, Ref, ref, watch } from 'vue';
import RawInputText from '@/components/form/RawInputText.vue';
import { InputOption, InputType } from '@/models';
import { ChevronDown, ChevronUp, X } from 'lucide-vue-next';
import { FieldComponentProps } from '@/plugins/field/field-component.model';
import { MaybeElementRef, onClickOutside, onKeyUp } from '@vueuse/core';

export interface Props extends FieldComponentProps {
  options: InputOption[];
  label?: string;
  placeholder?: string;
  open?: boolean;
  canAddNewOption?: boolean;
  newOption?: string;
}

const props = withDefaults(defineProps<Props>(), {
  modelValue: null,
  open: false,
  canAddNewOption: false,
});

const modelValue = defineModel<string | null>('modelValue', { required: true });

const rawInput: Ref<HTMLElement | null> = ref(null);

const emits = defineEmits<{
  addNewOption: [newOption: string];
  select: [option: InputOption];
  blur: [];
  focus: [];
  click: [];
  input: [e: InputEvent];
}>();

const getValidOptionByLabel = (label: string) =>
  props.options.find((o) => o.label === label);

const getValidOptionByValue = (value: string | null) =>
  props.options.find((o) => o.value === value);

const inputText = ref<string>(
  getValidOptionByValue(modelValue.value)?.label ?? '',
);
const isDatalistOpen = ref(props.open);

const closeDatalist = () => (isDatalistOpen.value = false);
const openDatalist = () => (isDatalistOpen.value = true);

onClickOutside(rawInput as MaybeElementRef, closeDatalist, {
  ignore: ['.datalist-option', '.icon-toggle-button', '.icon-clear'],
});

const onBlur = () => {
  emits('blur');
  closeDatalist();
};
onKeyUp('Escape', onBlur);
onKeyUp('Tab', onBlur);

const classes = computed(() => {
  return { 'is-error': props.errors?.length, 'is-disabled': props.disabled };
});

const filteredMenuItems = computed(() =>
  props.options.filter((o) => searchString(o.label, inputText.value)),
);

const selectItem = (option: InputOption) => {
  const existingOption =
    getValidOptionByLabel(option.label) || getValidOptionByValue(option.value);
  if (existingOption) {
    modelValue.value = existingOption?.value ?? null;
    emits('select', existingOption);
    setFocus();
    closeDatalist();
  }
};

const displayItem = (option: InputOption) => {
  const existingOption =
    getValidOptionByLabel(option.label) || getValidOptionByValue(option.value);

  if (existingOption) {
    inputText.value = existingOption.label;
    modelValue.value = existingOption.value;
  } else {
    modelValue.value = null;
    inputText.value = option.label;
  }
};

watch(
  () => modelValue.value,
  function updateFromProps(newValue) {
    const option = getValidOptionByValue(newValue);
    if (option) {
      displayItem(option);
      if (modelValue.value !== option.value) selectItem(option);
      // drop input value only if parent set nullable value
    } else if (modelValue.value !== undefined && modelValue.value !== null) {
      inputText.value = '';
    }
  },
);

const onClick = () => {
  if (!props.disabled) {
    isDatalistOpen.value = !isDatalistOpen.value;
  }
};

const onInput = ($event: InputEvent) => {
  if (inputText.value === '') {
    clearInput();
  } else {
    emits('input', $event);
  }
  openDatalist();
};

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

const clearInput = () => {
  if (!props.disabled) {
    inputText.value = '';
    modelValue.value = null;
    setFocus();
    closeDatalist();
  }
};

defineExpose({
  setFocus,
});
</script>

<style scoped lang="scss">
.field__input {
  .autocomplete-wrapper {
    width: 100%;
    padding: 0.7em;
    margin: 0.5em 0;
    border-radius: var(--bms-border-radius);
    border: 1px solid var(--field-border-color);
    background-color: var(--input-background-color);
    height: 48px;

    &:hover {
      --field-border-color: var(--bms-grey-100);
    }

    &:has(input:focus) {
      --field-border-color: var(--field-border-color-active);
    }

    &.is-error {
      --field-border-color: var(--bms-red-100);
      --input-background-color: var(--bms-red-25);
    }

    &.is-disabled {
      --field-border-color: var(--bms-field-disabled-border-color);
      --input-background-color: var(--bms-field-disabled-background);
      color: var(--bms-field-disabled-text-color);
      pointer-events: none;

      input:disabled {
        opacity: 1;
        -webkit-text-fill-color: currentColor;
      }
    }

    input {
      outline: none;
      appearance: none;
      border: 0;
      padding: 0;
      margin: 0;
      font: inherit;
      color: inherit;
      height: 24px;
      line-height: 1;
      width: 100%;
      background-color: transparent;
    }
  }
}
.icon {
  height: 1em;
  width: 1em;

  cursor: pointer;
}
</style>
