import { GroupedOption, BaseOption } from './types';
import { fromNullable, map, getOrElse } from '../../fp/Option';
import { pipe } from '../../fp/function';

const checkAtBottom = (element: HTMLDivElement | null): boolean | undefined =>
  pipe(
    fromNullable(element),
    map(el => el.scrollHeight - el.scrollTop === el.clientHeight),
    getOrElse(() => undefined)
  );

const getItemOffsetTop = (itemEl: HTMLLIElement | null): number | undefined =>
  pipe(
    fromNullable(itemEl),
    map(el => el.offsetTop),
    getOrElse(() => undefined)
  );

const isGroupedOptions = <T extends BaseOption>(
  options: T[] | GroupedOption<T>[]
): options is GroupedOption<T>[] => {
  const firstOption = options[0];
  return (
    firstOption !== undefined &&
    (firstOption as GroupedOption<T>).category !== undefined
  );
};

const mapOptions = <T extends BaseOption>(
  options: T[] | GroupedOption<T>[]
): GroupedOption<T>[] => {
  if (isGroupedOptions(options)) {
    return options;
  }

  return [{ category: '', options }];
};

const optionPredicate = <T extends BaseOption>(query?: string) => (
  option: T
): boolean => {
  if (query === undefined) {
    return true;
  }

  const matchedOptionText = option.text
    .toLowerCase()
    .includes(query.toLowerCase());

  const matchedOptionHelpText =
    option.helpText?.toLowerCase().includes(query.toLowerCase()) ?? false;

  return matchedOptionText || matchedOptionHelpText;
};

const filterGroupedOptions = <T extends BaseOption>(
  predicate: (opt: T) => boolean,
  groupedOptions: GroupedOption<T>[]
): GroupedOption<T>[] =>
  groupedOptions
    .map((groupedOpt: GroupedOption<T>) => ({
      ...groupedOpt,
      options: groupedOpt.options.filter(predicate),
    }))
    .filter((groupedOpt: GroupedOption<T>) => groupedOpt.options.length > 0);

const getAccumulatedIndex = <T extends BaseOption>(
  options: GroupedOption<T>[],
  catIndex: number
): number => {
  if (catIndex === 0) {
    return 0;
  }

  return options
    .slice(0, catIndex)
    .reduce((acc, groupedOpt) => acc + groupedOpt.options.length, 0);
};

export {
  isGroupedOptions,
  checkAtBottom,
  getItemOffsetTop,
  mapOptions,
  filterGroupedOptions,
  optionPredicate,
  getAccumulatedIndex,
};
