<template>
  <div class="rt-filters">
    <base-label-value class="base-label q-gutter-x-md">
      <template #label>Фильтры</template>
    </base-label-value>
    <q-card v-if="!loading" bordered>
      <q-table
        selection="single"
        :hide-bottom="true"
        :hide-selected-banner="true"
        :data="filters"
        :selected.sync="selected"
        :columns="columns"
        :visible-columns="visibleColumns"
        :rows-per-page-options="[0]"
        row-key="filterId"
      >
        <template v-slot:body="props">
          <q-tr :key="props.rowIndex" :props="props">
            <td></td>
            <q-td key="globalOperator" :props="props">
              <div v-if="!props.selected">{{ props.row.globalOperator }}</div>
              <q-select
                v-else
                :props="props"
                class="q-mr-sm"
                v-model="props.row.globalOperator"
                :options="globalOperatorOptions"
                :label="$t('criteriaDirectory.globalOperator')"
              />
            </q-td>
            <q-td key="guidFilter" :props="props">
              <div v-if="!props.selected">
                {{ getFilterTypeLabel(props.row.guidFilter) }}
              </div>
              <q-select
                v-else
                class="q-mr-sm"
                :label="$t('criteriaDirectory.descriptionFilters')"
                v-model="selectedFilterType"
                :options="filterTypeOptions"
                @input="handleFilterType"
              />
            </q-td>
            <q-td key="operator" :props="props">
              <div v-if="!props.selected">
                {{ getFilterOptionLabel(props.row.operator) }}
              </div>
              <q-select
                v-else
                class="q-mr-sm"
                :label="$t('criteriaDirectory.operatorFilters')"
                v-model="selectedOperator"
                :options="operatorOptions"
              />
            </q-td>
            <q-td key="value" :props="props">
              <div v-if="!props.selected">
                {{ getFilterValueLabel(props.row.value) }}
              </div>
              <div v-else>
                <q-input
                  v-if="isValueTypeA('STRING')"
                  v-model="selectedParamString"
                  :label="$t('criteriaDirectory.description')"
                />
                <q-select
                  v-if="isValueTypeA('BOOLEAN', 'LIST', 'DIRECTORY')"
                  class="q-mr-sm"
                  :label="$t('criteriaDirectory.valueFilters')"
                  v-model="selectedParam"
                  :options="searchParamOptions"
                />
                <arm-datepicker
                  v-if="isValueTypeA('DATE')"
                  format="YYYY-MM-DD"
                  type="date"
                  :label="$t('criteriaDirectory.valueFilters')"
                  v-model="selectedParamString"
                />
              </div>
            </q-td>
            <q-td key="actions" :props="props">
              <div v-if="!props.selected">
                <q-btn
                  v-if="!readOnly"
                  flat
                  dense
                  size="16"
                  icon="edit"
                  color="white"
                  text-color="primary"
                  :disabled="!!selected.length"
                  @click="edit(props)"
                />
                <q-btn
                  v-if="!readOnly"
                  flat
                  dense
                  size="16"
                  icon="delete"
                  color="white"
                  text-color="negative"
                  class="q-ml-sm"
                  :disabled="!!selected.length"
                  @click="deleteFilter(props.rowIndex)"
                />
              </div>
              <div v-else>
                <q-btn
                  flat
                  dense
                  size="16"
                  icon="save"
                  color="white"
                  text-color="primary"
                  :disabled="!filterTypeIsSelected"
                  @click="save(props)"
                />
                <q-btn
                  flat
                  dense
                  size="16"
                  icon="delete"
                  color="white"
                  text-color="negative"
                  class="q-ml-sm"
                  @click="deleteFilter(props.rowIndex)"
                />
              </div>
            </q-td>
          </q-tr>
        </template>
        <template v-if="!readOnly" v-slot:bottom-row>
          <q-tr>
            <q-td colspan="6" class="text-right">
              <q-btn
                flat
                dense
                icon="add"
                size="16"
                :disabled="!!selected.length && selected[0].guidFilter === ''"
                @click="add()"
              />
            </q-td>
          </q-tr>
        </template>
      </q-table>
    </q-card>
    <q-inner-loading class="relative-position-important" :showing="loading">
      <q-spinner-gears color="primary" size="50px" />
    </q-inner-loading>
  </div>
</template>

