<template>
  <div :class="classes"
       v-clickoutside="handleClose"
       :style="wrapperStyles">
    <div :class="[prefixCls + '__inner']"
         ref="reference"
         @click="toggleMenu">
      <div class="bcui-tag"
           v-for="(item, index) in selectedMultiple"
           :key="index">
        <span class="bcui-tag-text">{{ item.label }}</span>
        <Icon type="times"
              @click.native.stop="removeTag(index)"></Icon>
      </div>
      <span :class="[prefixCls + '__placeholder']"
            v-show="showPlaceholder && !filterable">{{ localePlaceholder }}</span>
      <span :class="[prefixCls + '__selected-value']"
            v-show="!showPlaceholder && !multiple && !filterable">{{ selectedSingle }}</span>
      <input type="text"
             v-if="filterable"
             v-model="query"
             :class="[prefixCls + '__input']"
             :placeholder="showPlaceholder ? localePlaceholder : ''"
             :style="inputStyle"
             @blur="handleBlur"
             @keydown="resetInputState"
             @keydown.delete="handleInputDelete"
             :disabled="disabled"
             ref="input">
      <Icon type="times"
            :class="[prefixCls + '__arrow']"
            v-show="showCloseIcon"
            @click.native.stop="clearSingleSelect"></Icon>
      <Icon type="triangle-bottom"
            :class="[prefixCls + '__arrow']"
            v-if="!remote"></Icon>
    </div>
    <transition :name="transitionName">
      <Drop v-show="dropVisible"
            :placement="placement"
            ref="dropdown"
            class="bcui-select__dropdown">
        <ul v-show="notFountShow"
            :class="[prefixCls + '__empty']">
          <li>{{ localeNotFoundText }}</li>
        </ul>
        <ul v-show="(!notFound && !remote) || (remote && !loading && !notFound)"
            :class="[prefixCls + '__dropdown-list']">
          <slot></slot>
        </ul>
        <ul v-show="loading"
            :class="[prefixCls + '__loading']">{{ localeLoadingText }}</ul>
      </Drop>
    </transition>
  </div>
</template>

