import { redisClient } from "../db/redis.js";
import { encode, decode } from "msgpackr";
import config from "../config/index.js";
export class Cache {
    ttl;
    prefix;
    constructor(name, ttlSeconds){
        this.ttl = ttlSeconds;
        this.prefix = `cache:${name}`;
    }
    prefixedKey(key) {
        return key ? `${this.prefix}:${key}` : this.prefix;
    }
    async set(key, value, transaction) {
        const _key = this.prefixedKey(key);
        const _value = Buffer.from(encode(value));
        const commander = transaction ?? redisClient;
        await commander.set(_key, _value, "EX", this.ttl);
    }
    async get(key, renew = false) {
        const _key = this.prefixedKey(key);
        const cached = await redisClient.getBuffer(_key);
        if (cached === null) return undefined;
        if (renew) await redisClient.expire(_key, this.ttl);
        return decode(cached);
    }
    async getAll(renew = false) {
        const finalPrefix = `${config.redis.prefix}:${this.prefix}:`;
        const keys = (await redisClient.keys(`${finalPrefix}*`)).map((p)=>p.substring(finalPrefix.length));
        const prefixedKeys = keys.map((p)=>this.prefixedKey(p));
        const map = new Map();
        if (keys.length === 0) {
            return map;
        }
        const values = await redisClient.mgetBuffer(prefixedKeys);
        for (const [i, key] of keys.entries()){
            const val = values[i];
            if (val !== null) {
                map.set(key, decode(val));
            }
        }
        if (renew) {
            const trans = redisClient.multi();
            for (const key of map.keys()){
                trans.expire(this.prefixedKey(key), this.ttl);
            }
            await trans.exec();
        }
        return map;
    }
    async delete(...keys) {
        if (keys.length > 0) {
            const _keys = keys.map((p)=>this.prefixedKey(p));
            await redisClient.del(_keys);
        }
    }
    /**
	 * Returns if cached value exists. Otherwise, calls fetcher and caches.
	 * Overwrites cached value if invalidated by the optional validator.
	 */ async fetch(key, fetcher, renew = false, validator) {
        const cachedValue = await this.get(key, renew);
        if (cachedValue !== undefined) {
            if (validator) {
                if (validator(cachedValue)) {
                    // Cache HIT
                    return cachedValue;
                }
            } else {
                // Cache HIT
                return cachedValue;
            }
        }
        // Cache MISS
        const value = await fetcher();
        await this.set(key, value);
        return value;
    }
    /**
	 * Returns if cached value exists. Otherwise, calls fetcher and caches if the fetcher returns a value.
	 * Overwrites cached value if invalidated by the optional validator.
	 */ async fetchMaybe(key, fetcher, renew = false, validator) {
        const cachedValue = await this.get(key, renew);
        if (cachedValue !== undefined) {
            if (validator) {
                if (validator(cachedValue)) {
                    // Cache HIT
                    return cachedValue;
                }
            } else {
                // Cache HIT
                return cachedValue;
            }
        }
        // Cache MISS
        const value = await fetcher();
        if (value !== undefined) {
            await this.set(key, value);
        }
        return value;
    }
}