<script lang="ts">
import { Component, Vue, Prop, Watch, PropSync } from 'vue-property-decorator';
import {
  FilterType,
  CriteriaApi,
  CriteriaFilter,
  CriteriaFilterGlobalOperatorEnum,
  CriteriaFilterOperatorEnum,
  RecipientCategory,
  SocialSupportMeasure,
} from '@/openapi/personal-accounts';
import ArmDatepicker from '@components/common/node-generator/components-lib/datepicker/arm-datepicker.vue';
import { notifyError, notifySuccess } from '@/utils/notify';
import { TableColumn } from '@/interfaces';
import { cloneDeep, get } from 'lodash';
import axios from 'axios';

const criteriaApi = new CriteriaApi();

interface Option {
  label: string;
  value: string;
}

interface FilterTypeOption {
  label: FilterType['description'];
  value: FilterType['id'];
  data: FilterType;
}

interface TableRow<T> {
  selected: boolean;
  row: T;
}

@Component({
  components: {
    ArmDatepicker,
  },
})
export default class CriteriaFilters extends Vue {
  @PropSync('value', { default: () => [] }) filters: CriteriaFilter[];
  @Prop({ default: false }) readOnly: boolean;

  get columns(): TableColumn<FilterType>[] {
    return [
      {
        label: this.$t('criteriaDirectory.globalOperator'),
        name: 'globalOperator',
        align: 'left',
      },
      {
        label: this.$t('criteriaDirectory.descriptionFilters'),
        name: 'guidFilter',
      },
      {
        label: this.$t('criteriaDirectory.operatorFilters'),
        name: 'operator',
      },
      {
        label: this.$t('criteriaDirectory.valueFilters'),
        name: 'value',
      },
      {
        label: 'Действия',
        name: 'actions',
        align: 'right',
      },
    ];
  }

  get visibleColumns(): string[] {
    if (this.readOnly) {
      return ['globalOperator', 'guidFilter', 'operator', 'value'];
    } else {
      return ['globalOperator', 'guidFilter', 'operator', 'value', 'actions'];
    }
  }

  get operatorLabels(): Record<string, string> {
    return {
      EQUAL: '==',
      NOT_EQUAL: '!=',
      GREATER_THAN: '>',
      GREATER_THAN_OR_EQUAL: '>=',
      LESS_THAN: '<',
      LESS_THAN_OR_EQUAL: '<=',
      LIKE: 'Like',
      NOT_LIKE: 'NotLike',
    };
  }

  selected: CriteriaFilter[] = [];

  globalOperatorOptions: CriteriaFilterGlobalOperatorEnum[] = Object.values(
    CriteriaFilterGlobalOperatorEnum
  );

  selectedParam: Option | null = null;
  selectedParamString: string | null = '';
  searchParamOptions: Option[] = [];
  loading = false;

  selectedFilterType: FilterTypeOption | null = null;
  filterTypeOptions: FilterTypeOption[] = [];
  filterValueLabels: Record<string, string> | null = null;

  selectedOperator: Option | null = null;
  operatorOptions: Option[] = [];

  async handleFilterType(filterType: FilterTypeOption): Promise<void> {
    if (!this.selected[0]) return;
    this.selected[0].guidFilter = filterType?.data.id || '';
    this.operatorOptions = this.getOperatorOptions(filterType);
    this.selectedOperator = this.operatorOptions[0];
    this.searchParamOptions = await this.getSearchParams(filterType);
    if (this.searchParamOptions.length) {
      this.selectedParam = this.searchParamOptions[0];
    } else {
      this.selectedParamString = '';
    }
  }

  getOperatorOptions(filterType: FilterTypeOption): Option[] {
    return (
      filterType?.data.operator?.flatMap((op) => {
        return Object.entries(op).map(([value, label]) => {
          return {
            label,
            value,
          };
        });
      }) || []
    );
  }

  async getSearchParams(filterType: FilterTypeOption): Promise<Option[]> {
    if (filterType.data.type === 'BOOLEAN') {
      return [
        {
          label: 'Да',
          value: 'true',
        },
        {
          label: 'Нет',
          value: 'false',
        },
      ];
    }

    const options = (
      await Promise.all(
        filterType?.data.searchParams?.map(async (param) => {
          const [key, value] = Object.entries(param)[0];

          return [
            {
              label: value,
              value: key,
            },
          ];
        }) || []
      )
    ).flat();
    return options;
  }

  async fetchDict(url: string): Promise<Option[]> {
    const { data } = await axios.get<
      Array<RecipientCategory | SocialSupportMeasure>
    >(url);
    const records = data.map(({ id, code, name }) => {
      return {
        label: `${code} ${name}`,
        value: id || '',
      };
    });
    return records;
  }

