import { computeContextMultiplier } from '../context-weighted.js';
import { computeHealthWeight } from '../health-weighted.js';
import { buildQuotaBuckets } from './native-router-hotpath.js';
import { computeContextWeightMultipliers } from './context-weight-multipliers.js';
import { pickPriorityGroup } from './tier-priority.js';
import { extractProviderId, getProviderModelId } from './key-parsing.js';
function buildPrimaryTargetGroups(candidates, deps) {
    const groups = new Map();
    for (const key of candidates) {
        const providerId = extractProviderId(key) ?? '';
        if (!providerId) {
            continue;
        }
        let modelId;
        try {
            modelId = getProviderModelId(key, deps.providerRegistry) ?? undefined;
        }
        catch {
            modelId = undefined;
        }
        const groupId = modelId ? `${providerId}.${modelId}` : providerId;
        const entry = groups.get(groupId) ?? [];
        entry.push(key);
        groups.set(groupId, entry);
    }
    return groups;
}
function resolveGroupWeight(groupId, weights) {
    if (!weights) {
        return 1;
    }
    const direct = weights[groupId];
    if (typeof direct === 'number' && Number.isFinite(direct) && direct > 0) {
        return direct;
    }
    const providerId = groupId.split('.')[0] ?? groupId;
    const providerOnly = weights[providerId];
    if (typeof providerOnly === 'number' && Number.isFinite(providerOnly) && providerOnly > 0) {
        return providerOnly;
    }
    return 1;
}
function buildGroupWeights(groups, weights) {
    if (!groups.size) {
        return undefined;
    }
    const out = {};
    for (const [groupId] of groups.entries()) {
        out[groupId] = resolveGroupWeight(groupId, weights);
    }
    return out;
}
function hasNonUniformWeights(candidates, weights) {
    if (!weights || candidates.length < 2) {
        return false;
    }
    let ref;
    for (const key of candidates) {
        const raw = weights[key];
        if (typeof raw !== 'number' || !Number.isFinite(raw)) {
            continue;
        }
        if (ref === undefined) {
            ref = raw;
        }
        else if (Math.abs(raw - ref) > 1e-6) {
            return true;
        }
    }
    return false;
}
export function selectProviderKeyWithQuotaBuckets(opts) {
    const { routeName, tier, stickyKey, candidates, isSafePool, deps, options, contextResult, warnRatio, isRecoveryAttempt, now, nowForWeights, healthWeightedCfg, contextWeightedCfg, quotaView, isAvailable, selectFirstAvailable, applyAliasStickyQueuePinning, preferAntigravityAliasesOnRetry } = opts;
    const bucketInputs = candidates.map((key, order) => {
        const entry = quotaView(key);
        const penaltyRaw = entry?.selectionPenalty;
        return {
            key,
            order,
            hasQuota: Boolean(entry),
            inPool: entry ? entry.inPool !== false : true,
            ...(entry && typeof entry.cooldownUntil === 'number' ? { cooldownUntil: entry.cooldownUntil } : {}),
            ...(entry && typeof entry.blacklistUntil === 'number' ? { blacklistUntil: entry.blacklistUntil } : {}),
            ...(entry && typeof entry.priorityTier === 'number' ? { priorityTier: entry.priorityTier } : {}),
            ...(typeof penaltyRaw === 'number' ? { selectionPenalty: penaltyRaw } : {})
        };
    });
    const { priorities: sortedPriorities, buckets } = buildQuotaBuckets(bucketInputs, now);
    for (const priority of sortedPriorities) {
        const bucket = buckets.get(priority) ?? [];
        if (!bucket.length) {
            continue;
        }
        bucket.sort((a, b) => a.order - b.order);
        let bucketCandidates = bucket.map((item) => item.key);
        if (bucketCandidates.length === 1) {
            return bucketCandidates[0] ?? null;
        }
        if (isRecoveryAttempt) {
            bucketCandidates = preferAntigravityAliasesOnRetry(bucketCandidates);
        }
        bucketCandidates = applyAliasStickyQueuePinning(bucketCandidates);
        const bucketWeights = {};
        for (const item of bucket) {
            if (healthWeightedCfg.enabled) {
                const entry = quotaView(item.key);
                const { weight } = computeHealthWeight(entry, nowForWeights, healthWeightedCfg);
                bucketWeights[item.key] = weight;
            }
            else {
                bucketWeights[item.key] = Math.max(1, Math.floor(100 / (1 + Math.max(0, item.penalty))));
            }
        }
        if (isSafePool && contextWeightedCfg.enabled) {
            const ctx = computeContextWeightMultipliers({
                candidates: bucketCandidates,
                usage: contextResult.usage,
                warnRatio,
                cfg: contextWeightedCfg
            });
            if (ctx) {
                for (const key of bucketCandidates) {
                    const m = computeContextMultiplier({
                        effectiveSafeRefTokens: ctx.ref,
                        effectiveSafeTokens: ctx.eff[key] ?? 1,
                        cfg: contextWeightedCfg
                    });
                    bucketWeights[key] = Math.max(1, Math.round((bucketWeights[key] ?? 1) * m));
                }
            }
        }
        if (tier.mode === 'priority') {
            if (!isRecoveryAttempt) {
                const group = pickPriorityGroup({
                    candidates: bucketCandidates,
                    orderedTargets: tier.targets,
                    providerRegistry: deps.providerRegistry,
                    availabilityCheck: isAvailable
                });
                if (!group) {
                    continue;
                }
                const groupWeights = {};
                for (const key of group.groupCandidates) {
                    groupWeights[key] = bucketWeights[key] ?? 1;
                }
                const allowGrouped = !hasNonUniformWeights(group.groupCandidates, bucketWeights);
                if (allowGrouped && deps.loadBalancer.getPolicy().strategy !== 'sticky') {
                    const groups = buildPrimaryTargetGroups(group.groupCandidates, deps);
                    if (groups.size > 0) {
                        const groupWeightMap = buildGroupWeights(groups, deps.loadBalancer.getPolicy().weights);
                        const selected = deps.loadBalancer.selectGrouped({
                            routeName: `${routeName}:${tier.id}:priority:${priority}:group:${group.groupId}`,
                            groups,
                            stickyKey: options.allowAliasRotation ? undefined : stickyKey,
                            weights: groupWeightMap,
                            availabilityCheck: isAvailable
                        }, 'round-robin');
                        if (selected) {
                            return selected;
                        }
                    }
                }
                const selected = deps.loadBalancer.select({
                    routeName: `${routeName}:${tier.id}:priority:${priority}:group:${group.groupId}`,
                    candidates: group.groupCandidates,
                    stickyKey: options.allowAliasRotation ? undefined : stickyKey,
                    weights: groupWeights,
                    availabilityCheck: isAvailable
                }, 'round-robin');
                if (selected) {
                    return selected;
                }
                continue;
            }
            const recovered = selectFirstAvailable(bucketCandidates);
            if (recovered)
                return recovered;
            continue;
        }
        if (isRecoveryAttempt) {
            const recovered = selectFirstAvailable(bucketCandidates);
            if (recovered)
                return recovered;
            continue;
        }
        const allowGrouped = !hasNonUniformWeights(bucketCandidates, bucketWeights);
        if (allowGrouped && deps.loadBalancer.getPolicy().strategy !== 'sticky') {
            const groups = buildPrimaryTargetGroups(bucketCandidates, deps);
            if (groups.size > 0) {
                const groupWeightMap = buildGroupWeights(groups, deps.loadBalancer.getPolicy().weights);
                const selected = deps.loadBalancer.selectGrouped({
                    routeName: `${routeName}:${tier.id}:${priority}`,
                    groups,
                    stickyKey: options.allowAliasRotation ? undefined : stickyKey,
                    weights: groupWeightMap,
                    availabilityCheck: isAvailable
                }, tier.mode === 'round-robin' ? 'round-robin' : undefined);
                if (selected) {
                    return selected;
                }
            }
        }
        const selected = deps.loadBalancer.select({
            routeName: `${routeName}:${tier.id}`,
            candidates: bucketCandidates,
            stickyKey: options.allowAliasRotation ? undefined : stickyKey,
            weights: bucketWeights,
            availabilityCheck: isAvailable
        }, tier.mode === 'round-robin' ? 'round-robin' : undefined);
        if (selected) {
            return selected;
        }
    }
    return null;
}
