<template>
  <div class="qf-select" :class="[disabled ? 'is-disabled' : '', size ? 'qf-select-' + size : '']" tabindex="1">
    <div @click="toggleMenu" class="qf-select-input" @mouseenter="inputHovering = true" @mouseleave="inputHovering = false" :class="[inputHovering ? 'is-hover': '']">
      <input type="text" :placeholder="placeholder" v-model="selectedLabel" ref="reference" readonly :disabled="disabled" @keydown.down.prevent="navigateOptions('next')" @keydown.up.prevent="navigateOptions('prev')" @keydown.enter.prevent="selectOption" @keydown.esc.stop.prevent="visible = false" @focus="handleFocus" @blur="handleBlur">
      <span class="qf-select-input-suffix">
        <qf-font-icon v-show="!showClear" class="qf-select-trigger" name="ic-ic_collect" :class="[visible ? 'is-reverse' : '']"></qf-font-icon>
        <qf-font-icon v-show="showClear" class="qf-select-trigger" name="ic-ic_clean" @click="clear"></qf-font-icon>
      </span>
    </div>
    <qf-popper ref="popper" placement="bottom-start" trigger="manual" :popperClass="'qf-select-popper'" :preventOverflow="false" :visibleArrow="false">
      <qf-input v-if="searchable" v-model="query" @input="handleQueryChange" @keydown.native.down.stop.prevent="navigateOptions('next')" @keydown.native.up.stop.prevent="navigateOptions('prev')" @keydown.native.enter.prevent="selectOption" @keydown.native.esc.stop.prevent="visible = false" :maxlength="maxlength" :placeholder="autoCreate ? t('qf.select.create'): t('qf.select.search')" ref="search"></qf-input>
      <transition>
        <ul class="qf-select-options" :style="optionsStyle">
          <qf-option v-for="item in createdOptions" created :key="getCreateKey(item)" :label="item.label" :value="item.value">
          </qf-option>
          <slot></slot>
        </ul>
      </transition>
    </qf-popper>
  </div>
</template>

<script>
import ObjectUtil from '../../util/object';
import ScrollUtil from '../../util/scroll';
import QfPopper from '../../popper';
import QfInput from '../../input';
import QfOption from '../../option';
import QfFontIcon from '../../font-icon';
import NavigationMixin from '../../util/navigation';
import { on, off } from '../../util/dom';
import LocaleMixin from '../../locale/mixin';
import { t } from '../../locale';