  save(filter: TableRow<CriteriaFilter>): void {
    filter.row.guidFilter = this.selectedFilterType?.data.id || '';
    filter.row.operator = this.selectedOperator
      ?.value as CriteriaFilterOperatorEnum;

    if (this.isValueTypeA('BOOLEAN', 'LIST', 'DIRECTORY')) {
      filter.row.value = this.selectedParam?.value || '';
    } else {
      filter.row.value = this.selectedParamString || '';
    }
    filter.selected = false;
    this.$emit('changeFilter', this.filters);
  }

  async edit(filter: TableRow<CriteriaFilter>): Promise<void> {
    this.selectedFilterType =
      this.filterTypeOptions.find(({ data }) => {
        return data.id === filter.row.guidFilter;
      }) || null;

    if (this.selectedFilterType) {
      this.operatorOptions = this.getOperatorOptions(this.selectedFilterType);
      this.selectedOperator =
        this.operatorOptions.find(({ value }) => {
          return value === filter.row.operator;
        }) || null;

      this.searchParamOptions = await this.getSearchParams(
        this.selectedFilterType
      );
      if (this.isValueTypeA('BOOLEAN', 'LIST', 'DIRECTORY')) {
        this.selectedParam =
          this.searchParamOptions.find(({ value }) => {
            return filter.row.value === value;
          }) || null;
      } else {
        this.selectedParamString = filter.row.value;
      }
    }
    filter.selected = true;
  }

  add(): void {
    const filters = this.filters || [];
    const newFilter = cloneDeep({
      guidFilter: '',
      value: '',
      operator: CriteriaFilterOperatorEnum.Equal,
      globalOperator: CriteriaFilterGlobalOperatorEnum.And,
      filterId: 1,
    });
    this.selectedFilterType = {
      data: {
        schema: '',
      },
      value: '',
      label: '',
    };
    filters.push(newFilter);
    this.filters = filters;
    this.selected = [newFilter];
    this.selected[0].filterId = this.filters.length;
    this.operatorOptions = [];
    this.selectedOperator = null;
    this.selectedParam = null;
    this.selectedParamString = '';
  }

  deleteFilter(index: number): void {
    if (this.filters == null) return;
    if (index !== -1) {
      this.filters.splice(index, 1);
      this.filters = this.filters.map((e, index) => ({
        ...e,
        filterId: index + 1,
      }));
      this.selected = [];
    }
    this.$emit('changeFilter', this.filters);
  }

  getFilterTypeLabel(value: string): string {
    const option = this.filterTypeOptions.find((e) => e.value === value);

    return option?.label || '';
  }

  getFilterValueLabel(value: string): string {
    return get(this.filterValueLabels, value, value);
  }

  getFilterOptionLabel(option: string): string {
    return get(this.operatorLabels, option, '');
  }

  isValueTypeA(...types: string[]): boolean {
    if (!this.selectedFilterType?.data.type) return false;
    return types.includes(this.selectedFilterType?.data.type);
  }

  get filterTypeIsSelected(): boolean {
    if (
      !!this.selectedFilterType?.value &&
      !this.isValueTypeA('BOOLEAN', 'LIST', 'DIRECTORY')
    ) {
      return !!this.selectedParamString;
    } else {
      return !!this.selectedFilterType?.value;
    }
  }

  async getFilterTypeOptions(): Promise<void> {
    this.loading = true;
    try {
      const { data } = await criteriaApi.listFilterTypes();

      const records = await Promise.all(
        data.map(async (e) => {
          if (e.type === 'DIRECTORY' && e.searchParams) {
            const fetch = await this.fetchDict(e.searchParams[0].GET);
            const list = fetch.map((i) => ({ [i.value]: i.label }));

            return { ...e, searchParams: list };
          }
          return e;
        })
      );
      this.filterTypeOptions = records.map((filterType) => {
        return {
          value: filterType.id,
          label: filterType.description,
          data: filterType,
        };
      });

      this.filterValueLabels = records.reduce((lables, { searchParams }) => {
        if (searchParams) {
          return {
            ...lables,
            ...searchParams.reduce((mem, param) => {
              return {
                ...mem,
                ...param,
              };
            }, {} as Record<string, string>),
          };
        }
        return lables;
      }, {} as Record<string, string>);
      this.filterValueLabels = {
        ...this.filterValueLabels,
        true: 'Да',
        false: 'Нет',
      };
      this.loading = false;
    } catch (error) {
      if (error instanceof Error) notifyError(error.message);
      this.filterTypeOptions = [];
      this.loading = false;
    }
  }

  created(): void {
    this.getFilterTypeOptions();
  }
}
</script>

<style>
.relative-position-important {
  position: relative !important;
}
</style>
