import { Component, Prop, Watch } from 'vue-property-decorator';
import { mixins } from 'vue-class-component';
import {
  WidgetWrapper,
  FormWidget,
  SubscriberWidget,
  ValidatedField,
  ObjectMetaModel,
  ObjectMetaModelValue,
} from '@devsun/form-generator/dist';
import { QSelect } from 'quasar';
import { ReferenceBooksItemOption } from '@/components/common/reference-book-item';
import isEqual from 'lodash/isEqual';
import has from 'lodash/has';

function concatUniq<T, E>(arr: T, val: E) {
  const array = Array.isArray(arr) ? arr.slice() : !arr ? [] : [String(arr)];
  if (!array.includes(val)) array.push(val);
  return array;
}

/**
 * Массив опций на данный момент состоит из объектов:
 * {
 *  label: string;
 *  value: string;
 * }
 *
 * Пропсы:
 * optionValue: string (default: 'value') - ключ массива опций,
 * который будет использован в качестве значения(отправлен в модель)
 *
 * optionLabel: string (default: 'label') - ключ массива опций,
 * который будет использован в заголовка
 *
 * valuesIn: string[] ([optionValue, ...valuesIn]) - массив дополнительных ключей массива опций,
 * по которым будет произведён поиск входных значений (при этом в модель будет отправлен optionValue)
 *
 * Пример 1:
 * valuesIn = ['label', 'value'];
 * options = [
 * {
 *  label: 'Заголовок 1';
 *  value: '1';
 * },
 * {
 *  label: 'Заголовок 2';
 *  value: '2';
 * }
 * ];
 * Входное значение:
 * 'Заголовок 1'
 * Выходное значение:
 * '1'
 *
 * Пример 2:
 * valuesIn = ['label', 'value'];
 * options = [
 * {
 *  label: 'Заголовок 1';
 *  value: '1';
 * },
 * {
 *  label: 'Заголовок 2';
 *  value: '2';
 * }
 * ];
 * Входное значение:
 * '1'
 * Выходное значение:
 * '1'
 *
 * Пример 3:
 * valuesIn = ['label', 'value'];
 * optionValue= 'label'
 * options = [
 * {
 *  label: 'Заголовок 1';
 *  value: '1';
 * },
 * {
 *  label: 'Заголовок 2';
 *  value: '2';
 * }
 * ];
 * Входное значение:
 * '1'
 * Выходное значение:
 * 'Заголовок 1'
 */

