/**
 * Internal cached object
 */
interface CacheObj<T = unknown> {
  value: T
  expiresAt: number
}

/**
 * Local in-memory object cache implementation with built-in TTL
 */
export class Cache {
  private cache: Map<string, CacheObj>
  private defaultTtl: number

  /**
   * @param defaultTtl Default TTL for the cache in milliseconds. Defaults to 60 minutes
   */
  constructor(defaultTtl?: number) {
    this.cache = new Map()
    this.defaultTtl = defaultTtl ?? 3600_000
  }

  /**
   * Get object from cache
   *
   * @param key Cache key
   * @returns Cached object or undefined if the object doesn't exist in cache or has expired
   */
  public get<T = unknown>(key: string): T | undefined {
    const cached = this.cache.get(key)
    if (!cached || cached.expiresAt < Date.now()) {
      this.cache.delete(key)
      return undefined
    }
    return cached.value as T
  }

  /**
   * Put object to cache
   *
   * @param key Cache key
   * @param value Cached value
   * @param ttl Cache validity, default to 60 minutes
   */
  public put(key: string, value: unknown, ttl?: number): void {
    this.cache.set(key, { value, expiresAt: Date.now() + (ttl ?? this.defaultTtl) })
  }

  /**
   * Return boolean that indicates if cache has an object with this key
   *
   * @param key Cache key
   * @returns `true` if cache has the given key and it has not expired, `false` otherwise
   */
  public has(key: string): boolean {
    const cached = this.get(key)
    return cached ? true : false
  }
}
