import fastMemoize from 'fast-memoize'

import { MEMOIZE_CACHE_CLEAR_THRESHOLD } from '../constants.js'

// This is basically the same as fast-memoize's default cache, except that it
// clears the cache once the size exceeds MEMOIZE_CACHE_CLEAR_THRESHOLD to
// prevent memory bloat.
// https://github.com/caiogondim/fast-memoize.js/blob/5cdfc8dde23d86b16e0104bae1b04cd447b98c63/src/index.js#L114-L128
export class MemoizeCache<TValue = unknown> {
  cache: Record<string, TValue> = {}
  cacheSize: number

  /**
   * @param config Can also contain the config options used to
configure fast-memoize.
   * @see ://github.com/caiogondim/fast-memoize.js
   */
  constructor({ cacheSize = MEMOIZE_CACHE_CLEAR_THRESHOLD } = {}) {
    this.cacheSize = cacheSize
  }

  has(key: string) {
    return key in this.cache
  }

  get(key: string): TValue {
    return this.cache[key]
  }

  set(key: string, value: TValue) {
    if (Object.keys(this.cache).length > this.cacheSize) {
      this.cache = {}
    }

    this.cache[key] = value
  }
}

/**
 * @template Copied from https://github.com/caiogondim/fast-memoize.js/blob/5cdfc8dde23d86b16e0104bae1b04cd447b98c63/typings/fast-memoize.d.ts#L1
 * @returns T
 * @see ://github.com/caiogondim/fast-memoize.js
 */
// `never[]` (rather than `any[]`) as the rest-parameter type is intentional:
// `never` is assignable to any parameter type, so this constrains T to "any
// function type" without introducing `any`, while inference still preserves
// fn's real parameter list — calling the memoized function with the wrong
// argument types still errors as expected.
export const memoize = <T extends (...args: never[]) => unknown>(
  fn: T,
  config: Record<string, unknown> = {}
): T =>
  fastMemoize(fn, {
    cache: { create: () => new MemoizeCache<ReturnType<T>>(config) },
    ...config,
  }) as T
