import dayjs from 'dayjs';
import { isArray, isDate, isNull, negate } from 'lodash';
import { FilterMetadata } from 'primeng/api';
import { DataView } from 'primeng/dataview';
import { Table, TableLazyLoadEvent } from 'primeng/table';
import {
  FilterOperator,
  FilterSuffix,
  PaginationQueryString,
} from './query-strings';

const convertValue = (value: any): string => {
  if (isDate(value)) {
    return dayjs(value).format('YYYY-MM-DD');
  }
  return value;
};

const convertOperators = (
  matchMode: string | undefined,
): [FilterOperator, FilterSuffix?] => {
  switch (matchMode) {
    case 'startsWith':
      return ['$sw'];
    case 'between':
      return ['$btw'];
    case 'contains':
      return ['$ilike'];
    case 'notContains':
      return ['$ilike', '$not'];
    case 'equals':
    case 'is':
    case 'dateIs':
      return ['$eq'];
    case 'notEquals':
    case 'isNot':
      return ['$eq', '$not'];
    case 'in':
      return ['$in'];
    case 'lt':
    case 'before':
    case 'dateBefore':
      return ['$lt'];
    case 'lte':
      return ['$lte'];
    case 'gt':
    case 'after':
    case 'dateAfter':
      return ['$gt'];
    case 'gte':
      return ['$gte'];
    case 'endsWith':
      throw new Error(`PrimeNG filter 'endsWith' is not supported`);
    default:
      return ['$ilike'];
  }
};

export const convertLazyLoadEvent = (
  event: TableLazyLoadEvent,
): ReturnType<typeof PaginationQueryString.builder> => {
  const query = PaginationQueryString.builder();

  if (event.first != undefined) {
    const { first, rows } = event;
    const limit = rows ?? 20;
    const page = first / limit + 1;
    query.setPagination({ limit, page });
  }
  if (event.sortField != undefined) {
    const { sortField, sortOrder } = event;
    const order = (sortOrder ?? 1) > 0 ? 'ASC' : 'DESC';
    if (isArray(sortField)) {
      for (const sortFieldEntry of sortField) {
        query.addOrder(sortFieldEntry, order);
      }
    } else {
      query.addOrder(sortField, order);
    }
  } else if (event.multiSortMeta != undefined) {
    for (const multiSortMetaEntry of event.multiSortMeta) {
      const { field: sortField, order: sortOrder } = multiSortMetaEntry;
      const order = (sortOrder ?? 1) > 0 ? 'ASC' : 'DESC';
      query.addOrder(sortField, order);
    }
  }
  for (const [filterKey, filterValue] of Object.entries(event.filters ?? {})) {
    if (filterValue == undefined) continue;
    const metadatas: FilterMetadata[] = isArray(filterValue)
      ? [...filterValue]
      : [filterValue];
    for (const metadata of metadatas.filter((x) => {
      if (x.value === null || x.value === undefined) return false;
      if (Array.isArray(x.value)) return x.value.length > 0;
      return true;
    })) {
      const value = convertValue(metadata.value);
      const operators = convertOperators(metadata.matchMode);

      const values = isArray(value) ? value : [value];
      if (values.some((value) => value === null) && operators[0] === '$in') {
        query.addFilter(filterKey, ['$null'], []);
        const nonNullValues = values.filter(negate(isNull));
        if (nonNullValues.length > 0) {
          query.addFilter(filterKey, ['$in'], nonNullValues, '$or');
        }
      } else {
        query.addFilter(filterKey, operators, value);
      }
    }
  }
  if (
    event.globalFilter != undefined &&
    typeof event.globalFilter === 'string'
  ) {
    query.setSearch(event.globalFilter);
  }

  return query;
};

export const getInitialTableLazyLoadEvent = (
  table: Table,
): TableLazyLoadEvent => ({
  first: 0,
  rows: table.rows ?? undefined,
  sortOrder: table.sortOrder,
  sortField: table.sortField ?? undefined,
  multiSortMeta: table.multiSortMeta ?? undefined,
});

export const getInitialDataViewLazyLoadEvent = (dataView: DataView) => ({
  first: 0,
  rows: dataView.rows,
  sortOrder: dataView.sortOrder,
  sortField: dataView.sortField,
});
