/* eslint-disable prefer-rest-params */
/* eslint-disable prefer-spread */

// This memoize function is heavily inspired by reselect's defaultMemoize
// https://github.com/reduxjs/reselect

// Cache implementation based on Erik Rasmussen's `lru-memoize`:
// https://github.com/erikras/lru-memoize

const NOT_FOUND = 'NOT_FOUND';
function createSingletonCache(equals) {
  let entry;
  return {
    get(key) {
      if (entry && equals(entry.key, key)) {
        return entry.value;
      }
      return NOT_FOUND;
    },
    put(key, value) {
      entry = {
        key,
        value
      };
    },
    getEntries() {
      return entry ? [entry] : [];
    },
    clear() {
      entry = undefined;
    }
  };
}
function createLruCache(maxSize, equals) {
  let entries = [];
  function get(key) {
    const cacheIndex = entries.findIndex(entry => equals(key, entry.key));

    // We found a cached entry
    if (cacheIndex > -1) {
      const entry = entries[cacheIndex];

      // Cached entry not at top of cache, move it to the top
      if (cacheIndex > 0) {
        entries.splice(cacheIndex, 1);
        entries.unshift(entry);
      }
      return entry.value;
    }

    // No entry found in cache, return sentinel
    return NOT_FOUND;
  }
  function put(key, value) {
    if (get(key) === NOT_FOUND) {
      // eslint-disable-next-line no-warning-comments
      // TODO Is unshift slow?
      entries.unshift({
        key,
        value
      });
      if (entries.length > maxSize) {
        entries.pop();
      }
    }
  }
  function getEntries() {
    return entries;
  }
  function clear() {
    entries = [];
  }
  return {
    get,
    put,
    getEntries,
    clear
  };
}
const defaultEqualityCheck = (first, second) => {
  return first === second;
};
function createCacheKeyComparator(equalityCheck) {
  return function areArgumentsShallowlyEqual(prev, next) {
    if (prev === null || next === null || prev.length !== next.length) {
      return false;
    }

    // Do this in a for loop (and not a `forEach` or an `every`) so we can determine equality as fast as possible.
    const length = prev.length;
    for (let i = 0; i < length; i++) {
      if (!equalityCheck(prev[i], next[i])) {
        return false;
      }
    }
    return true;
  };
}
// defaultMemoize now supports a configurable cache size with LRU behavior,
// and optional comparison of the result value with existing values
function memoize(func, equalityCheckOrOptions) {
  const providedOptions = typeof equalityCheckOrOptions === 'object' ? equalityCheckOrOptions : {
    equalityCheck: equalityCheckOrOptions
  };
  const {
    equalityCheck = defaultEqualityCheck,
    maxSize = 1,
    resultEqualityCheck
  } = providedOptions;
  const comparator = createCacheKeyComparator(equalityCheck);
  const cache = maxSize === 1 ? createSingletonCache(comparator) : createLruCache(maxSize, comparator);

  // we reference arguments instead of spreading them for performance reasons
  function memoized() {
    let value = cache.get(arguments);
    if (value === NOT_FOUND) {
      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      // @ts-ignore
      value = func.apply(null, arguments);
      if (resultEqualityCheck) {
        const entries = cache.getEntries();
        const matchingEntry = entries.find(entry => resultEqualityCheck(entry.value, value));
        if (matchingEntry) {
          value = matchingEntry.value;
        }
      }
      cache.put(arguments, value);
    }
    return value;
  }
  memoized.clearCache = () => cache.clear();
  return memoized;
}

export { createCacheKeyComparator, defaultEqualityCheck, memoize };