<script>
  import Icon from '../icon';
  import Drop from '../base-drop';
  import clickoutside from '../../directives/clickoutside';
  import { oneOf, findComponentDownward } from '../../utils/tools';

  import Emitter from '../../mixins/emitter';

  const prefixCls = 'bcui-select';

  export default {
    name: 'bc-select',
    mixins: [Emitter],
    components: {
      Icon,
      Drop,
    },
    directives: {
      clickoutside,
    },
    props: {
      value: {
        type: [String, Number, Array],
        default: '',
      },
      label: {
        type: [String, Number, Array],
        default: '',
      },
      multiple: {
        type: Boolean,
        default: false,
      },
      disabled: {
        type: Boolean,
        default: false,
      },
      clearable: {
        type: Boolean,
        default: false,
      },
      placeholder: {
        type: String,
      },
      filterable: {
        type: Boolean,
        default: false,
      },
      filterMethod: {
        type: Function,
      },
      remote: {
        type: Boolean,
        default: false,
      },
      remoteMethod: {
        type: Function,
      },
      loading: {
        type: Boolean,
        default: false,
      },
      loadingText: {
        type: String,
      },
      size: {
        validator(value) {
          return oneOf(value, ['small', 'large', 'default']);
        },
      },
      labelInValue: {
        type: Boolean,
        default: false,
      },
      notFoundText: {
        type: String,
      },
      placement: {
        validator(value) {
          return oneOf(value, ['top', 'bottom']);
        },
        default: 'bottom',
      },
      width: {
        type: Number,
      }
    },
    data() {
      return {
        prefixCls: prefixCls,
        visible: false,
        options: [],
        optionInstances: [],
        selectedSingle: '', // label
        selectedMultiple: [],
        focusIndex: 0,
        query: '',
        lastQuery: '',
        selectToChangeQuery: false, // when select an option, set this first and set query, because query is watching, it will emit event
        inputLength: 20,
        notFound: false,
        slotChangeDuration: false, // if slot change duration and in multiple, set true and after slot change, set false
        model: this.value,
        currentLabel: this.label,
      };
    },
    computed: {
      classes() {
        return [
          `${prefixCls}`,
          {
            [`${prefixCls}--visible`]: this.visible,
            [`${prefixCls}--disabled`]: this.disabled,
            [`${prefixCls}--multiple`]: this.multiple,
            [`${prefixCls}--single`]: !this.multiple,
            [`${prefixCls}--show-clear`]: this.showCloseIcon,
            [`${prefixCls}--${this.size}`]: !!this.size,
          },
        ];
      },
      showPlaceholder() {
        let status = false;
        if (typeof this.model === 'string') {
          if (this.model === '') {
            status = true;
          }
        } else if (Array.isArray(this.model)) {
          if (!this.model.length) {
            status = true;
          }
        } else if (this.model === null) {
          status = true;
        }
        return status;
      },
      
      showCloseIcon() {
        return !this.multiple && this.clearable && !this.showPlaceholder;
      },

      wrapperStyles(){
        let style = {};
        if (this.width) {
          style.width = `${this.width}px`;
        }
        return style;
      },

      inputStyle() {
        let style = {};
        if (this.multiple) {
          if (this.showPlaceholder) {
            style.width = '100%';
          } else {
            style.width = `${this.inputLength}px`;
          }
        }
        return style;
      },
      localePlaceholder() {
        if (this.placeholder === undefined) {
          return '请选择';
        } else {
          return this.placeholder;
        }
      },
      localeNotFoundText() {
        if (this.notFoundText === undefined) {
          return '没有匹配数据';
        } else {
          return this.notFoundText;
        }
      },
      localeLoadingText() {
        if (this.loadingText === undefined) {
          return '加载中...';
        } else {
          return this.loadingText;
        }
      },
      transitionName() {
        return this.placement === 'bottom' ? 'slide-up' : 'slide-down';
      },
      dropVisible() {
        let status = true;
        const options = this.$slots.default || [];
        if (!this.loading && this.remote && this.query === '' && !options.length) status = false;
        return this.visible && status;
      },
      notFountShow() {
        const options = this.$slots.default || [];
        return (this.notFound && !this.remote) || (this.remote && !this.loading && !options.length);
      },
    },
    methods: {
      toggleMenu() {
        if (this.disabled) {
          return false;
        }
        this.visible = !this.visible;
      },
      hideMenu() {
        this.visible = false;
        this.focusIndex = 0;
        this.broadcast('bc-option', 'on-select-close');
      },
      // find option component
      findChild(cb) {
        const find = function(child) {
          const name = child.$options.componentName;
          if (name) {
            cb(child);
          } else if (child.$children.length) {
            child.$children.forEach((innerChild) => {
              find(innerChild, cb);
            });
          }
        };
        if (this.optionInstances.length) {
          this.optionInstances.forEach((child) => {
            find(child);
          });
        } else {
          this.$children.forEach((child) => {
            find(child);
          });
        }
      },
      updateOptions(init, slot = false) {
        let options = [];
        let index = 1;
        this.findChild((child) => {
          options.push({
            value: child.value,
            label: child.label === undefined ? child.$el.innerHTML : child.label,
          });
          child.index = index++;
          if (init) {
            this.optionInstances.push(child);
          }
        });
        this.options = options;
        if (init) {
          if (!this.remote) {
            this.updateSingleSelected(true, slot);
            this.updateMultipleSelected(true, slot);
          }
        }
      },
      updateSingleSelected(init = false, slot = false) {
        const type = typeof this.model;
        if (type === 'string' || type === 'number') {
          let findModel = false;
          for (let i = 0; i < this.options.length; i++) {
            if (this.model === this.options[i].value) {
              this.selectedSingle = this.options[i].label;
              findModel = true;
              break;
            }
          }
          if (slot && !findModel) {
            this.model = '';
            this.query = '';
          }
        }
        this.toggleSingleSelected(this.model, init);
      },
      clearSingleSelect() {
        if (this.showCloseIcon) {
          this.findChild((child) => {
            child.selected = false;
          });
          this.model = '';
          if (this.filterable) {
            this.query = '';
          }
        }
      },
      updateMultipleSelected(init = false, slot = false) {
        if (this.multiple && Array.isArray(this.model)) {
          let selected = this.remote ? this.selectedMultiple : [];
          for (let i = 0; i < this.model.length; i++) {
            const model = this.model[i];
            for (let j = 0; j < this.options.length; j++) {
              const option = this.options[j];
              if (model === option.value) {
                selected.push({
                  value: option.value,
                  label: option.label,
                });
              }
            }
          }
          const selectedArray = [];
          const selectedObject = {};
          selected.forEach((item) => {
            if (!selectedObject[item.value]) {
              selectedArray.push(item);
              selectedObject[item.value] = 1;
            }
          });
          this.selectedMultiple = this.remote ? selectedArray : selected;
          if (slot) {
            let selectedModel = [];
            for (let i = 0; i < selected.length; i++) {
              selectedModel.push(selected[i].value);
            }
            // if slot change and remove a selected option, emit user
            if (this.model.length === selectedModel.length) {
              this.slotChangeDuration = true;
            }
            this.model = selectedModel;
          }
        }
        this.toggleMultipleSelected(this.model, init);
      },
      removeTag(index) {
        if (this.disabled) {
          return false;
        }
        if (this.remote) {
          const tag = this.model[index];
          this.selectedMultiple = this.selectedMultiple.filter((item) => item.value !== tag);
        }
        this.model.splice(index, 1);
        if (this.filterable && this.visible) {
          this.$refs.input.focus();
        }
        this.broadcast('Drop', 'on-update-popper');
      },
      // to select option for single
      toggleSingleSelected(value, init = false) {
        if (!this.multiple) {
          let label = '';
          this.findChild((child) => {
            if (child.value === value) {
              child.selected = true;
              label = child.label === undefined ? child.$el.innerHTML : child.label;
            } else {
              child.selected = false;
            }
          });
          this.hideMenu();
          if (!init) {
            if (this.labelInValue) {
              this.$emit('on-change', {
                value: value,
                label: label,
              });
              this.dispatch('bc-form-item', 'on-form-change', {
                value: value,
                label: label,
              });
            } else {
              this.$emit('on-change', value);
              this.dispatch('bc-form-item', 'on-form-change', value);
            }
          }
        }
      },
      // to select option for multiple
      toggleMultipleSelected(value, init = false) {
        if (this.multiple) {
          let hybridValue = [];
          for (let i = 0; i < value.length; i++) {
            hybridValue.push({
              value: value[i],
            });
          }
          this.findChild((child) => {
            const index = value.indexOf(child.value);
            if (index >= 0) {
              child.selected = true;
              hybridValue[index].label =
                child.label === undefined ? child.$el.innerHTML : child.label;
            } else {
              child.selected = false;
            }
          });
          if (!init) {
            if (this.labelInValue) {
              this.$emit('on-change', hybridValue);
              this.dispatch('bc-form-item', 'on-form-change', hybridValue);
            } else {
              this.$emit('on-change', value);
              this.dispatch('bc-form-item', 'on-form-change', value);
            }
          }
        }
      },
      handleClose() {
        this.hideMenu();
      },
      handleKeydown(e) {
        if (this.visible) {
          const keyCode = e.keyCode;
          // Esc slide-up
          if (keyCode === 27) {
            e.preventDefault();
            this.hideMenu();
          }
          // next
          if (keyCode === 40) {
            e.preventDefault();
            this.navigateOptions('next');
          }
          // prev
          if (keyCode === 38) {
            e.preventDefault();
            this.navigateOptions('prev');
          }
          // enter
          if (keyCode === 13) {
            e.preventDefault();
            this.findChild((child) => {
              if (child.isFocus) {
                child.select();
              }
            });
          }
        }
      },
      navigateOptions(direction) {
        if (direction === 'next') {
          const next = this.focusIndex + 1;
          this.focusIndex = this.focusIndex === this.options.length ? 1 : next;
        } else if (direction === 'prev') {
          const prev = this.focusIndex - 1;
          this.focusIndex = this.focusIndex <= 1 ? this.options.length : prev;
        }
        let child_status = {
          disabled: false,
          hidden: false,
        };
        let find_deep = false; // can next find allowed
        this.findChild((child) => {
          if (child.index === this.focusIndex) {
            child_status.disabled = child.disabled;
            child_status.hidden = child.hidden;
            if (!child.disabled && !child.hidden) {
              child.isFocus = true;
            }
          } else {
            child.isFocus = false;
          }
          if (!child.hidden && !child.disabled) {
            find_deep = true;
          }
        });
        this.resetScrollTop();
        if ((child_status.disabled || child_status.hidden) && find_deep) {
          this.navigateOptions(direction);
        }
      },
      resetScrollTop() {
        const index = this.focusIndex - 1;
        let bottomOverflowDistance =
          this.optionInstances[index].$el.getBoundingClientRect().bottom -
          this.$refs.dropdown.$el.getBoundingClientRect().bottom;
        let topOverflowDistance =
          this.optionInstances[index].$el.getBoundingClientRect().top -
          this.$refs.dropdown.$el.getBoundingClientRect().top;
        if (bottomOverflowDistance > 0) {
          this.$refs.dropdown.$el.scrollTop += bottomOverflowDistance;
        }
        if (topOverflowDistance < 0) {
          this.$refs.dropdown.$el.scrollTop += topOverflowDistance;
        }
      },
      handleBlur() {
        setTimeout(() => {
          const model = this.model;
          if (this.multiple) {
            this.query = '';
          } else {
            if (model !== '') {
              this.findChild((child) => {
                if (child.value === model) {
                  this.query = child.label === undefined ? child.searchLabel : child.label;
                }
              });
              // 如果删除了搜索词，下拉列表也清空了，所以强制调用一次remoteMethod
              if (this.remote && this.query !== this.lastQuery) {
                this.$nextTick(() => {
                  this.query = this.lastQuery;
                });
              }
            } else {
              this.query = '';
            }
          }
        }, 300);
      },
      resetInputState() {
        this.inputLength = this.$refs.input.value.length * 12 + 20;
      },
      handleInputDelete() {
        if (this.multiple && this.model.length && this.query === '') {
          this.removeTag(this.model.length - 1);
        }
      },
      // use when slot changed
      slotChange() {
        this.options = [];
        this.optionInstances = [];
      },
      setQuery(query) {
        if (!this.filterable) return;
        this.query = query;
      },
      modelToQuery() {
        if (!this.multiple && this.filterable && this.model !== undefined) {
          this.findChild((child) => {
            if (this.model === child.value) {
              if (child.label) {
                this.query = child.label;
              } else if (child.searchLabel) {
                this.query = child.searchLabel;
              } else {
                this.query = child.value;
              }
            }
          });
        }
      },
      broadcastQuery(val) {
        if (findComponentDownward(this, 'bc-option-group')) {
          this.broadcast('bc-option-group', 'on-query-change', val);
          this.broadcast('bc-option', 'on-query-change', val);
        } else {
          this.broadcast('bc-option', 'on-query-change', val);
        }
      },
      updateLabel() {
        if (this.remote) {
          if (!this.multiple && this.model !== '') {
            this.selectToChangeQuery = true;
            if (this.currentLabel === '') this.currentLabel = this.model;
            this.lastQuery = this.currentLabel;
            this.query = this.currentLabel;
          } else if (this.multiple && this.model.length) {
            if (this.currentLabel.length !== this.model.length) this.currentLabel = this.model;
            this.selectedMultiple = this.model.map((item, index) => {
              return {
                value: item,
                label: this.currentLabel[index],
              };
            });
          }
        }
      },
    },
    mounted() {
      this.modelToQuery();

      // 处理 remote 初始值
      this.updateLabel();

      this.$nextTick(() => {
        this.broadcastQuery('');
      });

      this.updateOptions(true);

      document.addEventListener('keydown', this.handleKeydown);

      this.$on('append', () => {
        if (!this.remote) {
          this.modelToQuery();
          this.$nextTick(() => {
            this.broadcastQuery('');
          });
        } else {
          this.findChild((child) => {
            child.selected = this.multiple
              ? this.model.indexOf(child.value) > -1
              : this.model === child.value;
          });
        }
        this.slotChange();
        this.updateOptions(true, true);
      });
      this.$on('remove', () => {
        if (!this.remote) {
          this.modelToQuery();
          this.$nextTick(() => {
            this.broadcastQuery('');
          });
        } else {
          this.findChild((child) => {
            child.selected = this.multiple
              ? this.model.indexOf(child.value) > -1
              : this.model === child.value;
          });
        }
        this.slotChange();
        this.updateOptions(true, true);
      });
      this.$on('on-select-selected', (value) => {
        if (this.model === value) {
          this.hideMenu();
        } else {
          if (this.multiple) {
            const index = this.model.indexOf(value);
            if (index >= 0) {
              this.removeTag(index);
            } else {
              this.model.push(value);
              this.broadcast('Drop', 'on-update-popper');
            }
            if (this.filterable) {
              // remote&filterable&multiple时，一次点多项，不应该设置true，因为无法置为false，下次的搜索会失效
              if (this.query !== '') this.selectToChangeQuery = true;
              this.query = '';
              this.$refs.input.focus();
            }
          } else {
            this.model = value;
            if (this.filterable) {
              this.findChild((child) => {
                if (child.value === value) {
                  if (this.query !== '') this.selectToChangeQuery = true;
                  this.lastQuery = this.query =
                    child.label === undefined ? child.searchLabel : child.label;
                }
              });
            }
          }
        }
      });
    },
    beforeDestroy() {
      document.removeEventListener('keydown', this.handleKeydown);
    },
    watch: {
      value(val) {
        this.model = val;
        if (val === '') this.query = '';
      },
      label(val) {
        this.currentLabel = val;
        this.updateLabel();
      },
      model() {
        this.$emit('input', this.model);
        this.modelToQuery();
        if (this.multiple) {
          if (this.slotChangeDuration) {
            this.slotChangeDuration = false;
          } else {
            this.updateMultipleSelected();
          }
        } else {
          this.updateSingleSelected();
        }
        // #957
        if (!this.visible && this.filterable) {
          this.$nextTick(() => {
            this.broadcastQuery('');
          });
        }
      },
      visible(val) {
        if (val) {
          if (this.filterable) {
            if (this.multiple) {
              this.$refs.input.focus();
            } else {
              this.$refs.input.select();
            }
            if (this.remote) {
              this.findChild((child) => {
                child.selected = this.multiple
                  ? this.model.indexOf(child.value) > -1
                  : this.model === child.value;
              });
              // remote下，设置了默认值，第一次打开时，搜索一次
              const options = this.$slots.default || [];
              if (this.query !== '' && !options.length) {
                this.remoteMethod(this.query);
              }
            }
          }
          this.broadcast('Drop', 'on-update-popper');
        } else {
          if (this.filterable) {
            this.$refs.input.blur();
            // #566 reset options visible
            setTimeout(() => {
              this.broadcastQuery('');
            }, 300);
          }
          this.broadcast('Drop', 'on-destroy-popper');
        }
      },
      query(val) {
        if (this.remote && this.remoteMethod) {
          if (!this.selectToChangeQuery) {
            this.$emit('on-query-change', val);
            this.remoteMethod(val);
          }
          this.focusIndex = 0;
          this.findChild((child) => {
            child.isFocus = false;
          });
        } else {
          if (!this.selectToChangeQuery) {
            this.$emit('on-query-change', val);
          }
          this.broadcastQuery(val);
          let is_hidden = true;
          this.$nextTick(() => {
            this.findChild((child) => {
              if (!child.hidden) {
                is_hidden = false;
              }
            });
            this.notFound = is_hidden;
          });
        }
        this.selectToChangeQuery = false;
        this.broadcast('Drop', 'on-update-popper');
      },
    },
  };
</script>




