/* eslint-disable unused-imports/no-unused-vars */
import * as R from "ramda";
import { invariant } from "@applicaster/zapp-react-native-utils/errorUtils";

/**
 * shifts an array by the given offset. a negative offset will take items
 * from the end of the array and add them at the beginning, while a positive offset
 * will take an item at the beginning and shift it to the end
 * shiftArray(-1, [1,2,3,4]) => [4, 1, 2, 3]
 * shiftArray(1, [1,2,3,4]) => [2, 3, 4, 1]
 * an offset of 0 leaves the array unchanged
 * this function returns a new array and doesn't mutate the original one
 * curried function which can be invoked like offset => array => result
 * @param {Number} offset offset to shift the array
 * @param {Array<T>} array array to shift
 * @returns {Array<T>}
 */
export const shiftArray = R.curry(
  <T extends unknown>(offset: number, array: T[]): T[] => {
    return R.concat(
      R.slice(offset, R.length(array), array),
      R.slice(0, offset, array)
    );
  }
);

/**
 * Tries to remove an item from an array, and returns the array if the item is not found
 * @param {any} item to remove
 * @param {Array<any>} list to remove the item from
 * @returns {Array<any>}
 */
export function removeItemFromList<T extends unknown>(item: T, list: T[]): T[] {
  return R.compose(
    R.ifElse(R.equals(-1), R.always(list), R.remove(R.__, 1, list)),
    R.indexOf(item)
  )(list);
}

export const mapPromises = R.curry(
  <T extends unknown, S extends unknown>(
    fn: (value: T, index?: number) => Promise<S>,
    values: T[]
  ): Promise<S[]> => Promise.all(R.addIndex(R.map)(R.nAry(2, fn), values))
);

const waitForPromiseAndRun = R.curryN(
  3,
  async <T extends unknown, S extends unknown>(
    fn: (currentValue: T, previousValue: S, index?: number) => Promise<S>,
    previousPromise: Promise<S>,
    currentValue: T,
    index: number
  ) => {
    const previousValue = await previousPromise;

    return fn(currentValue, previousValue, index);
  }
);

export const reducePromises = R.curry(
  <T extends unknown, S extends unknown>(
    fn: (current: T, previous: S, index?: number) => Promise<S>,
    initialValue: S,
    values: T[]
  ): Promise<S> =>
    R.addIndex(R.reduce)(
      waitForPromiseAndRun<T, S>(fn),
      Promise.resolve(initialValue),
      values
    )
);

export const mapAndSplit = R.curry(
  <T extends unknown, S extends unknown>(
    mapperFn: (T) => S,
    chunks: number
  ): S[][] => R.compose(R.splitEvery(chunks), R.map(mapperFn))
);

export const isLast = (index: number, length: number): boolean =>
  index >= length - 1;

export const isFirst = (index: number): boolean => index <= 0;

export const isIndexInRange = (index: number, length: number): boolean => {
  if (length <= 0) return false;
  if (index < 0) return false;

  return index + 1 <= length;
};

export const makeListOfIndexes = (size: number): number[] =>
  Array.from({ length: size }, (_, index) => index);

export const makeListOf = (value: unknown, size: number): number[] => {
  return Array(size).fill(value);
};

/** Checks if a value is a non-empty array */
export function isFilledArray(value: unknown): boolean {
  return R.is(Array, value) && R.length(value) > 0;
}

// get random item from the list
export const sample = (xs: unknown[]): unknown => {
  invariant(R.is(Array, xs), `input value is not a array: ${xs}`);
  invariant(isFilledArray(xs), `input array is empty: ${xs}`);

  const index = Math.floor(Math.random() * xs.length);

  return xs[index];
};
