/**
 * @license
 * Copyright 2025 Vybestack LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import { mergeRefreshedToken, sanitizeTokenForProxy, } from '@vybestack/llxprt-code-core';
// ─── Constants ───────────────────────────────────────────────────────────────
const MAX_RETRIES = 2;
const RETRY_DELAYS = [1_000, 3_000];
const AUTH_ERROR_PATTERNS = [
    '401',
    'invalid_grant',
    'invalid_client',
    'unauthorized',
];
// ─── Helpers ─────────────────────────────────────────────────────────────────
function isAuthError(error) {
    if (!(error instanceof Error))
        return false;
    const msg = error.message.toLowerCase();
    if (AUTH_ERROR_PATTERNS.some((p) => msg.includes(p)))
        return true;
    const anyErr = error;
    if (anyErr.status === 401)
        return true;
    if (typeof anyErr.code === 'string' &&
        AUTH_ERROR_PATTERNS.some((p) => anyErr.code === p))
        return true;
    return false;
}
function delay(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
}
// ─── Coordinator ─────────────────────────────────────────────────────────────
export class RefreshCoordinator {
    options;
    cooldownMs;
    lastRefreshMap = new Map();
    inflightMap = new Map();
    constructor(options) {
        this.options = options;
        this.cooldownMs = options.cooldownMs ?? 30_000;
    }
    async refresh(provider, bucket) {
        const key = bucket ? `${provider}:${bucket}` : provider;
        const inflight = this.inflightMap.get(key);
        if (inflight)
            return inflight;
        const lastRefresh = this.lastRefreshMap.get(key);
        if (lastRefresh !== undefined) {
            const elapsed = Date.now() - lastRefresh;
            if (elapsed < this.cooldownMs) {
                const remainingMs = this.cooldownMs - elapsed;
                return {
                    status: 'rate_limited',
                    retryAfter: Math.ceil(remainingMs / 1_000),
                };
            }
        }
        const promise = this.executeRefresh(provider, bucket, key);
        this.inflightMap.set(key, promise);
        try {
            return await promise;
        }
        finally {
            this.inflightMap.delete(key);
        }
    }
    reset() {
        this.lastRefreshMap.clear();
        this.inflightMap.clear();
    }
    async executeRefresh(provider, bucket, key) {
        const { tokenStore, refreshFn } = this.options;
        const currentToken = await tokenStore.getToken(provider, bucket);
        if (!currentToken) {
            return { status: 'error', error: 'Token not found' };
        }
        for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
            try {
                const newToken = await refreshFn(provider, currentToken);
                const merged = mergeRefreshedToken(currentToken, newToken);
                await tokenStore.saveToken(provider, merged, bucket);
                this.lastRefreshMap.set(key, Date.now());
                const sanitized = sanitizeTokenForProxy(merged);
                return { status: 'ok', token: sanitized };
            }
            catch (err) {
                if (isAuthError(err)) {
                    return {
                        status: 'auth_error',
                        error: err instanceof Error ? err.message : String(err),
                    };
                }
                if (attempt < MAX_RETRIES) {
                    await delay(RETRY_DELAYS[attempt]);
                }
                else {
                    return {
                        status: 'error',
                        error: err instanceof Error ? err.message : String(err),
                    };
                }
            }
        }
        return { status: 'error', error: 'Refresh failed' };
    }
}
//# sourceMappingURL=refresh-coordinator.js.map