interface CreateCacheOptions {
  expires?: number; // Default expiration time for all cache entries
}

interface SetCacheOptions {
  expires?: number; // Override expiration for individual cache entries
}

export function createCache<T>(
  createOpts: CreateCacheOptions = {},
) {
  const cache = new Map<string, { value: T; expiresAt?: number }>();

  return {
    get(key: string): T | undefined {
      const entry = cache.get(key);
      if (!entry) return undefined;

      const now = Date.now();
      if (entry.expiresAt && entry.expiresAt <= now) {
        cache.delete(key); // Remove expired entry
        return undefined;
      }

      return entry.value; // Return value if not expired
    },

    set(key: string, value: T | unknown, opts: SetCacheOptions = {}) {
      const now = Date.now();
      const expiresIn = opts.expires ?? createOpts.expires;
      const expiresAt = expiresIn ? now + expiresIn : undefined;

      cache.set(key, { value: value as T, expiresAt });
    },

    cleanup() {
      const now = Date.now();
      for (const [key, entry] of cache.entries()) {
        if (entry.expiresAt && entry.expiresAt <= now) {
          cache.delete(key);
        }
      }
    },

    info(): { count: number; sizeInKB: number } {
      // Cleanup expired entries before calculating info
      this.cleanup();

      // Count the number of objects in the cache
      const count = cache.size;

      // Approximate the size in KB by serializing each key and value
      let totalBytes = 0;
      for (const [key, entry] of cache.entries()) {
        const keySize = new TextEncoder().encode(key).length;
        const valueSize = new TextEncoder().encode(
          JSON.stringify(entry.value),
        ).length;
        totalBytes += keySize + valueSize;
      }

      const sizeInKB = totalBytes / 1024; // Convert bytes to kilobytes
      return { count, sizeInKB };
    },

    has(key: string): boolean {
      const entry = cache.get(key);
      if (!entry) return false;

      const now = Date.now();
      if (entry.expiresAt && entry.expiresAt <= now) {
        cache.delete(key); // Remove expired entry
        return false;
      }

      return true;
    },

    keys(): string[] {
      this.cleanup(); // Cleanup before returning keys
      return Array.from(cache.keys());
    },

    size(): number {
      this.cleanup(); // Cleanup before returning size
      return cache.size;
    },
  };
}
