<template>
  <div class="air-dropdown" :style="{ width: `${dropdownWidth}px` }">
    <input
      v-if="searchable"
      class="dropdownInput"
      type="text"
      ref="inputRef"
      :placeholder="$t('components.search')"
      @input="handleSearchValueChange"
      @keydown="handleInputKeyDown"
    />

    <div class="options" ref="optionsRef">
      <div
        class="option text-textDarkest"
        v-for="option in filteredOptions"
        :key="option[itemKey]"
        :data-select-option-value="option[itemKey]"
        :data-testid="`select-option:${option[itemText]}`"
        @mouseenter="handleOptionMouseEnter"
        @click="selectOptionValue(option[itemKey])"
      >
        <slot name="option" :option="option">{{ option[itemText] }}</slot>
      </div>

      <div
        v-if="isOptionCreatable"
        class="option text-textDarkest"
        :data-create-option-label="{ searchValue }"
        @mouseenter="handleOptionMouseEnter"
        @click="createOption(searchValue)"
      >
        {{
          isCreatingOption
            ? `Creating "${searchValue}"...`
            : `Create "${searchValue}"`
        }}
      </div>
    </div>

    <div v-if="filteredOptions.length === 0" class="optionsNoResults">
      {{$t('components.noResults')}}
    </div>
  </div>
</template>

<script>
import Vue from 'vue';
import { Component, Prop, Ref } from 'vue-property-decorator';

const activeOptionClass = 'select-option-is-active';

export default @Component({
  name: 'itfDropdown',
  components: {}
})
class itfDropdown extends Vue {
  @Prop({ type: Number, default: undefined }) dropdownWidth;
  @Prop({ type: [Array, String, Number], default: undefined }) value;
  @Prop({ type: Boolean }) isValueEmpty;
  @Prop({ type: Boolean }) searchable;
  @Prop({ type: String, default: 'value' }) itemKey;
  @Prop({ type: String, default: 'label' }) itemText;
  @Prop({ type: String, default: '' }) searchValue;
  @Prop({ type: Function, required: true }) deactivateDropdown;
  @Prop({ type: Array, required: true }) options;
  @Prop({ type: Function, default: undefined }) onCreate;
  @Prop({ type: Boolean }) multiple;
  @Prop({ type: Boolean }) withClearValue;

  @Ref('optionsRef') optionsRef;
  @Ref('inputRef') inputRef;

  isCreatingOption = false;

  get optionsFilteredBySearchValue() {
    return this.options.filter((option) => option[this.itemText]
      .toString()
      .toLowerCase()
      .includes(this.searchValue.toLowerCase()));
  }

  get filteredOptions() {
    return this.multiple
      ? this.optionsFilteredBySearchValue.filter(
        (option) => !(this.value).includes(option[this.itemKey]),
      )
      : this.optionsFilteredBySearchValue.filter(
        (option) => this.value !== option[this.itemKey],
      );
  }

  get isOptionCreatable() {
    return this.onCreate
      && this.searchValue
      && !this.options.map((option) => option[this.itemText]).includes(this.searchValue);
  }

  handleSearchValueChange(event) {
    this.$emit('searchValueChange', (event.target)[this.itemKey]);
  }

  async getActiveOptionNode() {
    await this.$nextTick();
    return this.optionsRef
      ? this.optionsRef.querySelector(`.${activeOptionClass}`)
      : null;
  }

  selectOptionValue (optionValue) {
    if (this.multiple) {
      this.$emit('change', [
        ...new Set([...(this.value), optionValue]),
      ]);
    } else {
      this.deactivateDropdown();
      this.$emit('change', optionValue);
    }
  }

  createOption (newOptionLabel) {
    this.isCreatingOption = true;
    this.onCreate(
      newOptionLabel,
      (createdOptionValue) => {
        this.isCreatingOption = false;
        this.selectOptionValue(createdOptionValue);
      },
    );
  };

  async handleInputEnterKeyDown (event) {
    event.preventDefault();
    const $active = await this.getActiveOptionNode();
    if (!$active) return;
    const optionValueToSelect = $active.getAttribute(
      'data-select-option-value',
    );
    const optionLabelToCreate = $active.getAttribute(
      'data-create-option-label',
    );
    if (optionValueToSelect) {
      this.selectOptionValue(optionValueToSelect);
    } else if (optionLabelToCreate) {
      this.createOption(optionLabelToCreate);
    }
  }

  async handleInputArrowUpOrDownKeyDown (event) {
    const $active = (await this.getActiveOptionNode());
    const $options = this.optionsRef;
    if (!$active || !$options) return;
    const $optionsHeight = $options.getBoundingClientRect().height;
    const $activeHeight = $active.getBoundingClientRect().height;
    if (event.keyCode === 40) {
      if ($options.lastElementChild === $active) {
        $active.classList.remove(activeOptionClass);
        $options.firstElementChild?.classList.add(activeOptionClass);
        $options.scrollTop = 0;
      } else {
        $active.classList.remove(activeOptionClass);
        $active.nextElementSibling?.classList.add(activeOptionClass);
        if ($active.offsetTop > $options.scrollTop + $optionsHeight / 1.4) {
          $options.scrollTop += $activeHeight;
        }
      }
    } else if (event.keyCode === 38) {
      if ($options.firstElementChild === $active) {
        $active.classList.remove(activeOptionClass);
        $options.lastElementChild?.classList.add(activeOptionClass);
        $options.scrollTop = $options.scrollHeight;
      } else {
        $active.classList.remove(activeOptionClass);
        $active.previousElementSibling?.classList.add(activeOptionClass);
        if ($active.offsetTop < $options.scrollTop + $optionsHeight / 2.4) {
          $options.scrollTop -= $activeHeight;
        }
      }
    }
  }

  handleInputEscapeKeyDown(event) {
    event.stopImmediatePropagation();
    this.deactivateDropdown();
  }

  handleInputKeyDown(event) {
    if (event.keyCode === 27) {
      this.handleInputEscapeKeyDown(event);
    } else if (event.keyCode === 13) {
      this.handleInputEnterKeyDown(event);
    } else if (event.keyCode === 40 || event.keyCode === 38) {
      this.handleInputArrowUpOrDownKeyDown(event);
    }
  }

  async handleOptionMouseEnter(event) {
    const $active = await this.getActiveOptionNode();
    if ($active) $active.classList.remove(activeOptionClass);
    (event.currentTarget).classList.add(activeOptionClass);
  }

  async setFirstOptionAsActive() {
    const $active = await this.getActiveOptionNode();
    if (!this.optionsRef) return;
    if ($active) $active.classList.remove(activeOptionClass);
    if (this.optionsRef.firstElementChild) {
      this.optionsRef.firstElementChild.classList.add(activeOptionClass);
    }
  }

  mounted() {
    this.setFirstOptionAsActive();
  }
}
</script>
