<template>
  <FormGroup
    :class="{
      'dropdown-open': isFocused,
      'tao__form-group--loading': loading
    }"
    :data-select="currentLabelId"
    class="tao__form-group-dropdown relative">
    <template v-if="!defaultInput">
      <FormInput
        ref="formInput"
        :disabled="disabled"
        :multiple="multiple"
        :readonly="readonly"
        @mousedown.prevent="toggleDropdown">
        <template v-for="(option, index) in valueAsArray">
          <span
            v-show="!search && mutableValue !== emptyValue || search && multiple"
            :key="`${index}-selecteg-tag`"
            :class="{
              'form__selected-single pl-1-4': !multiple,
              'form__selected-tag flex align-center': true,
              'rounded-circle max-w-full pin-l': true,
              'pl-1 pr-1-2': multiple
            }">
            <span class="form__selected-selected inline-block overflow-hidden ml-1-2">
              <slot
                :option="option || {}"
                name="selected">
                <slot :option="option || {}">
                  {{ getSelectedItemLabel(index) }}
                </slot>
              </slot>
            </span>
            <button
              v-if="multiple"
              :disabled="disabled"
              :class="{
                'form__selected-close cursor-pointer': true,
                'p-0 border-none ml-1 font-bold rounded-circle opacity-high-min': true
              }"
              @click.prevent="deselectItem(option)"
              type="button"
              aria-label="Remover Item">
              <span
                class="text-white"
                aria-hidden="true">&times;</span>
            </button>
          </span>
        </template>
        <input
          ref="input"
          :id="currentLabelId"
          v-model="search"
          v-mask="config"
          v-focus="focus"
          v-bind="$attrs"
          :class="{
            'cursor-pointer': readonly
          }"
          :type="type"
          :disabled="disabled"
          :placeholder="showPlaceholder"
          :autocomplete="autocomplete"
          :readonly="readonly"
          :aria-label="label"
          :value="currentValue"
          :name="name"
          v-on="listeners"
          @focus="isFocused = true"
          @blur="blurInput"
          @keydown.up.prevent="typeAheadUp"
          @keydown.down.prevent="typeAheadDown"
          @keydown.enter.prevent="typeAheadSelect"
          @keydown.delete="maybeDeleteValue"
          class="tao__input">
        <button
          ref="arrow"
          :disabled="disabled"
          :class="{
            'cursor-pointer': !disabled,
            'opacity-md-max cursor-not-allowed': disabled
          }"
          @click.prevent.self
          @keydown.enter="isFocused = !isFocused"
          @blur="blurInput"
          class="icon__form show-icon inline-block p-0 cursor-pointer
          text-03 bg-transparent text-center rounded m-1-4 rounded-circle">
          <TaoMiniIcon
            name="keyboard_arrow_right" />
        </button>
      </FormInput>
    </template>

    <template
      v-else>
      <div
        :class="{
          'is-focused': isFocused
        }"
        class="tao__select-wrapper w-full"
        style="order:1">
        <select
          ref="select"
          :id="currentLabelId"
          v-model="mutableValue"
          v-bind="$attrs"
          :disabled="disabled"
          :readonly="readonly"
          :aria-label="label"
          @focus="isFocused = true"
          @blur="isFocused = false"
          class="tao__input border-form-02">
          <option
            :value="emptyValue"
            selected
            disabled>
            {{ placeholder }}
          </option>
          <option
            v-for="(option, index) in options"
            :key="index + 'normalSelect'"
            :value="optionValue !== null ? option[optionValue] || option : option">
            <slot
              :option="item"
              name="selection">
              <slot :option="option">
                {{ optionLabel ? option[optionLabel] || option : option }}
              </slot>
            </slot>
          </option>
        </select>
        <TaoMiniIcon
          class="absolute text-02 opacity-high-half pointer-events-none"
          name="keyboard_arrow_right" />
      </div>
    </template>

    <FormLabel
      v-if="label"
      :for="currentLabelId">
      {{ label }}
      <slot name="label" />
    </FormLabel>
    <transition :name="transition">
      <div
        v-if="!defaultInput"
        v-show="isFocused"
        class="tao__form-dropdown absolute w-full bg-white py-2 px-1 pin-l">
        <ul
          ref="dropdownMenu"
          @mousedown="mousedown = true"
          role="listbox"
          class="form__list">
          <li
            v-for="(item, index) in filteredOptions"
            :key="index + 'tao-select'"
            :class="{
              'selected': typeAheadPointer === index
            }"
            @mouseover="typeAheadPointer = index"
            class="font-medium text-01 form__list--li rounded">
            <a
              :data-index="index"
              :class="{
                'form__selected-list text-brand-darker': isItemSelected(item),
              }"
              @mousedown.prevent.stop="selectItem(item)"
              class="block text-left rounded cursor-pointer py-1 px-2">
              <slot
                :option="item"
                name="selection">
                <slot :option="item">
                  {{ checkItemLabel(item) }}
                </slot>
              </slot>
            </a>
          </li>
          <li
            v-if="!filteredOptions.length"
            class="tao__form-list-error font-medium text-01
            text-centerpy-1-2 px-2 pointer-events-none">
            {{ searchErrorMessage }}
          </li>
        </ul>
      </div>
    </transition>
  </FormGroup>
