import { LRUCache } from "lru-cache";
import type { Language } from "@/i18n/resolver";

// --- Cache key convention ---

export function cacheKey(
  endpoint: string,
  query: Record<string, unknown>,
  locale: Language,
): string {
  const sortedQuery = Object.keys(query)
    .sort()
    .map((k) => `${k}=${String(query[k])}`)
    .join("&");
  return `${locale}:${endpoint}?${sortedQuery}`;
}

// --- (1) Request-scoped SSR dedup cache ---

export class RequestScopedCache {
  private readonly store = new Map<string, Promise<unknown>>();

  get<T>(key: string): Promise<T> | undefined {
    return this.store.get(key) as Promise<T> | undefined;
  }

  set<T>(key: string, promise: Promise<T>): void {
    this.store.set(key, promise);
  }
}

// --- (2) Client-side per-tab in-memory TTL cache ---

export class ClientMemoryCache<T extends NonNullable<unknown>> {
  private readonly cache: LRUCache<string, T>;

  constructor(options: { max: number; defaultTtlMs: number }) {
    this.cache = new LRUCache<string, T>({
      max: options.max,
      ttl: options.defaultTtlMs,
      ttlResolution: 0,
    });
  }

  get(key: string): T | undefined {
    return this.cache.get(key);
  }

  set(key: string, value: T, ttlMs?: number): void {
    this.cache.set(key, value, ttlMs !== undefined ? { ttl: ttlMs } : undefined);
  }

  delete(key: string): void {
    this.cache.delete(key);
  }

  clear(): void {
    this.cache.clear();
  }

  get size(): number {
    return this.cache.size;
  }
}

// --- (3) Shared per-VM LRU with byte cap ---

export class ServerLruCache<T extends NonNullable<unknown>> {
  private readonly cache: LRUCache<string, T>;

  constructor(options: {
    maxBytes: number;
    defaultTtlMs: number;
    sizeCalculation?: (value: T, key: string) => number;
  }) {
    this.cache = new LRUCache<string, T>({
      maxSize: options.maxBytes,
      ttl: options.defaultTtlMs,
      ttlResolution: 0,
      sizeCalculation:
        options.sizeCalculation ??
        ((value: T) => {
          try {
            return JSON.stringify(value).length;
          } catch {
            return 1024; // fallback for non-serializable values
          }
        }),
    });
  }

  get(key: string): T | undefined {
    return this.cache.get(key);
  }

  set(key: string, value: T, ttlMs?: number): void {
    this.cache.set(key, value, ttlMs !== undefined ? { ttl: ttlMs } : undefined);
  }

  delete(key: string): void {
    this.cache.delete(key);
  }

  clear(): void {
    this.cache.clear();
  }

  get calculatedSize(): number {
    return this.cache.calculatedSize;
  }
}
