const DEFAULT_EXPIRATION_TIME = 1000 * 60 * 60 * 24 // 1 day

export class Cache {
  private storage = new Map<
    any,
    {
      expires: number
      value: any
    }
  >()

  private static normalizeKey(key: any) {
    return typeof key === 'object' ? JSON.stringify(key) : key
  }

  public has(key: any) {
    return this.hit(key)
  }

  public get<T>(key: any) {
    return this.storage.get(Cache.normalizeKey(key))?.value as T
  }

  public set(key: any, value: any, seconds: number = DEFAULT_EXPIRATION_TIME) {
    this.storage.set(Cache.normalizeKey(key), {
      value,
      expires: Date.now() + seconds * 1000,
    })
  }

  public hit(key: any) {
    return !this.miss(key)
  }

  public miss(key: any) {
    key = Cache.normalizeKey(key)

    if (!this.storage.has(key)) {
      return true
    }

    const { expires } = this.storage.get(key)!

    if (expires < Date.now()) {
      this.storage.delete(key)
      return true
    }

    return false
  }

  public remove(key: any) {
    this.storage.delete(Cache.normalizeKey(key))
  }

  async remember<R>(key: any, resolver: () => R | Promise<R>, seconds: number = DEFAULT_EXPIRATION_TIME) {
    this.hit(key) || this.set(key, await resolver(), seconds)
    return this.get(key) as Awaited<R>
  }

  clear() {
    this.storage.clear()
  }
}

export const cache = new Cache()
