type CacheEntry = { value: string; expiresAtMs?: number };

const nowMs = () => Date.now();

class LocalCache {
  private store = new Map<string, CacheEntry>();

  private isExpired(entry: CacheEntry | undefined) {
    return entry?.expiresAtMs !== undefined && entry.expiresAtMs <= nowMs();
  }

  private getEntry(key: string): CacheEntry | undefined {
    const entry = this.store.get(key);
    if (!entry) return undefined;
    if (this.isExpired(entry)) {
      this.store.delete(key);
      return undefined;
    }
    return entry;
  }

  async get(key: string): Promise<string | null> {
    const entry = this.getEntry(key);
    return entry ? entry.value : null;
  }

  async set(key: string, value: string, opts?: { exSeconds?: number; nx?: boolean }): Promise<boolean> {
    const existing = this.getEntry(key);
    if (opts?.nx && existing) return false;

    const expiresAtMs = opts?.exSeconds !== undefined ? nowMs() + opts.exSeconds * 1000 : undefined;
    this.store.set(key, { value, expiresAtMs });
    return true;
  }

  async del(key: string): Promise<boolean> {
    const existed = this.getEntry(key) !== undefined;
    this.store.delete(key);
    return existed;
  }
}

export const cache = new LocalCache();

/**
 * Best-effort local process lock.
 * - Returns true only once per key during ttl window.
 * - Single process only (no cross-instance coordination).
 */
export const tryAcquireLock = async (key: string, ttlSeconds: number): Promise<boolean> => {
  return cache.set(`lock:${key}`, '1', { exSeconds: ttlSeconds, nx: true });
};
