/**
 * AccountPool — manages a pool of proxy accounts with round-robin / fill-first
 * selection and exponential-backoff cooldowns for quota-exceeded accounts.
 *
 * @module auth/accountPool
 */
import { tokenStore } from "./tokenStore.js";
// =============================================================================
// CONSTANTS
// =============================================================================
const DEFAULT_COOLDOWN_MS = 300_000;
const DEFAULT_MAX_COOLDOWN_MS = 1_800_000;
// =============================================================================
// IMPLEMENTATION
// =============================================================================
export class AccountPool {
    accounts = new Map();
    cursor = 0;
    config;
    constructor(config = {}) {
        this.config = {
            strategy: config.strategy ?? "round-robin",
            defaultCooldownMs: config.defaultCooldownMs ?? DEFAULT_COOLDOWN_MS,
            maxCooldownMs: config.maxCooldownMs ?? DEFAULT_MAX_COOLDOWN_MS,
            maxRetryAccounts: config.maxRetryAccounts ?? 0,
        };
    }
    /** Add (or replace) an account in the pool. */
    addAccount(account) {
        // Clear any stale token refresher for a previously-registered account
        // with the same id — wireTokenRefresh() captures the old account object
        // in its closure, so leaving it behind would refresh the wrong tokens.
        tokenStore.clearTokenRefresher(account.id);
        this.accounts.set(account.id, { ...account });
    }
    /** Remove an account from the pool by id. */
    removeAccount(id) {
        tokenStore.clearTokenRefresher(id);
        this.accounts.delete(id);
    }
    /**
     * Return the next healthy account according to the configured strategy.
     * Returns `null` when no healthy accounts are available.
     */
    getNextAccount() {
        this._expireCooldowns();
        const healthy = this._getHealthyAccounts();
        if (healthy.length === 0) {
            return null;
        }
        if (this.config.strategy === "fill-first") {
            const account = healthy[0];
            account.requestCount++;
            account.lastUsed = Date.now();
            return account;
        }
        // round-robin
        const index = this.cursor % healthy.length;
        this.cursor++;
        const account = healthy[index];
        account.requestCount++;
        account.lastUsed = Date.now();
        return account;
    }
    /**
     * Mark an account as quota-exceeded.  The account enters a cooldown state
     * with exponential backoff capped at `maxCooldownMs`.
     *
     * @deprecated Proxy routes now use `RuntimeAccountState` in `claudeProxyRoutes.ts`
     * for runtime account state management. This method is retained for public API compatibility.
     *
     * @param id             Account id
     * @param retryAfterMs   Optional explicit retry-after duration (from server header)
     */
    markQuotaExceeded(id, retryAfterMs) {
        const account = this.accounts.get(id);
        if (!account) {
            return;
        }
        account.consecutiveFailures++;
        const backoff = Math.min(this.config.defaultCooldownMs *
            Math.pow(2, account.consecutiveFailures - 1), this.config.maxCooldownMs);
        account.cooldownUntil = Date.now() + (retryAfterMs ?? backoff);
        account.status = "cooling";
    }
    /**
     * Manually mark an account as available (healthy), clearing its cooldown.
     *
     * @deprecated Proxy routes now use `RuntimeAccountState` in `claudeProxyRoutes.ts`
     * for runtime account state management. This method is retained for public API compatibility.
     */
    markAvailable(id) {
        const account = this.accounts.get(id);
        if (!account) {
            return;
        }
        account.status = "healthy";
        account.cooldownUntil = undefined;
    }
    /**
     * Mark an account as having completed a request successfully.
     *
     * @deprecated Proxy routes now use `RuntimeAccountState` in `claudeProxyRoutes.ts`
     * for runtime account state management. This method is retained for public API compatibility.
     */
    markSuccess(id) {
        const account = this.accounts.get(id);
        if (!account) {
            return;
        }
        account.status = "healthy";
        account.cooldownUntil = undefined;
        account.consecutiveFailures = 0;
    }
    /** Return the number of currently healthy (not cooling/disabled) accounts. */
    getHealthyCount() {
        this._expireCooldowns();
        return this._getHealthyAccounts().length;
    }
    /** Return a snapshot of all accounts in the pool. */
    getAllAccounts() {
        return Array.from(this.accounts.values());
    }
    /** Return the configured selection strategy. */
    getStrategy() {
        return this.config.strategy;
    }
    /**
     * Wire automatic token refresh for an OAuth account.
     * Call after addAccount() for OAuth accounts that have a refresh token.
     *
     * @param accountId  The account id to wire refresh for
     * @param refreshFn  Function that takes a refresh token and returns new token data
     */
    wireTokenRefresh(accountId, refreshFn) {
        const account = this.accounts.get(accountId);
        if (!account || account.type !== "oauth" || !account.tokens?.refreshToken) {
            return;
        }
        tokenStore.setTokenRefresher(accountId, async (refreshToken) => {
            const result = await refreshFn(refreshToken);
            const newTokens = {
                accessToken: result.accessToken,
                refreshToken: result.refreshToken ?? refreshToken,
                expiresAt: result.expiresAt instanceof Date
                    ? result.expiresAt.getTime()
                    : result.expiresAt,
                tokenType: result.tokenType ?? "Bearer",
            };
            // Update in-memory account
            if (account.tokens) {
                account.tokens.accessToken = newTokens.accessToken;
                account.tokens.expiresAt = newTokens.expiresAt;
                if (newTokens.refreshToken) {
                    account.tokens.refreshToken = newTokens.refreshToken;
                }
            }
            return newTokens;
        });
    }
    // ---------------------------------------------------------------------------
    // Private helpers
    // ---------------------------------------------------------------------------
    _getHealthyAccounts() {
        return Array.from(this.accounts.values()).filter((a) => a.status === "healthy");
    }
    _expireCooldowns() {
        const now = Date.now();
        for (const account of this.accounts.values()) {
            if (account.status === "cooling" &&
                account.cooldownUntil &&
                now >= account.cooldownUntil) {
                account.status = "healthy";
                account.cooldownUntil = undefined;
            }
        }
    }
}
//# sourceMappingURL=accountPool.js.map