"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.resetDesktopVersionPolicyServiceForTests = exports.getDesktopVersionPolicyService = void 0;
const logger_1 = require("../utils/logger");
const log = (0, logger_1.createLogger)('DesktopVersionPolicyService');
const DEFAULT_POLICY_URL = 'https://releases.rivet.design/desktop-version-policy.json';
const DEFAULT_CACHE_TTL_MS = 5 * 60 * 1000;
const DEFAULT_POLICY_FETCH_TIMEOUT_MS = 5000;
const normalizeSemverCore = (version) => {
    const [core] = version.trim().split('-');
    const parts = core.split('.');
    if (parts.length !== 3) {
        return null;
    }
    const [major, minor, patch] = parts.map((part) => Number.parseInt(part, 10));
    if (!Number.isInteger(major) ||
        !Number.isInteger(minor) ||
        !Number.isInteger(patch)) {
        return null;
    }
    return `${major}.${minor}.${patch}`;
};
const computeMinorFloor = (version) => {
    const normalized = normalizeSemverCore(version);
    if (!normalized) {
        return null;
    }
    const [major, minor] = normalized.split('.');
    return `${major}.${minor}.0`;
};
const parseDesktopVersionPolicy = (payload) => {
    if (!payload || typeof payload !== 'object') {
        return null;
    }
    const record = payload;
    if (typeof record.latestVersion !== 'string') {
        return null;
    }
    const normalizedLatest = normalizeSemverCore(record.latestVersion);
    if (!normalizedLatest) {
        return null;
    }
    const normalizedMinimumFromPayload = typeof record.minimumVersion === 'string'
        ? normalizeSemverCore(record.minimumVersion)
        : null;
    const minimumVersion = normalizedMinimumFromPayload ?? computeMinorFloor(normalizedLatest);
    if (!minimumVersion) {
        return null;
    }
    const sourceTimestamp = typeof record.publishedAt === 'string' && record.publishedAt.length > 0
        ? record.publishedAt
        : new Date().toISOString();
    return {
        minimumVersion,
        latestVersion: normalizedLatest,
        downloadUrl: typeof record.downloadUrl === 'string' ? record.downloadUrl : undefined,
        sourceTimestamp,
    };
};
class DesktopVersionPolicyService {
    cache = {
        value: null,
        fetchedAtMs: 0,
    };
    inFlightFetch = null;
    policyUrl;
    cacheTtlMs;
    fetchTimeoutMs;
    constructor() {
        this.policyUrl = process.env.RIVET_DESKTOP_POLICY_URL ?? DEFAULT_POLICY_URL;
        const ttlFromEnv = Number.parseInt(process.env.RIVET_DESKTOP_POLICY_CACHE_TTL_MS ?? '', 10);
        this.cacheTtlMs =
            Number.isInteger(ttlFromEnv) && ttlFromEnv > 0
                ? ttlFromEnv
                : DEFAULT_CACHE_TTL_MS;
        const timeoutFromEnv = Number.parseInt(process.env.RIVET_DESKTOP_POLICY_FETCH_TIMEOUT_MS ?? '', 10);
        this.fetchTimeoutMs =
            Number.isInteger(timeoutFromEnv) && timeoutFromEnv > 0
                ? timeoutFromEnv
                : DEFAULT_POLICY_FETCH_TIMEOUT_MS;
    }
    shouldUseFreshCache(nowMs) {
        if (this.cache.fetchedAtMs <= 0) {
            return false;
        }
        return nowMs - this.cache.fetchedAtMs < this.cacheTtlMs;
    }
    async getPolicy() {
        const nowMs = Date.now();
        if (this.shouldUseFreshCache(nowMs)) {
            return this.cache.value;
        }
        if (!this.inFlightFetch) {
            this.inFlightFetch = this.fetchAndCachePolicy().finally(() => {
                this.inFlightFetch = null;
            });
        }
        const fetched = await this.inFlightFetch;
        if (fetched) {
            return fetched;
        }
        // Fail open: stale cached value is still returned if network fetch fails.
        return this.cache.value;
    }
    async fetchAndCachePolicy() {
        try {
            const abortController = new AbortController();
            const timeoutId = setTimeout(() => abortController.abort(), this.fetchTimeoutMs);
            const response = await fetch(this.policyUrl, {
                signal: abortController.signal,
            }).finally(() => {
                clearTimeout(timeoutId);
            });
            if (!response.ok) {
                throw new Error(`Policy request failed with status ${response.status}`);
            }
            const parsed = parseDesktopVersionPolicy(await response.json());
            if (!parsed) {
                throw new Error('Policy payload shape is invalid');
            }
            this.cache = {
                value: parsed,
                fetchedAtMs: Date.now(),
            };
            return parsed;
        }
        catch (error) {
            log.warn('Failed to fetch desktop version policy:', error);
            // Negative cache failures so repeated /api/config calls don't trigger
            // a fresh network timeout every time while the policy endpoint is down.
            this.cache = {
                value: this.cache.value,
                fetchedAtMs: Date.now(),
            };
            return null;
        }
    }
}
let instance = null;
const getDesktopVersionPolicyService = () => {
    if (!instance) {
        instance = new DesktopVersionPolicyService();
    }
    return instance;
};
exports.getDesktopVersionPolicyService = getDesktopVersionPolicyService;
const resetDesktopVersionPolicyServiceForTests = () => {
    instance = null;
};
exports.resetDesktopVersionPolicyServiceForTests = resetDesktopVersionPolicyServiceForTests;
//# sourceMappingURL=DesktopVersionPolicyService.js.map