export default {
  name: 'QfSelect',
  mixins: [LocaleMixin, NavigationMixin],

  components: {
    QfPopper,
    QfOption,
    QfFontIcon,
    QfInput
  },
  props: {
    placeholder: {
      type: String,
      default() {
        return t('qf.select.placeholder');
      }
    },
    name: String,
    id: String,
    value: {
      required: true
    },
    displayField: String,
    disabled: Boolean,
    searchable: Boolean,
    max: Number,
    size: String,
    clearable: Boolean,
    maxlength: Number,
    multiple: Boolean,
    multipleLimit: {
      type: Number,
      default: 0
    },
    defaultFirstOption: {
      type: Boolean,
      default: true
    },
    autoCreate: {
      type: Boolean,
      default: false
    },
    valueKey: String
  },
  provide() {
    return {
      'select': this
    };
  },
  data() {
    return {
      options: [],
      cachedOptions: [],
      createdOptions: [],
      selected: this.multiple ? [] : {},
      optionsCount: 0,
      filteredOptionsCount: 0,
      inputHovering: false,
      visible: false,
      hoverIndex: -1,
      query: '',
      previousQuery: null,
      minWidth: 0
    };
  },
  created() {
    if (this.multiple && !Array.isArray(this.value)) {
      this.$emit('input', []);
    }
    if (!this.multiple && Array.isArray(this.value)) {
      this.$emit('input', '');
    }
    this.$on('optionClick', this.handleOptionSelect);
  },
  mounted() {
    on(document, 'click', this.handleDocumentClick);
    this.setSelected();
    this.$nextTick(() => {
      this.minWidth = this.$el.clientWidth - 8;
    });
  },

  computed: {
    optionsStyle: {
      cache: false,
      get() {
        var maxHeight = 32 * 6;
        if (this.max) {
          maxHeight = this.max * 32;
        }
        return {
          maxHeight: maxHeight + 'px',
          minWidth: this.minWidth + 'px',
          width: this.minWidth + 'px'
        };
      }
    },
    showClear() {
      if (!this.disabled && this.inputHovering && this.clearable && ((!Array.isArray(this.value) && this.value) || (Array.isArray(this.value) && this.value.length > 0))) {
        return true;
      }
      return false;
    },
    selectedLabel() {
      if (this.selected) {
        if (this.multiple) {
          var label = '';
          var i = 0;
          this.selected.forEach((item) => {
            if (i > 0) {
              label += ';';
            }
            label += item.currentLabel;
            i++;
          });
          return label;
        } else {
          return this.selected.label;
        }
      } else {
        return '';
      }
    }
  },

  watch: {
    value(val) {
      this.setSelected();
    },

    visible(val) {
      if (!val) {
        this.query = '';
        this.previousQuery = null;
        this.resetHoverIndex();
        this.$refs.popper.doClose();
      } else {
        if (this.$slots.default || this.autoCreate) {
          this.$emit('queryChange', this.query);
          this.$refs.popper.doShow();
        }
        if (this.searchable) {
          this.$nextTick(() => {
            this.$refs.search.focus();
          });
        }
      }
      this.$emit('visible-change', val);
    },

    options() {
      let inputs = this.$el.querySelectorAll('input');
      if ([].indexOf.call(inputs, document.activeElement) === -1) {
        this.setSelected();
      }
      if (this.defaultFirstOption && this.filteredOptionsCount) {
        this.checkDefaultFirstOption();
      }
    }
  },

  methods: {

    getCreateKey(item) {
      let value = item.value;
      return this.valueKey ? value[this.valueKey] : value;
    },

    emitChange(val) {
      if (!ObjectUtil.valueEquals(this.value, val)) {
        this.$emit('change', val);
      }
    },

    focus() {
      this.$refs.reference.focus();
    },

    handleOptionSelect(option) {
      if (this.multiple) {
        const value = this.value.slice();
        const optionIndex = this.getValueIndex(value, option.value);
        if (optionIndex > -1) {
          value.splice(optionIndex, 1);
        } else if (this.multipleLimit <= 0 || value.length < this.multipleLimit) {
          value.push(option.value);
        }
        this.$emit('input', value);
        this.emitChange(value);
      } else {
        this.$emit('input', option.value);
        this.emitChange(option.value);
        this.visible = false;
      }
      this.$nextTick(() => this.scrollToOption(option));
    },

    clear(event) {
      event.stopPropagation();
      let result = '';
      if (this.multiple) {
        result = [];
      }
      this.$emit('input', result);
      this.emitChange(result);
      this.visible = false;
      this.$emit('clear');
    },

    handleFocus(event) {
      this.$emit('focus', event);
    },

    handleBlur(event) {
      this.$emit('blur', event);
    },

    handleClose() {
      this.visible = false;
    },

    scrollToOption(option) {
      const target = Array.isArray(option) && option[0] ? option[0].$el : option.$el;
      if (this.$refs.popper && target) {
        const menu = this.$refs.popper.$el.querySelector('.qf-select-options');
        ScrollUtil.scrollIntoView(menu, target);
      }
    },

    getValueIndex(arr = [], value) {
      const isObject = Object.prototype.toString.call(value).toLowerCase() === '[object object]';
      if (!isObject) {
        return arr.indexOf(value);
      } else {
        const valueKey = this.valueKey;
        let index = -1;
        arr.some((item, i) => {
          if (ObjectUtil.getValueByPath(item, valueKey) === ObjectUtil.getValueByPath(value, valueKey)) {
            index = i;
            return true;
          }
          return false;
        });
        return index;
      }
    },

    handleQueryChange(val) {
      this.hoverIndex = -1;
      this.$emit('queryChange', val);
      if (this.defaultFirstOption && this.filteredOptionsCount) {
        this.checkDefaultFirstOption();
      }
    },

    checkDefaultFirstOption() {
      this.hoverIndex = -1;
      for (let i = 0; i !== this.options.length; ++i) {
        const option = this.options[i];
        if (this.query) {
          // highlight first options that passes the filter
          if (!option.disabled && !option.groupDisabled && option.visible) {
            this.hoverIndex = i;
            break;
          }
        } else {
          // highlight currently selected option
          if (option.itemSelected) {
            this.hoverIndex = i;
            break;
          }
        }
      }
    },

    onOptionDestroy(index) {
      if (index > -1) {
        this.optionsCount--;
        this.filteredOptionsCount--;
        this.options.splice(index, 1);
      }
    },

    resetHoverIndex() {
      setTimeout(() => {
        if (!this.multiple) {
          this.hoverIndex = this.options.indexOf(this.selected);
        } else {
          if (this.selected.length > 0) {
            this.hoverIndex = Math.min.apply(null, this.selected.map(item => this.options.indexOf(item)));
          } else {
            this.hoverIndex = -1;
          }
        }
      }, 300);
    },

    setSelected() {
      if (!this.multiple) {
        let option = this.getOption(this.value);
        this.selected = option;
        return;
      }
      let result = [];
      if (Array.isArray(this.value)) {
        this.value.forEach(value => {
          result.push(this.getOption(value));
        });
      }
      this.selected = result;
    },

    getOption(value) {
      let option;
      const isObject = Object.prototype.toString.call(value).toLowerCase() === '[object object]';
      for (let i = this.cachedOptions.length - 1; i >= 0; i--) {
        const cachedOption = this.cachedOptions[i];
        const isEqual = isObject
          ? ObjectUtil.getValueByPath(cachedOption.value, this.valueKey) === ObjectUtil.getValueByPath(value, this.valueKey)
          : cachedOption.value === value;
        if (isEqual) {
          option = cachedOption;
          break;
        }
      }
      if (option) return option;
      const label = !isObject
        ? value : (this.displayField ? value[this.displayField] : '');
      let newOption = {
        value: value,
        currentLabel: label,
        label: label
      };
      return newOption;
    },

    selectOption() {
      if (this.options[this.hoverIndex]) {
        this.handleOptionSelect(this.options[this.hoverIndex]);
      } else if (this.query && this.autoCreate) {
        let value = this.query;
        if (this.valueKey) {
          value = {};
          value[this.valueKey] = this.query;
        }
        let newOption = {
          value: value,
          label: this.query,
          currentLabel: this.query,
          created: true
        };
        this.createdOptions.splice(0, 0, newOption);
        this.handleOptionSelect(newOption);
        this.query = '';
        this.$emit('queryChange', this.query);
      }
    },

    handleDocumentClick(e) {
      const popper = this.$refs.popper;

      if (!this.$el || this.$el.contains(e.target) || !popper || popper.$el.contains(e.target)) return;
      this.visible = false;
    },

    toggleMenu() {
      if (!this.disabled && (this.$slots.default || this.autoCreate)) {
        this.visible = !this.visible;
      }
    }
  },

  beforeDestroy() {
    off(document, 'click', this.handleDocumentClick);
  }
};
</script>
