<template>
  <ElPopover
    :append-to-body="appendToBody"
    :disabled="disableClick"
    :popper-class="popperClass"
    :trigger="popoverTrigger"
    :value="showPopover"
    :visible-arrow="false"
    placement="bottom"
    :width="popoverWidth"
    @show="onShow"
    @hide="onHide"
  >
    <ElInput
      slot="reference"
      ref="multi-select-input"
      :disabled="disableClick"
      :value="displayValue"
      class="multi-select-dropdown"
      size="large"
      placeholder="Select"
      readonly
      @keydown.native.enter="onInputEnter"
      @keydown.native.tab="handleInputTab"
      @click.native="togglePopover"
    />

    <label
      v-if="options.length === 0"
      class="empty-text"
    >{{ emptyText }}</label>

    <TfCheckbox
      v-if="options.length > 1"
      ref="check-all"
      v-model="checkAll"
      :indeterminate="false"
      :disabled="disabledCheckBoxes || forceDisableCheckboxClick"
      data-test="select all options"
      @change="handleCheckAllChange"
      @keydown.native.enter="handleCheckAllChange(!checkAll)"
    >
      Select all
    </TfCheckbox>

    <ElCheckboxGroup
      v-model="clonedValue"
      @change="handleMultiSelectChange"
    >
      <TfCheckbox
        v-for="(option, index) in options"
        ref="checkbox"
        :key="option.id"
        :data-test="`toggle comments for ${option[label]}`"
        :disabled="disabledCheckBoxes || forceDisableCheckboxClick"
        :label="option[valueKey]"
        @keydown.native.enter="onCheckBoxEnter"
        @keydown.native.tab="handleCheckboxTab($event, index)"
      >
        {{ option[label] }}
      </TfCheckbox>
    </ElCheckboxGroup>
  </ElPopover>
</template>