@Component({
  components: {
    QSelect,
    WidgetWrapper,
  },
})
export default class DictionarySelect extends mixins(
  SubscriberWidget,
  ValidatedField,
  FormWidget
) {
  @Prop({ required: true }) value!: ObjectMetaModelValue;
  @Prop({ type: Boolean }) readonlyAsText!: boolean;
  @Prop({ type: Boolean }) multiple!: boolean;
  @Prop() valuesIn!: string[];
  @Prop() allowedValues!: any[];
  @Prop() model!: ObjectMetaModel;
  @Prop() dictionary!: string;
  @Prop() valueName?: string;
  @Prop({ default: false }) isNested: boolean;

  $refs!: {
    select: QSelect;
    widget: WidgetWrapper;
  };

  protected get optionValue(): string {
    return (this.$props?.optionValue as string) ?? 'value';
  }

  protected get selectProps() {
    return this.fieldNode?.rawAttrs?.props || undefined;
  }

  protected options: ReferenceBooksItemOption[] = [];
  protected filteredOptions: ReferenceBooksItemOption[] = [];
  protected originalOptions!: ReferenceBooksItemOption[];

  protected dictionaryReady(options: ReferenceBooksItemOption[]) {
    this.options = this.hasAllowedOptionsList
      ? options.filter((el) => this.checkOption(el))
      : options;
    this.originalOptions = options;
    this.$refs.select?.refresh();
    this.setValueByOptionIfNotEqual();
  }

  protected selectedOption: string = '';
  protected selectedGroupIndex: number | null = null; // using only for "nested" case

  isSelectedOption(child: ReferenceBooksItemOption) {
    let isSelected = false;
    if (has(this.selected, 'code')) {
      if (
        this.selectedOption === child.label &&
        this.selected.code === child.value
      ) {
        isSelected = true;
      }
    }
    return isSelected;
  }

  get isClearModalOpen() {
    return this.$store.getters['formGenerator/isClearModalOpen'];
  }

  get disable(): boolean {
    return this.isClearModalOpen;
  }

  get isMultipleOptions() {
    return !!this.options.find((option: ReferenceBooksItemOption) =>
      has(option, 'children')
    );
  }

  get hasAllowedOptionsList(): boolean {
    return Array.isArray(this.allowedValues) && this.allowedValues.length > 0;
  }

  selectOption(child: ReferenceBooksItemOption, scope: any) {
    this.selectedGroupIndex = this.isMultipleOptions ? scope.index : null;
    this.selected = child.value;
    this.selectedOption = child.label;
  }

  hasOption(scope: any) {
    return scope.opt.children.some((c: any) => {
      return c.label === this.selectedOption;
    });
  }

  @Watch('isReadonly')
  protected setValueByOptionIfNotEqual() {
    if (!this.isReadonly && !isEqual(this.outValue, this.value?.__id))
      this.selected = this.outValue;
  }

  @Watch('allowedValues')
  onAllowedValuesChanged(): void {
    this.dictionaryReady(this.originalOptions);
  }

  get outValue() {
    if (this.selected === undefined) {
      return this.value?.__id;
    }
    return Array.isArray(this.selected)
      ? this.selected.map((e: any) => e[this.optionValue])
      : this.selected[this.optionValue];
  }

  get isReadonly() {
    return this.fieldNode.readonly;
  }

  get selected() {
    let selected = this.value?.__id || '';

    if (!Array.isArray(selected)) {
      selected = [selected];
    }
    const valuesIn = concatUniq(this.valuesIn, this.optionValue);
    const options =
      this.selectedGroupIndex !== null &&
      has(this.options, [this.selectedGroupIndex, 'children'])
        ? (this.options[this.selectedGroupIndex]
            .children as ReferenceBooksItemOption[])
        : this.options;
    selected = options.filter((option) =>
      valuesIn.some((key: keyof typeof option) => {
        if (key in option) {
          const index = selected.indexOf(option[key]);
          const indexByValueObj = selected.findIndex((selectedIt: any) => {
            return (
              isEqual(selectedIt, option[key]) || selectedIt == option[key]
            );
          });

          if (index !== -1 || indexByValueObj !== -1) {
            selected.splice(index === -1 ? indexByValueObj : index, 1);
            return true;
          }
        }
      })
    );
    return this.multiple ? selected.slice() : selected[0];
  }

  set selected(selected: any) {
    if (!Array.isArray(selected)) selected = [selected];
    this.$refs.widget?.inputEvents?.input(
      this.multiple ? selected.slice() : this.getStringValue(selected[0])
    );
  }

  getStringValue(valueObj: { code: string; valueName: string } | string) {
    if (typeof valueObj === 'string') return valueObj;

    if (valueObj && valueObj.valueName === 'value') return valueObj.code;

    return valueObj;
  }

  checkOption(el: ReferenceBooksItemOption): boolean {
    if (el.children) {
      // supports only 1 nested level
      return el.children.reduce(
        (acc: boolean, curr: ReferenceBooksItemOption) => {
          return acc ? acc : this.allowedValues.includes(curr.value);
        },
        false
      );
    } else {
      return this.allowedValues.includes(el.value);
    }
  }

  filterItems(query: string, done: Function) {
    if (query === '') {
      done(() => {
        this.filteredOptions = this.options;
      });
      return;
    }

    done(() => {
      const needle = query.toLowerCase();
      this.filteredOptions = this.options.filter(
        (v) => v.label.toLowerCase().indexOf(needle) > -1
      );
    });
  }

  onPopupShow() {
    this.$store.commit('formGenerator/setClearModal', true);
  }
}
