import type { AnyFn, Dictionary, MicroMemoize } from './types';

/**
 * @constant DEFAULT_OPTIONS_KEYS the default options keys
 */
const DEFAULT_OPTIONS_KEYS: Dictionary<true> = {
  isEqual: true,
  isMatchingKey: true,
  isPromise: true,
  maxSize: true,
  onCacheAdd: true,
  onCacheChange: true,
  onCacheHit: true,
  transformKey: true,
};

/**
 * @function slice
 *
 * @description
 * slice.call() pre-bound
 */
export const { slice } = Array.prototype;

/**
 * @function cloneArray
 *
 * @description
 * clone the array-like object and return the new array
 *
 * @param arrayLike the array-like object to clone
 * @returns the clone as an array
 */
export function cloneArray(arrayLike: any[] | IArguments) {
  const { length } = arrayLike;

  if (!length) {
    return [];
  }

  if (length === 1) {
    return [arrayLike[0]];
  }

  if (length === 2) {
    return [arrayLike[0], arrayLike[1]];
  }

  if (length === 3) {
    return [arrayLike[0], arrayLike[1], arrayLike[2]];
  }

  return slice.call(arrayLike, 0);
}

/**
 * @function getCustomOptions
 *
 * @description
 * get the custom options on the object passed
 *
 * @param options the memoization options passed
 * @returns the custom options passed
 */
export function getCustomOptions<Fn extends AnyFn>(
  options: MicroMemoize.Options<Fn>,
) {
  const customOptions: MicroMemoize.Options<Fn> = {};

  /* eslint-disable no-restricted-syntax */

  for (const key in options) {
    if (!DEFAULT_OPTIONS_KEYS[key]) {
      customOptions[key] = options[key];
    }
  }

  /* eslint-enable */

  return customOptions;
}

/**
 * @function isMemoized
 *
 * @description
 * is the function passed already memoized
 *
 * @param fn the function to test
 * @returns is the function already memoized
 */
export function isMemoized(fn: any): fn is MicroMemoize.Memoized<AnyFn> {
  return (
    typeof fn === 'function' && (fn as MicroMemoize.Memoized<AnyFn>).isMemoized
  );
}

/**
 * @function isSameValueZero
 *
 * @description
 * are the objects equal based on SameValueZero equality
 *
 * @param object1 the first object to compare
 * @param object2 the second object to compare
 * @returns are the two objects equal
 */
export function isSameValueZero(object1: any, object2: any) {
  // eslint-disable-next-line no-self-compare
  return object1 === object2 || (object1 !== object1 && object2 !== object2);
}

/**
 * @function mergeOptions
 *
 * @description
 * merge the options into the target
 *
 * @param existingOptions the options provided
 * @param newOptions the options to include
 * @returns the merged options
 */
export function mergeOptions<Fn extends AnyFn>(
  existingOptions:
    | MicroMemoize.NormalizedOptions<AnyFn>
    | MicroMemoize.NormalizedOptions<Fn>,
  newOptions: MicroMemoize.Options<Fn>,
): Readonly<MicroMemoize.NormalizedOptions<Fn>> {
  const target = {} as MicroMemoize.NormalizedOptions<Fn>;

  /* eslint-disable no-restricted-syntax */

  for (const key in existingOptions) {
    target[key] = existingOptions[key];
  }

  for (const key in newOptions) {
    target[key] = newOptions[key];
  }

  /* eslint-enable */

  return target;
}
