"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
var CacheService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.CacheService = void 0;
const common_1 = require("@nestjs/common");
const cache_manager_1 = require("@nestjs/cache-manager");
const cache_category_enum_1 = require("./enums/cache-category.enum");
const cache_tokens_1 = require("./cache.tokens");
/**
 * Centralized cache service that wraps cache-manager with custom logic
 */
let CacheService = CacheService_1 = class CacheService {
    constructor(cacheManager, redisStore) {
        this.cacheManager = cacheManager;
        this.redisStore = redisStore;
        this.logger = new common_1.Logger(CacheService_1.name);
    }
    /**
     * Get a value from cache
     */
    async get(key) {
        try {
            const value = await this.cacheManager.get(key);
            if (value !== undefined && value !== null) {
                this.logger.debug(`Cache HIT: ${key}`);
            }
            else {
                this.logger.debug(`Cache MISS: ${key}`);
            }
            return value;
        }
        catch (error) {
            this.logger.error(`Cache GET error for key ${key}:`, error.message);
            return undefined;
        }
    }
    /**
     * Set a value in cache with optional TTL
     */
    async set(key, value, options) {
        try {
            const ttlSeconds = this.resolveTTL(options);
            const ttlMilliseconds = ttlSeconds * 1000; // cache-manager v7 with Redis uses milliseconds
            await this.cacheManager.set(key, value, ttlMilliseconds);
            this.logger.debug(`Cache SET: ${key} (TTL: ${ttlSeconds}s = ${ttlMilliseconds}ms)`);
        }
        catch (error) {
            this.logger.error(`Cache SET error for key ${key}:`, error.message);
        }
    }
    /**
     * Delete a specific cache key
     */
    async delete(key) {
        try {
            await this.cacheManager.del(key);
            this.logger.debug(`Cache DELETE: ${key}`);
        }
        catch (error) {
            this.logger.error(`Cache DELETE error for key ${key}:`, error.message);
        }
    }
    async deletePattern(pattern) {
        if (!this.redisStore) {
            this.logger.warn(`Redis cache store not available, cannot delete pattern: ${pattern}`);
            return;
        }
        try {
            const fullPattern = `${CacheService_1.KEY_PREFIX}${pattern}`;
            const client = this.redisStore.client;
            const keys = [];
            for await (const key of client.scanIterator({
                MATCH: fullPattern,
                COUNT: 200,
            })) {
                keys.push(...(Array.isArray(key) ? key : [key]));
            }
            if (keys.length > 0) {
                await client.del(keys);
            }
            this.logger.debug(`Cache DELETE PATTERN: ${pattern} (${keys.length} keys deleted)`);
        }
        catch (error) {
            const msg = error instanceof Error ? error.message : String(error);
            this.logger.error(`Cache DELETE PATTERN error for ${pattern}: ${msg}`);
        }
    }
    /**
     * Clear all cache
     */
    async reset() {
        try {
            // In cache-manager v7, use store.reset() instead
            const store = this.cacheManager.store;
            if (store?.reset) {
                await store.reset();
                this.logger.log('Cache RESET: All cache cleared');
            }
            else {
                this.logger.warn('Cache reset not supported by this store');
            }
        }
        catch (error) {
            this.logger.error('Cache RESET error:', error.message);
        }
    }
    /**
     * Wrap a function with caching logic
     * If skipCache is true, always fetch fresh data
     */
    async wrap(key, factory, options) {
        // If skipCache is true, bypass cache and fetch fresh data
        if (options?.skipCache) {
            this.logger.debug(`Cache SKIP: ${key} (forced refresh)`);
            const value = await factory();
            // Validate if value should be cached
            const shouldCache = options?.shouldCache
                ? options.shouldCache(value)
                : true;
            if (shouldCache) {
                await this.set(key, value, options);
            }
            else {
                this.logger.debug(`NOT caching value (failed validation): ${key}`);
            }
            return value;
        }
        // Try to get from cache first
        const cached = await this.get(key);
        if (cached !== undefined && cached !== null) {
            return cached;
        }
        // Cache miss, fetch fresh data
        const value = await factory();
        // Validate if value should be cached
        const shouldCache = options?.shouldCache
            ? options.shouldCache(value)
            : true;
        if (shouldCache) {
            await this.set(key, value, options);
        }
        else {
            this.logger.debug(`NOT caching value (failed validation): ${key}`);
        }
        return value;
    }
    /**
     * Build a cache key with consistent format
     */
    buildKey(...parts) {
        return parts.filter((p) => p !== undefined && p !== null).join(':');
    }
    /**
     * Helper to determine if a response should be cached
     * Returns false for error responses (4xx, 5xx) or responses with errors
     *
     * @param value - The value to validate
     * @returns true if value should be cached, false otherwise
     *
     * @example
     * ```typescript
     * cacheService.wrap(key, factory, {
     *   shouldCache: CacheService.shouldCacheResponse
     * });
     * ```
     */
    static shouldCacheResponse(value) {
        // Don't cache null or undefined
        if (value === null || value === undefined) {
            return false;
        }
        // If the response contains an errors array with items, don't cache
        if (value?.errors &&
            Array.isArray(value.errors) &&
            value.errors.length > 0) {
            return false;
        }
        // If the response has an error property (boolean), don't cache
        if (value?.error === true) {
            return false;
        }
        // If the response has a statusCode indicating an error (4xx, 5xx), don't cache
        if (value?.statusCode && value.statusCode >= 400) {
            return false;
        }
        // If the response has a status property indicating error
        if (typeof value?.status === 'number' && value.status >= 400) {
            return false;
        }
        // Default: cache the response
        return true;
    }
    /**
     * Resolve TTL from options or category
     */
    resolveTTL(options) {
        if (options?.ttl !== undefined) {
            return options.ttl;
        }
        if (options?.category) {
            return cache_category_enum_1.CACHE_TTL_BY_CATEGORY[options.category];
        }
        // Default TTL: 1 hour
        return 3600;
    }
};
exports.CacheService = CacheService;
/**
 * Delete cache keys matching a pattern (e.g., "provider:hetzner:*")
 * Note: This requires Redis store with pattern matching support
 */
// The Redis key prefix configured in cache.module.ts
CacheService.KEY_PREFIX = 'flui:cache:';
exports.CacheService = CacheService = CacheService_1 = __decorate([
    (0, common_1.Injectable)(),
    __param(0, (0, common_1.Inject)(cache_manager_1.CACHE_MANAGER)),
    __param(1, (0, common_1.Optional)()),
    __param(1, (0, common_1.Inject)(cache_tokens_1.REDIS_CACHE_STORE)),
    __metadata("design:paramtypes", [Object, Object])
], CacheService);