</template>

<script>
// @flow
import mask from 'assets/masker/masker.directive';
import masker from 'assets/masker/masker';
import tokens from 'assets/masker/tokens';

import { forEach, isObject } from 'utils';

import focus from 'assets/focus/focus.directive';

import FormInput from './GenericFormInput.vue';
import FormGroup from './GenericFormGroup.vue';
import FormLabel from './GenericLabel.vue';

export default {
  name: 'TaoSelectInput',
  components: {
    FormLabel,
    FormInput,
    FormGroup,
    TaoMiniIcon: () => import('packages/TaoMiniIcon'),
  },
  directives: { mask, focus },
  props: {
    type: {
      type: String,
      default: 'text',
    },
    placeholder: {
      type: String,
      default: '',
    },
    label: {
      type: String,
      default: '',
    },
    autocomplete: {
      type: Boolean,
      default: false,
    },
    disableReselect: {
      type: Boolean,
      default: false,
    },
    readonly: {
      type: Boolean,
      default: false,
    },
    disabled: {
      type: Boolean,
      default: false,
    },
    formKey: {
      type: Number,
      default: 1,
    },
    options: {
      type: Array,
      default: () => [],
    },
    optionLabel: {
      type: String,
      default: null,
    },
    optionValue: {
      type: [String, Array, Object, Number, Boolean],
      default: null,
    },
    searchErrorMessage: {
      type: String,
      default: 'Não foram encontrados resultados',
    },
    multiple: {
      type: Boolean,
      default: false,
    },
    defaultInput: {
      type: Boolean,
      default: false,
    },
    mask: {
      type: [String, Array],
      default: '',
    },
    masked: {
      type: Boolean,
      default: false,
    },
    tokens: {
      type: Object,
      default: () => tokens,
    },
    value: {
      type: [String, Number, Object, Array, Boolean, undefined],
      default() { return this.emptyValue; },
    },
    onTyping: {
      type: Function,
      default: () => {},
    },
    onChange: {
      type: Function,
      // eslint-disable-next-line
      default: function (value) {
        this.$emit('input', value);
      },
    },
    emptyValue: {
      type: [String, Number, Object, Array, Boolean, undefined],
      default: () => null,
    },
    focus: {
      type: Boolean,
      default: false,
    },
    loading: {
      type: Boolean,
      default: false,
    },
    transition: {
      type: String,
      default: 'select-fade',
    },
  },
  data() {
    return {
      currentValue: this.value,
      isFocused: false,
      mutableValue: this.emptyValue,
      mutableValueChecker: this.emptyValue,
      search: '',
      typeAheadPointer: -1,
      mousedown: false,
    };
  },
  computed: {
    filteredOptions(): Object {
      const options = this.search.length
        ? this.filter(this.options, this.search, this)
        : this.options;
      return options;
    },
    showPlaceholder(): string {
      if (this.mutableValue === this.emptyValue
        || (Array.isArray(this.mutableValue) && !this.mutableValue.length)) {
        return this.placeholder;
      }
      return '';
    },
    valueAsArray(): Array<string> {
      if (this.multiple && this.mutableValue !== this.emptyValue) {
        return [].concat(this.mutableValue);
      }
      if (this.mutableValue !== this.emptyValue) {
        return [].concat(this.mutableValue);
      }
      return [];
    },
    currentLabelId(): string {
      if (this.label) {
        const lowerCaseLabel = this.label.toLowerCase();
        return `${lowerCaseLabel.replace(/[^a-zA-Z0-9]/g, '')}-${this.formKey}`;
      }
      if (this.placeholder) {
        const lowerCaseLabel = this.placeholder.toLowerCase();
        return `${lowerCaseLabel.replace(/[^a-zA-Z0-9]/g, '')}-${this.formKey}`;
      }
      return this.formKey;
    },
    config() {
      return {
        mask: this.mask,
        masked: this.masked,
        tokens: this.tokens,
      };
    },
  },
  watch: {
    value(value: any) {
      this.mutableValue = value;
    },
    masked() {
      this.refresh(this.search);
    },
    mutableValue(value: any, oldValue: any) {
      if (this.multiple) {
        this.setMultipleInitialValue();
        if (Array.isArray(this.mutableValue)) {
          this.mutableValueChecker = this.findMultipleValue();
        }
        return this.onChange ? this.onChange(value) : this.emptyValue;
      }
      this.mutableValueChecker = this.findValue(value);

      if (this.defaultInput) {
        const { select } = this.$refs;
        if (this.mutableValue !== this.emptyValue) {
          select.removeAttribute('style');
        }
      }
      return this.onChange && value !== oldValue ? this.onChange(value) : this.emptyValue;
    },
    search(value: string) {
      if (value) {
        this.onTyping(value);
        this.refresh(value);
        this.isFocused = true;
      }
    },
    filteredOptions() {
      this.typeAheadPointer = 0;
    },
    options() {
      this.search = '';
    },
    typeAheadPointer() {
      this.maybeAdjustScroll();
    },
    multiple(value: boolean) {
      this.mutableValue = value ? [] : this.emptyValue;
    },
  },
  created() {
    this.mutableValue = this.value;

    if (Array.isArray(this.mutableValue)) {
      this.mutableValueChecker = this.findMultipleValue();
    }
    if (this.multiple) {
      this.setMultipleInitialValue();
      this.findMultipleValue();
    } else {
      if (this.value === this.emptyValue || !this.value) { return; }
      const singleValue = this.value[this.optionValue] || this.value;
      this.mutableValueChecker = this.findInArray(singleValue, this.options);
    }
  },
  mounted() {
    const { formInput, select } = this.$refs;
    if (formInput) {
      const { childNodes } = formInput;
      if (childNodes.length) {
        forEach(childNodes, (node) => {
          node.addEventListener('focus', () => {
            const isFormDisabled = formInput.classList.contains('disabled');
            if (document.activeElement === node && !isFormDisabled) {
              formInput.classList.add('form__focus');
            }
          });
          node.addEventListener('blur', () => {
            formInput.classList.remove('form__focus');
          });
        });
      }
    }
    if (select) {
      if (this.mutableValue === this.emptyValue) {
        select.setAttribute('style', 'color:rgba(0,0,0,.25);');
      }
    }
  },
  methods: {
    isOptionValueValid(item: {}) {
      if (item && this.optionValue) {
        return item[this.optionValue] !== undefined;
      }
      return false;
    },
    checkItemLabel(item: {}): string {
      if (this.optionLabel) {
        return item[this.optionLabel] || item;
      }
      return String(item);
    },
    handleInput(event: InputEvent) {
      this.refresh(event.target.value);
    },
    selectItem(item: {}) {
      this.isFocused = false;
      this.search = '';
      if (this.isItemSelected(item)) {
        if (!this.disableReselect) {
          this.removeValueWhenItemIsSelected(item);
        }
        return;
      }
      if (this.multiple) {
        if (this.optionValue !== null) {
          this.mutableValue.push(this.isOptionValueValid(item) ? item[this.optionValue] : item);
          this.mutableValueChecker.push(item);
        } else {
          this.mutableValue.push(item);
          this.mutableValueChecker.push(item);
        }
        return;
      }
      if (this.optionValue !== null) {
        this.mutableValue = this.isOptionValueValid(item) ? item[this.optionValue] : item;
        this.mutableValueChecker = item;
      } else {
        this.mutableValue = item;
        this.mutableValueChecker = item;
      }
    },
    setMultipleInitialValue() {
      if (!this.mutableValue) {
        this.mutableValue = [];
      }
      if (!this.mutableValueChecker) {
        this.mutableValueChecker = [];
      }
    },
    removeValueWhenItemIsSelected(item: {}) {
      if (this.multiple) {
        this.deselectItem(item);
        return;
      }
      this.mutableValue = this.emptyValue;
      this.mutableValueChecker = this.emptyValue;
    },
    deselectItem(item: {}) {
      if (this.multiple) {
        const index = this.getItemIndex(item);
        this.mutableValueChecker.splice(index, 1);
        this.mutableValue.splice(index, 1);
      } else {
        this.mutableValueChecker = this.emptyValue;
        this.mutableValue = this.emptyValue;
      }
    },
    getItemIndex(item: {}) {
      let ref = -1;
      this.mutableValue.forEach((value) => {
        const valueString = JSON.stringify(value);
        const itemString = this.isOptionValueValid(item)
          ? JSON.stringify(item[this.optionValue])
          : JSON.stringify(item);

        if (valueString === itemString) {
          ref = value;
        }
      });
      return this.mutableValue.indexOf(ref);
    },
    getSelectedItemLabel(index: number) {
      if (this.multiple) {
        if (this.optionLabel !== this.emptyValue) {
          if (this.mutableValueChecker && this.mutableValueChecker.length) {
            return this.mutableValueChecker[index][this.optionLabel]
              || this.mutableValueChecker[index];
          }
          return this.mutableValue[this.optionLabel] || this.mutableValue;
        }
        return this.mutableValueChecker[index];
      }
      if (this.optionLabel !== this.emptyValue) {
        if (this.mutableValueChecker) {
          return this.mutableValueChecker[this.optionLabel] || this.mutableValueChecker;
        }
        return (this.mutableValue && this.mutableValue[this.optionLabel]) || this.mutableValue;
      }
      return this.mutableValue;
    },
    isItemSelected(item: {}) {
      const jsonItem = JSON.stringify(item);
      const jsonValue = JSON.stringify(this.mutableValueChecker);
      if (this.multiple) {
        return jsonValue.includes(jsonItem);
      }
      return jsonItem === jsonValue;
    },
    filter(options: Array<mixed>, search: string): Array<mixed> {
      return options.filter((option) => {
        let label = this.checkItemLabel(option);
        if (typeof label === 'number') {
          label = label.toString();
        }
        return this.filterBy(label, search);
      });
    },
    filterBy(label: string, search: string): boolean {
      let correctLabel = '';
      if (typeof label === 'string') {
        correctLabel = label.toLowerCase();
      }
      return correctLabel.indexOf(search.toLowerCase()) > -1;
    },
    blurInput() {
      this.isFocused = false;
      this.search = '';
    },
    typeAheadUp() {
      this.isFocused = true;
      if (this.typeAheadPointer > 0) {
        this.typeAheadPointer--;
        this.maybeAdjustScroll();
      }
    },
    typeAheadDown() {
      this.isFocused = true;
      if (this.typeAheadPointer < this.filteredOptions.length - 1) {
        this.typeAheadPointer++;
        this.maybeAdjustScroll();
      }
    },
    typeAheadSelect() {
      if (this.filteredOptions[this.typeAheadPointer]) {
        this.selectItem(this.filteredOptions[this.typeAheadPointer]);
      }
    },
    maybeAdjustScroll() {
      const pixelsToPointerTop = this.pixelsToPointerTop();
      const pixelsToPointerBottom = this.pixelsToPointerBottom();

      if (pixelsToPointerTop <= this.viewport().top) {
        return this.scrollTo(pixelsToPointerTop);
      }
      if (pixelsToPointerBottom >= this.viewport().bottom) {
        return this.scrollTo(this.viewport().top + this.pointerHeight());
      }
      return this.emptyValue;
    },
    maybeDeleteValue() {
      if (this.mutableValue && !this.search) {
        if (this.multiple) {
          this.mutableValue.pop();
          this.mutableValueChecker.pop();
        } else {
          this.mutableValue = this.emptyValue;
          this.mutableValueChecker = this.emptyValue;
        }
      }
    },
    pixelsToPointerTop() {
      let pixelsToPointerTop = 0;
      const { dropdownMenu } = this.$refs;
      if (dropdownMenu) {
        for (let i = 0; i < this.typeAheadPointer; i++) {
          pixelsToPointerTop += dropdownMenu.children[i].offsetHeight;
        }
      }
      return pixelsToPointerTop;
    },
    pixelsToPointerBottom() {
      return this.pixelsToPointerTop() + this.pointerHeight();
    },
    pointerHeight() {
      const { dropdownMenu } = this.$refs;
      const element = dropdownMenu ? dropdownMenu.children[this.typeAheadPointer] : false;
      return element ? element.offsetHeight : 0;
    },
    viewport() {
      const { dropdownMenu } = this.$refs;
      return {
        top: dropdownMenu ? dropdownMenu.scrollTop : 0,
        bottom: dropdownMenu ? dropdownMenu.offsetHeight + dropdownMenu.scrollTop : 0,
      };
    },
    scrollTo(position: number) {
      const { dropdownMenu } = this.$refs;
      if (dropdownMenu) {
        dropdownMenu.scrollTop = position;
      }
    },
    toggleDropdown(event: TargetEvent) {
      if (this.disabled) { return; }
      const { formInput, input, arrow } = this.$refs;
      const { tagName } = event.target;

      if (event.target === formInput
        || event.target === input
        || event.target === arrow
        || tagName === 'svg') {
        this.isFocused = !this.isFocused;
        if (this.isFocused) {
          input.focus();
        } else {
          input.blur();
        }
      } else {
        this.isFocused = false;
      }
    },
    findInArray(nameKey: string, array: Array<mixed>) {
      for (let i = 0, arr = array.length; i < arr; i++) {
        const item = array[i];
        const isValid = item
          && (
            // eslint-disable-next-line
            (isObject(item) && Object.values(item).includes(nameKey))
            || JSON.stringify(item) === JSON.stringify(nameKey)
            || item === nameKey
          );
        if (isValid) {
          return item;
        }
      }
      return this.emptyValue;
    },
    findMultipleValue(): Array<mixed> {
      if (Array.isArray(this.value) && this.value.length) {
        const optionValue = opt => opt[this.optionValue] || opt;
        const findValue = val => this.options.find(opt => optionValue(opt) === val);

        return this.value.map(value => findValue(value) || value);
      }
      return [];
    },
    findValue(value: any) {
      if (value) {
        return this.findInArray(value, this.options);
      }
      return this.emptyValue;
    },
    refresh(value: any) {
      if (this.disabled) { return; }

      this.currentValue = value;
      value = masker(value, this.mask, this.masked, this.tokens);
      if (value !== this.lastValue) {
        this.lastValue = value;
        this.$emit('input', value);
      }
    },
  },
};
</script>
