import { Option, UrlQueryParams, UrlQueryValue } from '@/recruits/models';
import moment from 'moment/moment';

export enum FILTER_TYPE {
  string = 'string',
  date = 'date',
  option = 'option',
}

export type StringTypeFilter = {
  type: FILTER_TYPE.string;
  value: string;
};
export type DateTypeFilter = {
  type: FILTER_TYPE.date;
  value: Date | null;
};
export type OptionTypeFilter = {
  type: FILTER_TYPE.option;
  value: Array<Option>;
  options: Array<Option>;
};

type AnyTypeFilter = StringTypeFilter | DateTypeFilter | OptionTypeFilter;

/**
 * Глобальный класс поиска и фильтрации back-end данных, содержащий общую логику:
 * 1) Подсчет кол-ва указанных фильтров - countFilters();
 * 2) Подготовка данных для запроса - prepareRequestParams();
 * 3) Обновление данными из параметров маршрута - updateParamsFromRoute();
 *
 * Для использования необходимо:
 * 1) Наследоваться от данного класса.
 * 2) В поле _filter, нового класса, описать значения фильтров, их типы и опции.
 * 3) Описать get() и set() для каждого фильтра и его опций.
 *
 * @example
 * export class NewClass extends GlobalSearchFilter {
 *   _filter: {
 *     search: StringTypeFilter;
 *     event: OptionTypeFilter;
 *     dateFrom: DateTypeFilter;
 *   } = {
 *     search: {
 *       type: FILTER_TYPE.string,
 *       value: '',
 *     },
 *     event: {
 *       type: FILTER_TYPE.option,
 *       value: [],
 *       options: [
 *         {
 *           value: 'navigation',
 *           label: 'Навигация',
 *         },
 *         {
 *           value: 'action',
 *           label: 'Действие',
 *         },
 *       ],
 *     },
 *     dateFrom: {
 *       type: FILTER_TYPE.date,
 *       value: null,
 *     },
 *   };
 *
 *   get event(): OptionTypeFilter['value'] {
 *     return this._filter.event.value;
 *   }
 *
 *   set event(value: OptionTypeFilter['value']) {
 *     this._filter.event.value = value;
 *   }
 *
 *   get eventOptions(): OptionTypeFilter['value'] {
 *     return this._filter.event.options;
 *   }
 *
 *   get dateFrom(): DateTypeFilter['value'] {
 *     return this._filter.dateFrom.value;
 *   }
 *
 *   set dateFrom(value: DateTypeFilter['value']) {
 *     this._filter.dateFrom.value = value;
 *   }
 * }
 *
 */
export class GlobalSearchFilter {
  _filter: {
    search: StringTypeFilter;
    [key: string]: AnyTypeFilter;
  } = {
    search: {
      value: '',
      type: FILTER_TYPE.string,
    },
  };

  get search(): string {
    return this._filter.search.value || '';
  }

  set search(value: string) {
    this._filter.search.value = value;
  }

  public countFilters(): number {
    return Object.values(this._filter).reduce((count, filter) => {
      function addValue() {
        count = count + 1;
      }
      if (filter.value) {
        if (filter.type === FILTER_TYPE.option) {
          if (filter.value.length) addValue();
        } else addValue();
      }
      return count;
    }, 0);
  }

  public prepareRequestParams(): UrlQueryParams {
    function prepareString(string: string) {
      return string || undefined;
    }
    function prepareDate(date: Date | null) {
      const momentDate = moment(date);
      return (
        (momentDate.isValid() && momentDate.format('YYYY-MM-DD')) || undefined
      );
    }
    function prepareOptions(options: Option[]) {
      return options.length ? options.map((s) => s.value) : undefined;
    }

    function getPrepareValue(filter: AnyTypeFilter): UrlQueryValue {
      switch (filter.type) {
        case FILTER_TYPE.string:
          return prepareString(filter.value);
        case FILTER_TYPE.date:
          return prepareDate(filter.value);
        case FILTER_TYPE.option:
          return prepareOptions(filter.value);
      }
    }
    return Object.entries(this._filter).reduce(
      (params: UrlQueryParams, [key, filter]) => {
        params[key] = getPrepareValue(filter);
        return params;
      },
      {}
    );
  }

  public updateParamsFromRoute(params: { [key: string]: string }): void {
    function getDateValue(value: string) {
      const momentDate = moment(value);
      return momentDate.isValid() ? momentDate.toDate() : null;
    }

    function getOptionsValue(
      valueParams: string,
      filterOptions: Option[]
    ): Option[] {
      return valueParams
        .split(',')
        .reduce((selectedOptions: Option[], optionValueFromParams) => {
          const selectedOption = filterOptions.find(
            ({ value }) => value === optionValueFromParams
          );
          if (selectedOption) selectedOptions.push(selectedOption);
          return selectedOptions;
        }, []);
    }

    Object.keys(params).forEach((key) => {
      const valueParams = params[key] ? String(params[key]) : '';
      const filter = this._filter[key];

      if (valueParams && filter)
        switch (filter.type) {
          case FILTER_TYPE.string:
            this._filter[key].value = valueParams;
            break;
          case FILTER_TYPE.date:
            this._filter[key].value = getDateValue(valueParams);
            break;
          case FILTER_TYPE.option:
            this._filter[key].value = getOptionsValue(
              valueParams,
              filter.options
            );
            break;
        }
    });
  }
}
