class L1MemoryCache {
  cache = /* @__PURE__ */ new Map();
  tagIndex = /* @__PURE__ */ new Map();
  // tag -> keys
  maxSize;
  cleanupInterval = null;
  constructor(maxSize = 1e4) {
    this.maxSize = maxSize;
    if (process.env.NODE_ENV !== "test") {
      this.cleanupInterval = setInterval(() => this.cleanup(), 3e5);
      if (this.cleanupInterval.unref) {
        this.cleanupInterval.unref();
      }
    }
  }
  set(key, value, ttlMs, tags = []) {
    var _a;
    if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
      this.evictOldest();
    }
    const existing = this.cache.get(key);
    if (existing) {
      for (const tag of existing.tags) {
        (_a = this.tagIndex.get(tag)) == null ? void 0 : _a.delete(key);
      }
    }
    this.cache.set(key, {
      value,
      expiresAt: Date.now() + ttlMs,
      tags
    });
    for (const tag of tags) {
      if (!this.tagIndex.has(tag)) {
        this.tagIndex.set(tag, /* @__PURE__ */ new Set());
      }
      this.tagIndex.get(tag).add(key);
    }
  }
  get(key) {
    const entry = this.cache.get(key);
    if (!entry) {
      return null;
    }
    if (entry.expiresAt < Date.now()) {
      this.delete(key);
      return null;
    }
    return entry.value;
  }
  delete(key) {
    var _a;
    const entry = this.cache.get(key);
    if (entry) {
      for (const tag of entry.tags) {
        (_a = this.tagIndex.get(tag)) == null ? void 0 : _a.delete(key);
      }
    }
    return this.cache.delete(key);
  }
  invalidateByTag(tag) {
    var _a;
    const keys = this.tagIndex.get(tag);
    if (!keys) return 0;
    const keysToDelete = [...keys];
    this.tagIndex.delete(tag);
    let count = 0;
    for (const key of keysToDelete) {
      const entry = this.cache.get(key);
      if (entry) {
        for (const otherTag of entry.tags) {
          if (otherTag !== tag) {
            (_a = this.tagIndex.get(otherTag)) == null ? void 0 : _a.delete(key);
          }
        }
        this.cache.delete(key);
        count++;
      }
    }
    return count;
  }
  clear() {
    this.cache.clear();
    this.tagIndex.clear();
  }
  /**
   * Evicts the oldest entry (FIFO - First In, First Out).
   * JavaScript Maps maintain insertion order, so the first key is the oldest.
   */
  evictOldest() {
    const firstKey = this.cache.keys().next().value;
    if (firstKey) {
      this.delete(firstKey);
    }
  }
  cleanup() {
    const now = Date.now();
    const toDelete = [];
    for (const [key, entry] of this.cache.entries()) {
      if (entry.expiresAt < now) {
        toDelete.push(key);
      }
    }
    for (const key of toDelete) {
      this.delete(key);
    }
  }
  destroy() {
    if (this.cleanupInterval) {
      clearInterval(this.cleanupInterval);
      this.cleanupInterval = null;
    }
    this.clear();
  }
  getStats() {
    return {
      size: this.cache.size,
      maxSize: this.maxSize,
      tagCount: this.tagIndex.size
    };
  }
}
let l1Cache = new L1MemoryCache();
let redisClient = null;
let redisInitialized = false;
let redisError = null;
const CACHE_PREFIX = "cache:";
const TAG_PREFIX = "cache:tag:";
async function getRedisClient() {
  if (redisInitialized) {
    return redisError ? null : redisClient;
  }
  if (!process.env.UPSTASH_REDIS_REST_URL || !process.env.UPSTASH_REDIS_REST_TOKEN) {
    redisInitialized = true;
    return null;
  }
  try {
    const { Redis } = await import("@upstash/redis");
    redisClient = new Redis({
      url: process.env.UPSTASH_REDIS_REST_URL,
      token: process.env.UPSTASH_REDIS_REST_TOKEN
    });
    redisInitialized = true;
    return redisClient;
  } catch (error) {
    redisError = error;
    redisInitialized = true;
    if (process.env.NODE_ENV === "development") {
      console.warn("[DistributedCache] Redis not available, using L1 only:", error.message);
    }
    return null;
  }
}
function serialize(value) {
  return JSON.stringify(value);
}
function deserialize(value) {
  try {
    return JSON.parse(value);
  } catch (error) {
    if (process.env.NODE_ENV === "development") {
      console.warn("[DistributedCache] JSON parse error, returning raw value:", error);
    }
    return value;
  }
}
async function cacheGet(key) {
  const l1Value = l1Cache.get(key);
  if (l1Value !== null) {
    return l1Value;
  }
  const redis = await getRedisClient();
  if (redis) {
    try {
      const redisKey = `${CACHE_PREFIX}${key}`;
      const redisValue = await redis.get(redisKey);
      if (redisValue) {
        const parsed = deserialize(redisValue);
        const remainingTtl = parsed.ttl - Date.now();
        if (remainingTtl <= 100) {
          return null;
        }
        l1Cache.set(key, parsed.value, remainingTtl, parsed.tags);
        return parsed.value;
      }
    } catch (error) {
      console.error("[DistributedCache] Redis get error:", error);
    }
  }
  return null;
}
async function cacheSet(key, value, ttlSeconds = 300, tags = []) {
  const ttlMs = ttlSeconds * 1e3;
  const expiresAt = Date.now() + ttlMs;
  l1Cache.set(key, value, ttlMs, tags);
  const redis = await getRedisClient();
  if (redis) {
    try {
      const redisKey = `${CACHE_PREFIX}${key}`;
      const redisValue = serialize({ value, tags, ttl: expiresAt });
      await redis.set(redisKey, redisValue, { ex: ttlSeconds });
      if (tags.length > 0) {
        await Promise.all(tags.map((tag) => redis.sadd(`${TAG_PREFIX}${tag}`, key)));
      }
    } catch (error) {
      console.error("[DistributedCache] Redis set error:", error);
    }
  }
}
async function cacheDelete(key) {
  const l1Deleted = l1Cache.delete(key);
  const redis = await getRedisClient();
  if (redis) {
    try {
      const redisKey = `${CACHE_PREFIX}${key}`;
      await redis.del(redisKey);
    } catch (error) {
      console.error("[DistributedCache] Redis delete error:", error);
    }
  }
  return l1Deleted;
}
async function cacheInvalidateByTag(tag) {
  const l1Count = l1Cache.invalidateByTag(tag);
  const redis = await getRedisClient();
  if (redis) {
    try {
      const tagKey = `${TAG_PREFIX}${tag}`;
      const keys = await redis.smembers(tagKey);
      if (keys.length > 0) {
        const redisKeys = keys.map((k) => `${CACHE_PREFIX}${k}`);
        const pipeline = redis.pipeline();
        pipeline.del(...redisKeys);
        pipeline.srem(tagKey, ...keys);
        const result = await pipeline.exec();
        if (!result) {
          console.error("[DistributedCache] Pipeline execution returned null for tag:", tag);
        }
      }
    } catch (error) {
      console.error("[DistributedCache] Redis invalidate error:", error);
    }
  }
  return l1Count;
}
async function scanAndDelete(redis, pattern) {
  let cursor = "0";
  let totalDeleted = 0;
  do {
    const [nextCursor, keys] = await redis.scan(cursor, { match: pattern, count: 100 });
    cursor = nextCursor;
    if (keys.length > 0) {
      const deleted = await redis.del(...keys);
      totalDeleted += deleted;
    }
  } while (cursor !== "0");
  return totalDeleted;
}
async function cacheClear() {
  l1Cache.clear();
  const redis = await getRedisClient();
  if (redis) {
    try {
      await scanAndDelete(redis, `${CACHE_PREFIX}*`);
      await scanAndDelete(redis, `${TAG_PREFIX}*`);
    } catch (error) {
      console.error("[DistributedCache] Redis clear error:", error);
    }
  }
}
async function isDistributedCacheAvailable() {
  const redis = await getRedisClient();
  return redis !== null;
}
async function getCacheStats() {
  const redis = await getRedisClient();
  return {
    l1: l1Cache.getStats(),
    l2Available: redis !== null
  };
}
function createCacheKey(namespace, ...parts) {
  return `${namespace}:${parts.join(":")}`;
}
function _resetForTesting() {
  l1Cache.destroy();
  l1Cache = new L1MemoryCache();
  redisClient = null;
  redisInitialized = false;
  redisError = null;
}
function destroyCache() {
  l1Cache.destroy();
}
export {
  l1Cache as _l1Cache,
  _resetForTesting,
  cacheClear,
  cacheDelete,
  cacheGet,
  cacheInvalidateByTag,
  cacheSet,
  createCacheKey,
  destroyCache,
  getCacheStats,
  isDistributedCacheAvailable
};
