import { DEFAULT_ROUTE, VirtualRouterError, VirtualRouterErrorCode } from '../types.js';
import { buildRoutingFeatures } from '../features.js';
import { persistRoutingInstructionState } from '../engine/routing-state/store.js';
import { selectDirectProviderModel, selectFromStickyPool as selectFromStickyPoolImpl } from '../engine/routing-pools/index.js';
export function selectRoutingTarget(engine, request, metadata, routingState, stateKey) {
    const features = buildRoutingFeatures(request, metadata);
    const directProviderModel = engine.parseDirectProviderModel(request?.model);
    let classification;
    let requestedRoute;
    let selection = null;
    const selectionDeps = {
        routing: engine.routing,
        providerRegistry: engine.providerRegistry,
        healthManager: engine.healthManager,
        contextAdvisor: engine.contextAdvisor,
        loadBalancer: engine.loadBalancer,
        isProviderCoolingDown: (key) => engine.isProviderCoolingDown(key),
        resolveStickyKey: (m) => engine.resolveStickyKey(m),
        quotaView: engine.quotaView
    };
    if (directProviderModel) {
        const forceMediaFallback = engine.shouldFallbackDirectModelForMedia(directProviderModel, features);
        const providerKeys = engine.providerRegistry.listProviderKeys(directProviderModel.providerId);
        let hasModel = false;
        for (const key of providerKeys) {
            try {
                const profile = engine.providerRegistry.get(key);
                if (profile?.modelId === directProviderModel.modelId) {
                    hasModel = true;
                    break;
                }
            }
            catch {
                continue;
            }
        }
        if (!hasModel) {
            throw new VirtualRouterError(`Unknown model ${directProviderModel.modelId} for provider ${directProviderModel.providerId}`, VirtualRouterErrorCode.CONFIG_ERROR, { providerId: directProviderModel.providerId, modelId: directProviderModel.modelId });
        }
        if (!forceMediaFallback) {
            const directSelection = selectDirectProviderModel(directProviderModel.providerId, directProviderModel.modelId, metadata, features, routingState, selectionDeps);
            if (!directSelection) {
                throw new VirtualRouterError(`All providers unavailable for model ${directProviderModel.providerId}.${directProviderModel.modelId}`, VirtualRouterErrorCode.PROVIDER_NOT_AVAILABLE, { providerId: directProviderModel.providerId, modelId: directProviderModel.modelId });
            }
            classification = {
                routeName: 'direct',
                confidence: 1,
                reasoning: `direct_model:${directProviderModel.providerId}.${directProviderModel.modelId}`,
                fallback: false,
                candidates: ['direct']
            };
            requestedRoute = 'direct';
            selection = directSelection;
        }
        else {
            classification = engine.classifier.classify(features);
            requestedRoute = engine.normalizeRouteAlias(classification.routeName || DEFAULT_ROUTE);
            selection = engine.selectProvider(requestedRoute, metadata, classification, features, routingState);
        }
    }
    else {
        // Prefer target (from "<**!provider.model**>") is evaluated before routing classification.
        const preferTarget = routingState.preferTarget;
        if (preferTarget && typeof preferTarget.provider === 'string' && preferTarget.provider.trim()) {
            const providerId = preferTarget.provider.trim();
            const keyAlias = typeof preferTarget.keyAlias === 'string' ? preferTarget.keyAlias.trim() : '';
            const modelId = typeof preferTarget.model === 'string' ? preferTarget.model.trim() : '';
            const keyIndex = typeof preferTarget.keyIndex === 'number' && Number.isFinite(preferTarget.keyIndex)
                ? Math.floor(preferTarget.keyIndex)
                : undefined;
            const candidateKeys = [];
            if (keyIndex !== undefined && keyIndex > 0) {
                const runtimeKey = engine.providerRegistry.resolveRuntimeKeyByIndex(providerId, keyIndex);
                if (runtimeKey) {
                    candidateKeys.push(runtimeKey);
                }
            }
            else if (modelId) {
                const allKeys = engine.providerRegistry.listProviderKeys(providerId);
                for (const key of allKeys) {
                    if (keyAlias) {
                        const prefix = `${providerId}.${keyAlias}.`;
                        if (!key.startsWith(prefix)) {
                            continue;
                        }
                    }
                    try {
                        const profile = engine.providerRegistry.get(key);
                        if (profile?.modelId === modelId) {
                            candidateKeys.push(key);
                        }
                    }
                    catch {
                        continue;
                    }
                }
            }
            const allowAliasRotation = !keyAlias && keyIndex === undefined;
            const eligibleKeys = (() => {
                if (candidateKeys.length === 0) {
                    return [];
                }
                const quotaView = selectionDeps.quotaView;
                const now = quotaView ? Date.now() : 0;
                return candidateKeys.filter((key) => {
                    if (!quotaView) {
                        if (engine.isProviderCoolingDown(key)) {
                            return false;
                        }
                        if (!engine.healthManager.isAvailable(key)) {
                            return false;
                        }
                        return true;
                    }
                    const entry = quotaView(key);
                    if (!entry) {
                        return true;
                    }
                    if (!entry.inPool) {
                        return false;
                    }
                    if (entry.cooldownUntil && entry.cooldownUntil > now) {
                        return false;
                    }
                    if (entry.blacklistUntil && entry.blacklistUntil > now) {
                        return false;
                    }
                    return true;
                });
            })();
            const preferSelection = eligibleKeys.length > 0
                ? selectFromStickyPoolImpl(new Set(eligibleKeys), metadata, features, routingState, selectionDeps, {
                    allowAliasRotation
                })
                : null;
            if (preferSelection) {
                classification = {
                    routeName: 'prefer',
                    confidence: 1,
                    reasoning: keyIndex !== undefined ? `prefer_key:${providerId}.${keyIndex}` : `prefer_model:${providerId}.${modelId}`,
                    fallback: false,
                    candidates: ['prefer']
                };
                requestedRoute = 'prefer';
                selection = {
                    ...preferSelection,
                    routeUsed: 'prefer',
                    poolId: 'prefer-primary'
                };
            }
            else if (routingState.preferTarget) {
                // Auto-clear only when the target becomes invalid or blocked by explicit routing instructions.
                // Do NOT clear for temporary unavailability (e.g. 429 cooldown, quota cooldown, transient health).
                const shouldAutoClear = (() => {
                    if (candidateKeys.length === 0) {
                        return true;
                    }
                    // Prefer selection failed despite eligible keys existing: treat as a hard block (e.g. routing rules).
                    if (eligibleKeys.length > 0) {
                        return true;
                    }
                    // If quota explicitly marks the preferred target as out-of-pool, clear the prefer instruction so
                    // the router can fall back to other targets without repeatedly retrying an impossible preference.
                    if (selectionDeps.quotaView) {
                        for (const key of candidateKeys) {
                            const entry = selectionDeps.quotaView(key);
                            if (entry && entry.inPool === false) {
                                return true;
                            }
                        }
                    }
                    return false;
                })();
                if (shouldAutoClear) {
                    routingState = {
                        ...routingState,
                        preferTarget: undefined
                    };
                    engine.routingInstructionState.set(stateKey, routingState);
                    persistRoutingInstructionState(stateKey, routingState, engine.routingStateStore);
                }
            }
        }
        if (!selection) {
            classification = metadata.routeHint && metadata.routeHint.trim()
                ? {
                    routeName: metadata.routeHint.trim(),
                    confidence: 1,
                    reasoning: `route_hint:${metadata.routeHint.trim()}`,
                    fallback: false,
                    candidates: [metadata.routeHint.trim()]
                }
                : engine.classifier.classify(features);
            requestedRoute = engine.normalizeRouteAlias(classification.routeName || DEFAULT_ROUTE);
            selection = engine.selectProvider(requestedRoute, metadata, classification, features, routingState);
        }
    }
    return {
        routingState,
        requestedRoute,
        classification: classification,
        selection: selection,
        features
    };
}