<script>
  import { isEqual } from 'lodash';

  export default {
    props: {
      disableClick: {
        type: Boolean,
        default: false,
      },
      value: {
        type: Array,
        default: null,
      },
      options: {
        type: Array,
        default: () => [],
      },
      label: {
        type: String,
        default: 'value',
      },
      concatenateLabels: {
        type: Boolean,
        default: false,
      },
      valueKey: {
        type: String,
        default: 'value',
      },
      emptyText: {
        type: String,
        default: 'No Options Available',
      },
      forceDisableCheckboxClick: {
        type: Boolean,
        default: false,
      },
      totalOptionsAvailable: {
        type: Number,
        default: 0,
      },
      appendToBody: {
        type: Boolean,
        default: false,
      },
      popperClass: {
        type: String,
        default: 'multi-select-popover',
      },
      popoverTrigger: {
        type: String,
        default: 'click',
      },
      searchableParent: {
        type: String,
        default: null,
      },
      nextTabbableElement: {
        type: String,
        default: null,
      },
      width: {
        type: [Number, String],
        default: 'auto',
      },
    },
    data: (vm) => ({
      checkAll: false,
      clonedValue: vm.value.slice(0),
      initialValues: null,
      showPopover: false,
      popoverVisible: false,
    }),
    computed: {
      /**
       * If there is only one option we disabled the checkboxes
       *
       * @returns {boolean}
       */
      disabledCheckBoxes() {
        return this.totalOptionsAvailable.length === 1;
      },
      /**
       * What value we display when in the main multi select "label"
       *
       * @returns {string}
       */
      displayValue() {
        const labels = [];

        // populate labels array
        this.options.forEach((option) => {
          if (this.value.indexOf(option[this.valueKey]) > -1) {
            labels.push(option[this.label]);
          }
        });
        // If all the options are selected
        if (this.options.length && (Number(this.totalOptionsAvailable) === labels.length)) {
          return 'All';
        } if (!labels.some((label) => Number.isNaN(label))) {
          // this is array of only numbers so lets roll them up
          return this.$TF.Formatting.getRanges(labels).join(', ');
        } if (this.concatenateLabels || labels.length === 1) {
          return labels.join(', ');
        }
        return '(Multi)';
      },
      /**
       * get the correct width for the popover
       *
       * @returns {string}
       */
      popoverWidth() {
        return this.popoverVisible && this.width === 'auto'
          ? this.$el.offsetWidth
          : this.width;
      },
    },
    watch: {
      /**
       * When the value changes we need to check if we should mark the 'checkall' checkbox
       *
       * @param {Array} currentValue
       * @param {Array} oldValue
       */
      value(currentValue, oldValue) {
        this.clonedValue = this.value.slice(0);
        const checkedCount = this.value.length;
        this.checkAll = checkedCount === this.options.length;
        const containersEqual = isEqual(oldValue, this.value);

        if (containersEqual) {
          return;
        }

        this.$emit('input', this.value);
        this.$emit('change', { value: this.value, oldValue });
      },
    },
    created() {
      const checkedCount = this.value.length;
      this.checkAll = checkedCount === this.options.length;
      if (this.totalOptionsAvailable === 1) {
        this.handleCheckAllChange(true);
      }
    },
    methods: {
      /**
       * Change the popoverVisible to true and
       * set the initial values to the value prop.
       */
      onShow() {
        this.popoverVisible = true;
        this.initialValues = this.value;
      },
      /**
       * We store the popoverVisible on show/hide
       * so we know if we have to close the select once the user tabs off.
       */
      onHide() {
        this.popoverVisible = false;
        this.$emit('hide', { value: this.value, initialValues: this.initialValues });
        this.initialValues = null;
      },
      /**
       * If the popover trigger is manual,
       * we use the 'showPopover' variable to control it and we toggle it on focus.
       */
      focus() {
        this.showPopover = true;
      },
      /**
       * If the popover trigger is manual,
       * we use the 'showPopover' variable to control it and toggle it on blur.
       */
      blur() {
        this.showPopover = false;
      },
      /**
       * Click on the checkbox when a checkbox is in focus the user hits enter.
       *
       * @param {event} event
       */
      onCheckBoxEnter(event) {
        const checkbox = event.target;
        checkbox.click();
      },
      /**
       * Toggle popover if the user hits enter when on the main input.
       */
      onInputEnter() {
        if (this.popoverTrigger === 'manual') {
          this.showPopover = !this.showPopover;
        } else {
          this.$refs['multi-select-input'].$el.click();
        }
      },
      /**
       * If the user hits tab while focused on the main input,
       * we move the user to the next checkbox.
       * For some reason Element puts everything in a strange order.
       *
       * @param {event} event
       */
      handleInputTab(event) {
        if (!event.shiftKey && this.popoverVisible) {
          const checkall = this.$refs['check-all'];
          const inputs = this.$refs.checkbox;
          if (checkall) {
            window.setTimeout(() => {
              checkall.$el.querySelector('input').focus();
            }, 100);
          } else if (inputs.length) {
            window.setTimeout(() => {
              inputs[0].$el.querySelector('input').focus();
            }, 100);
          }
        }
      },
      /**
       * If the user hits tab while focused on checkbox
       * or if the user was on the last checkbox,
       * we hide the popover and focus on the next elment outside of it
       *
       * @param {event} event
       * @param {number} index
       */
      handleCheckboxTab(event, index) {
        if (
          index === this.options.length - 1
          && this.nextTabbableElement
          && this.searchableParent
        ) {
          event.preventDefault();
          if (this.popoverTrigger === 'manual') {
            this.showPopover = !this.showPopover;
          } else {
            this.$refs['multi-select-input'].$el.click();
          }
          this.$el.closest(this.searchableParent).querySelector(this.nextTabbableElement).focus();
        }
      },
      /**
       * Manually toggle the popover
       */
      togglePopover() {
        if (this.popoverTrigger === 'manual') {
          this.showPopover = !this.showPopover;
        }
      },
      /**
       * Handle the click of single checkbox
       *
       * @param {Array} value
       */
      handleMultiSelectChange(value) {
        const checkedCount = value.length;
        this.checkAll = checkedCount === this.options.length;
        this.$emit('input', value);
      },
      /**
       * Handle the click of check all checkbox
       *
       * @param {boolean} checkAll
       */
      handleCheckAllChange(checkAll) {
        if (checkAll) {
          const values = [];
          this.options.forEach((option) => {
            values.push(option[this.valueKey]);
          });
          this.$emit('input', values);
          this.$emit('update');
        } else {
          this.$emit('input', []);
        }
      },
    },
  };
</script>
