import { Data } from '../../../types/data';
import { FilterValue, FilterValues } from '../../Filters';
import { SortData } from '../../List';
import { ExplorerDataProvider } from '../Explorer.model';

type FilterFunction<T extends Data> = (
  value: unknown,
  filter: FilterValue,
  item: T,
) => boolean;

interface LocalDataProviderConfig<T extends Data> {
  caseSensitiveSort?: boolean;
  filterFunctions?: {
    [property: string]: FilterFunction<T>;
  };
}

export function createInMemoryDataProvider<T extends Data>(
  data: T[],
  {
    caseSensitiveSort = false,
    filterFunctions = {},
  }: LocalDataProviderConfig<T> = {},
): ExplorerDataProvider<T> {
  return {
    loadData: ({ sorting, filters }) => {
      let result: T[] = [...data];

      if (filters) {
        result = applyFilter<T>(result, filters, filterFunctions);
      }

      if (sorting) {
        result = applySort(result, sorting, caseSensitiveSort);
      }

      return Promise.resolve({
        totalCount: data.length,
        filteredCount: result.length,
        data: result,
        hasMoreData: false,
      });
    },
  };
}

/**
 * Checks if the the `String(value)` exactly matches `String(filter)`.
 */
export const findExact: FilterFunction<Data> = (value, filter) => {
  return String(value) === String(filter);
};

/**
 * Checks if the filter string can be found somewhere in the value string. (case sensitive)
 */
export const findAnywhereInString: FilterFunction<Data> = (value, filter) => {
  return String(value).indexOf(String(filter)) >= 0;
};

/**
 * Checks if the filter string can be found somewhere in the value string. (case insensitive)
 */
export const findAnywhereInStringCaseInsensitive: FilterFunction<Data> = (
  value,
  filter,
) => {
  return String(value).toLowerCase().indexOf(String(filter).toLowerCase()) >= 0;
};

function applyFilter<T extends Data>(
  data: T[],
  filters: FilterValues<T>,
  filterFunctions: {
    [property: string]: FilterFunction<T>;
  },
): T[] {
  return data.filter((i) => {
    return Object.keys(filters).reduce<boolean>((prev, property) => {
      if (!prev) {
        return false;
      }

      // Use any given filter function or `findExact` if as fallback.
      const filterFn = filterFunctions[property] ?? findExact;
      return filterFn(i[property], filters[property], i);
    }, true);
  });
}

function applySort<T extends Data>(
  data: T[],
  sorting: SortData<T>,
  caseSensitiveSort: boolean,
): T[] {
  data = data.sort((a, b) => {
    let aValue = a[sorting.column];
    let bValue = b[sorting.column];
    if (!caseSensitiveSort && typeof aValue === 'string') {
      aValue = aValue.toLowerCase();
      bValue = bValue.toLowerCase();
    }
    if (aValue > bValue) {
      return 1;
    } else {
      return -1;
    }
  });
  if (sorting.direction === 'desc') {
    data = data.reverse();
  }

  return data;
}
