<template>
  <FormGroup
    :class="{
      'dropdown-open': focused,
      'tao__form-group--loading': loading
    }"
    class="tao__form-group-dropdown relative">
    <FormInput
      ref="formInput"
      :disabled="disabled"
      :multiple="multiple"
      :readonly="readonly"
      @mousedown.prevent="handleToggleDropdown">
      <template v-for="(option, index) in mutableValue">
        <span
          v-show="!search && currentValue !== 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">
            <slot
              :option="option || {}"
              name="selected">
              <slot :option="option || {}">
                {{ getOptionLabel(option) }}
              </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="deselect(option)"
            type="button"
            aria-label="Remover Item">
            <span
              class="text-white"
              aria-hidden="true">&times;</span>
          </button>
        </span>
      </template>
      <input
        ref="input"
        :id="id"
        v-model="search"
        v-focus="focus"
        v-bind="$attrs"
        :class="{ 'cursor-pointer': readonly }"
        :type="type"
        :disabled="disabled"
        :placeholder="currentPlaceholder"
        :autocomplete="autocomplete ? null : 'off'"
        :readonly="readonly"
        :aria-label="label"
        @focus="focused = true"
        @blur="handleInputBlur"
        @keydown.up.prevent="typeAheadUp"
        @keydown.down.prevent="typeAheadDown"
        @keydown.enter.prevent="typeAheadSelect"
        @keydown.delete="maybeDeleteValue"
        class="tao__input ml-1-4">
      <button
        v-show="!disabled && mutableValue.length"
        @click.prevent.stop="updateValue([])"
        type="button"
        class="icon__form inline-block p-0 cursor-pointer
        text-03 bg-transparent text-center rounded m-1-4 rounded-circle">
        <TaoMiniIcon
          class="p-1-4"
          name="close" />
      </button>
      <button
        ref="arrow"
        :disabled="disabled"
        :class="{
          'cursor-pointer': !disabled,
          'opacity-md-max cursor-not-allowed': disabled
        }"
        @click.prevent.self
        @keydown.enter="focused = !focused"
        @blur="handleInputBlur"
        type="button"
        class="icon__form show-icon inline-block p-0 cursor-pointer
        text-03 bg-transparent text-center rounded m-1-4 rounded-circle">
        <TaoMiniIcon
          ref="arrowIcon"
          name="keyboard_arrow_right" />
      </button>
    </FormInput>

    <FormLabel
      v-if="label"
      :for="id">
      {{ label }}
      <slot name="label" />
    </FormLabel>

    <transition :name="transition">
      <div
        v-show="focused"
        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="(option, index) in currentOptions"
            :key="`${index}-tao-select`"
            :class="{ selected: typeAheadPointer === index }"
            @mouseover="typeAheadPointer = index"
            class="font-medium text-01 form__list--li rounded">
            <a
              :class="{
                'form__selected-list text-brand-darker': hasOptionSelected(option),
              }"
              @mousedown.prevent.stop="select(option)"
              class="block text-left rounded cursor-pointer py-1 px-2">
              <slot
                :option="option"
                name="selection">
                {{ getOptionLabel(option) }}
              </slot>
            </a>
          </li>
          <li
            v-if="!currentOptions.length"
            class="tao__form-list-error font-medium text-01
            text-centerpy-1-2 px-2 pointer-events-none">
            <slot name="no-options">
              Não foi possível encontrar resultados
            </slot>
          </li>
        </ul>
      </div>
    </transition>
  </FormGroup>
</template>

<script>
import { isObject, isArray } from 'utils';

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

import FormGroup from './GenericFormGroup.vue';

import typeAheadMixin from './mixins/typeAhead.mixin';
import pointerScrollMixin from './mixins/pointerScroll.mixin';

function valueToArray(value) {
  if (!value) return [];
  if (isArray(value)) return value;

  return [].concat(value);
}

