<template>
  <q-select
    ref="select"
    class="select-base"
    filled
    :multiple="multiple"
    :options="filteredOptions"
    dense
    hide-bottom-space
    borderless
    v-bind="$attrs"
    :option-label="optionLabel"
    :rules="irules"
    lazy-rules
    :value="innerValue"
    :emit-value="emitValue"
    v-on="listeners"
    @popup-hide="clearFilterInput"
    @virtual-scroll="setFocus"
  >
    <template #option="scope">
      <template v-if="scope.index === 0">
        <q-input 
          v-if="showInput" 
          v-model="filter" 
          outlined 
          dense 
          debounce="500" 
          ref="input"
        >
          <template #append>
            <q-icon name="search" size="20px" />
          </template>
        </q-input>
        <q-item v-if="multiple && allButton">
          <q-item-section side>
            <q-checkbox
              :value="allSelected"
              :indeterminate-value="null"
              @input="iallSelected"
            />
          </q-item-section>
          <q-item-section>
            <q-item-label v-text="$t('base.all')" />
          </q-item-section>
        </q-item>
        <q-separator inset />
      </template>
      <q-separator v-if="scope.opt.separator" inset />
      <q-item
        v-bind="scope.itemProps"
        v-on="scope.itemEvents"
        :class="{ 'no-pointer-events' : !scope.opt }"
      >
        <q-item-section side v-if="multiple">
          <q-checkbox :value="scope.selected" @input="scope.itemEvents.click" />
        </q-item-section>
        <q-item-section>
          <q-item-label v-html="scope.opt[optionLabel]" />
        </q-item-section>
      </q-item>
    </template>
  </q-select>
</template>

<script lang="ts">
import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
import isEmpty from 'lodash/isEmpty';
import isString from 'lodash/isString';
import { QSelect } from 'quasar';

@Component({
  inheritAttrs: false,
})
export default class SelectBase extends Vue {
  @Prop({ type: [Boolean, String] }) required: boolean | string;
  @Prop() rules: any[];
  @Prop({ default: 'value' }) optionValue: string;
  @Prop({ type: Boolean, default: true }) multiple: boolean;
  @Prop({ default: 'label' }) optionLabel: string;
  @Prop({ type: [Boolean, Number, String] }) useInput:
    | boolean
    | number
    | string;

  @Prop() options: any[];
  @Prop({ type: Boolean, default: true }) allButton: boolean;
  @Prop({ type: Boolean }) emitValue: boolean;
  @Prop({ type: Boolean, default: true }) emitArray: boolean;
  @Prop() value: any[];

  protected filter = '';
  protected filteredOptions: any[] = [];

  @Watch('options')
  protected optionsUpdated() {
    this.getOptionsFilter();
  }

  @Watch('filter')
  protected getOptionsFilter(): void {
    const select = this.$refs.select as any;
    select.__resetVirtualScroll();
    let { optionLabel, filter, showInput, options } = this;

    filter = filter.trim().toLowerCase();
    if (!showInput || !filter || !Array.isArray(this.options) || !this.filteredOptions.length) {
      this.filteredOptions = this.options;
      return;
    }

    this.filteredOptions = options.filter(({ separator, [optionLabel]: label }, i) => {
      return !(!separator && (!label || !label.toLowerCase().includes(filter)));
    });

    // Заглушка для 0 результатов фильтра
    if (!this.filteredOptions.length) {
      this.filteredOptions.push('');
    }
  }

  protected clearFilterInput() {
    this.$nextTick(() => {
      const select = this.$refs.select as any;
      if (!select.menu) {
        this.filter = '';
        this.filteredOptions = this.options;
      }
    })
  }

  protected setFocus() {
    this.$nextTick(() => (this.$refs.input as QSelect)?.focus());
  }

  protected get listeners() {
    return {
      ...this.$listeners,
      input: this.input,
    };
  }

  protected inValue(value: SelectBase['value']) {
    return !this.multiple && this.emitArray && Array.isArray(value)
      ? value[0]
      : value;
  }

  protected outValue(innerValue: SelectBase['innerValue']) {
    return !this.multiple && this.emitArray && !Array.isArray(innerValue)
      ? isEmpty(innerValue)
        ? []
        : [innerValue]
      : innerValue;
  }

  protected requiredRule(v: any) {
    return (
      (!!v && (!Array.isArray(v) || !!v.length)) ||
      this.$t('base.validation.required.default')
    );
  }

  @Watch('value', { deep: true, immediate: true })
  protected valueChanged(value: SelectBase['value']) {
    this.innerValue = this.inValue(value);
    this.allSelected = this.gallSelected();
    this.$emit( 'isAllSelected', this.allSelected);
  }

  protected innerValue!: SelectBase['value'];

  protected input(value: SelectBase['innerValue']) {
    this.innerValue = value;
    this.$emit('input', this.outValue(value));
    this.allSelected = this.gallSelected();
    this.$nextTick(() => (this.$refs.input as QSelect)?.focus());
  }

  protected allSelected: boolean | null = false;

  protected gallSelected() {
    if (!this.innerValue || !this.innerValue.length) {
      return false;
    }
    if (
      this.innerValue.length ===
      this.options.filter(({ separator }) => !separator).length
    ) {
      return true;
    }

    return null;
  }

  protected iallSelected(value: boolean) {
    if (!value) {
      this.$emit('input', (this.innerValue = []));
    } else {
      let filtered = this.options.filter(({ separator }) => !separator);

      if (this.emitValue) {
        filtered = filtered.map((e) => e[this.optionValue]);
      }
      this.$emit('input', (this.innerValue = filtered));
    }
    this.allSelected = this.gallSelected();
  }

  @Watch('required', { immediate: true })
  protected requiredChanged() {
    this.$nextTick(() => (this.$refs.select as QSelect)?.resetValidation());
  }

  protected get irules() {
    if (!this.rules && !this.required) {
      return [];
    }
    const rules = this.rules?.slice() || [];

    if (this.required) {
      if (isString(this.required)) {
        rules.push(
          (v: any) => !isString(this.requiredRule(v)) || this.required
        );
      } else {
        rules.push(this.requiredRule);
      }
    }

    return rules;
  }

  protected get showInput(): SelectBase['useInput'] {
    const { useInput, options } = this;

    if (typeof useInput === 'boolean') {
      return useInput;
    }
    if (typeof useInput === 'number' || /^\d+$/.test(useInput)) {
      return options.length >= useInput;
    }

    return !!useInput;
  }
}
</script>

<style lang="sass" scoped>
.select-base

  font-size: 12px

  ::v-deep.q-field__native
    > span
      white-space: nowrap
      text-overflow: ellipsis
      overflow: hidden
      max-width: 100%
    min-height: 32px
  ::v-deep.text-negative 
    color: #ec942c !important
  ::v-deep.q-field__bottom 
      color: #ec942c !important
</style>
