// engine/llm-providers/load-balancer.ts — Load balancing strategies.
// SPEC-083 AC-04
import { checkProviderHealth } from './health-checker.js';
/** Round-robin state: index per invocation. */
let roundRobinIndex = 0;
/** Simulated request counts per provider (for least-loaded). */
const requestCounts = new Map();
/** Simulated latency records per provider (for latency-based). */
const latencyRecords = new Map();
/**
 * Rank providers using the specified load balancing strategy.
 * Returns an ordered list of recommendations (best first).
 */
export function balanceLoad(definitions, strategy, tier) {
    const healthy = definitions.filter((d) => {
        const h = checkProviderHealth(d);
        return h.status !== 'unavailable';
    });
    if (healthy.length === 0) {
        return [];
    }
    switch (strategy) {
        case 'round-robin':
            return rankRoundRobin(healthy, tier);
        case 'least-loaded':
            return rankLeastLoaded(healthy, tier);
        case 'latency-based':
            return rankLatencyBased(healthy, tier);
        case 'cost-based':
            return rankCostBased(healthy, tier);
    }
}
function rankRoundRobin(defs, tier) {
    const start = roundRobinIndex % defs.length;
    roundRobinIndex++;
    const ordered = [...defs.slice(start), ...defs.slice(0, start)];
    return ordered.map((d, i) => buildRecommendation(d, tier, 1.0 - i * 0.1, 'round-robin'));
}
function rankLeastLoaded(defs, tier) {
    const sorted = [...defs].sort((a, b) => {
        const countA = requestCounts.get(a.id) ?? 0;
        const countB = requestCounts.get(b.id) ?? 0;
        return countA - countB;
    });
    return sorted.map((d, i) => buildRecommendation(d, tier, 1.0 - i * 0.1, 'least-loaded'));
}
function rankLatencyBased(defs, tier) {
    const sorted = [...defs].sort((a, b) => {
        /* v8 ignore next 2 -- fallback for providers without latency records */
        const latA = latencyRecords.get(a.id) ?? 500;
        const latB = latencyRecords.get(b.id) ?? 500;
        return latA - latB;
    });
    return sorted.map((d, i) => buildRecommendation(d, tier, 1.0 - i * 0.1, 'latency-based'));
}
function rankCostBased(defs, tier) {
    const sorted = [...defs].sort((a, b) => {
        /* v8 ignore next 6 -- defensive fallbacks for providers without matching tier */
        const modelA = a.models.find((m) => m.tier === tier) ?? a.models[0];
        const modelB = b.models.find((m) => m.tier === tier) ?? b.models[0];
        const costA = modelA
            ? modelA.pricingPerMToken.input + modelA.pricingPerMToken.output
            : Infinity;
        const costB = modelB
            ? modelB.pricingPerMToken.input + modelB.pricingPerMToken.output
            : Infinity;
        return costA - costB;
    });
    return sorted.map((d, i) => buildRecommendation(d, tier, 1.0 - i * 0.1, 'cost-based'));
}
function buildRecommendation(def, tier, score, strategy) {
    const model = def.models.find((m) => m.tier === tier) ?? def.models[0];
    return {
        providerId: def.id,
        /* v8 ignore next 2 -- model always exists when providers have models */
        modelId: model?.id ?? 'unknown',
        tier: model?.tier ?? tier,
        costEstimate: { tokensEstimated: 0, usdEstimated: 0 },
        reasoning: [`Selected via ${strategy} strategy`],
        score: Math.max(score, 0.1),
    };
}
/** Update simulated request count for a provider. */
export function recordRequest(providerId) {
    requestCounts.set(providerId, (requestCounts.get(providerId) ?? 0) + 1);
}
/** Update simulated latency for a provider. */
export function recordLatency(providerId, latencyMs) {
    latencyRecords.set(providerId, latencyMs);
}
/** Reset all load balancer state (for testing). */
export function resetLoadBalancer() {
    roundRobinIndex = 0;
    requestCounts.clear();
    latencyRecords.clear();
}
//# sourceMappingURL=load-balancer.js.map