export default {
  /**
   * TODO: Change easteregg LcnoSelectInput
   * to be the new TaoSelectInput
   */
  name: 'LcnoSelectInput',
  components: {
    FormGroup,
    FormLabel: () => import('./GenericLabel'),
    FormInput: () => import('./GenericFormInput'),
    TaoMiniIcon: () => import('packages/TaoMiniIcon'),
  },
  directives: { focus },
  mixins: [typeAheadMixin, pointerScrollMixin],
  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,
    },
    options: {
      type: Array,
      default: () => [],
    },
    optionLabel: {
      type: [String, Function],
      default: 'label',
    },
    optionValue: {
      type: [String, Number, Boolean, Function],
      default: null,
    },
    multiple: {
      type: Boolean,
      default: false,
    },
    taggable: {
      type: Boolean,
      default: false,
    },
    value: {
      type: [String, Number, Object, Array, Boolean, undefined],
      default() { return this.emptyValue; },
    },
    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',
    },
    id: {
      type: String,
      default() {
        return this.label || 'select-input';
      },
    },
  },
  data() {
    return {
      focused: false,
      mousedown: -1,
      search: '',
      mutableValue: valueToArray(this.value),
      pushedTags: [],
    };
  },
  computed: {
    taggableOptions() {
      return this.options.concat(this.pushedTags);
    },
    defaultOptions() {
      return this.search
        ? this.filter(this.options, this.search)
        : this.options;
    },
    currentOptions() {
      const options = this.defaultOptions;

      if (this.taggable && this.search.length && !this.optionExists(this.search)) {
        options.unshift(this.search);
      }

      return options;
    },
    currentValue() {
      if (this.multiple) {
        return this.mutableValue;
      }
      if (this.mutableValue.hasOwnProperty(0)) {
        return this.mutableValue[0];
      }
      return this.emptyValue;
    },
    currentPlaceholder() {
      if (this.multiple) {
        return this.currentValue.length === 0 ? this.placeholder : '';
      }
      return this.currentValue === this.emptyValue ? this.placeholder : '';
    },
  },
  watch: {
    value(value) {
      this.mutableValue = valueToArray(value);
    },
  },
  created() {
    this.$on('option:created', option => this.pushedTags.push(option));
  },
  methods: {
    getOptionLabel(option) {
      if (typeof option === 'object') {
        if (!option.hasOwnProperty(this.optionLabel)) {
          return console.warn(`
            [tao-select-input]: Label key "option.${this.optionLabel}" does not
            exists in options object ${JSON.stringify(option)}.
          `);
        }
        return option[this.optionLabel];
      }
      return option;
    },
    handleInputBlur() {
      this.focused = false;
      this.search = '';
    },
    handleToggleDropdown(event) {
      if (this.disabled) return;

      const {
        formInput, input, arrow, arrowIcon,
      } = this.$refs;
      const { target } = event;

      const toggleTargets = [
        formInput,
        input,
        arrow,
        arrowIcon.$el,
        ...(arrowIcon ? Array.prototype.slice.call(arrowIcon.$el.childNodes) : []),
      ];

      if (toggleTargets.indexOf(target) > -1) {
        this.focused = !this.focused;
        if (this.focused) {
          input.focus();
        } else {
          input.blur();
        }
      } else {
        this.focused = false;
      }
    },
    select(option) {
      if (this.hasOptionSelected(option)) {
        this.deselect(option);
        return;
      }

      if (this.taggable && !this.optionExists(option)) {
        option = this.createOption(option);
      }

      if (this.multiple) {
        option = this.mutableValue.concat(option);
      }

      this.updateValue(option);
      this.onAfterSelect();
    },
    onAfterSelect() {
      const { input } = this.$refs;

      this.focused = false;
      this.search = '';
      input.blur();
    },
    deselect(option) {
      const optionIndex = this.mutableValue.findIndex(val => this.compareOptions(val, option));

      this.mutableValue.splice(optionIndex, 1);
      this.updateValue(this.mutableValue);
    },
    compareOptions(value, option) {
      if (typeof value !== 'object' && typeof option !== 'object') {
        return value === option;
      }
      const hasSameLabel = this.getOptionLabel(value) === this.getOptionLabel(option);

      return hasSameLabel;
    },
    updateValue(value) {
      this.mutableValue = valueToArray(value);

      if (isObject(value) && value.hasOwnProperty(this.optionValue)) {
        value = value[this.optionValue];
      }

      if (isArray(value)) {
        if (value.length && this.multiple) {
          value = value.map(val => val[this.optionValue] || val);
        }
        if (!value.length) {
          value = this.emptyValue;
        }
      }

      this.$emit('input', value);
    },
    createOption(newOption) {
      const option = this.options[0];
      const isOptionObj = isObject(option) && option.hasOwnProperty(this.optionValue);

      if (isOptionObj) {
        newOption = {
          [this.optionLabel]: newOption,
          [this.optionValue]: newOption,
        };
      }

      this.$emit('option:created', newOption);
      return newOption;
    },
    filter(options, search) {
      return options.filter((option) => {
        const label = this.getOptionLabel(option);
        return this.filterBy(label, search);
      });
    },
    filterBy(label, search) {
      const correctLabel = String(label).toLowerCase();

      return correctLabel.indexOf(search.toLowerCase()) > -1;
    },
    hasOptionSelected(option) {
      return this.mutableValue.some(value => this.compareOptions(value, option));
    },
    maybeDeleteValue() {
      if (this.mutableValue && !this.search) {
        if (this.multiple) {
          this.mutableValue.pop();
          this.updateValue(this.mutableValue);
        }
      }
    },
    optionExists(option) {
      return this.defaultOptions.some((opt) => {
        const isSameObj = typeof opt === 'object' && this.getOptionLabel(opt) === option;
        const isSame = opt === option;

        return isSameObj || isSame;
      });
    },
  },
};
</script>
