import { resolveModelId } from "./auto-model-selection.js";
/**
 * Build the stable identity key for a concrete provider route.
 *
 * Purpose: make fallback recovery compare full provider/model routes instead of
 * ambiguous bare model ids.
 *
 * Consumer: resolveNextConfiguredModelRoute() when skipping failed and current
 * runtime routes.
 */
export function modelRouteKey(route) {
	return `${route.provider.toLowerCase()}/${route.id.toLowerCase()}`;
}
function dedupeConfiguredRoutes(modelConfig) {
	const seen = new Set();
	const routes = [];
	for (const route of [modelConfig.primary, ...modelConfig.fallbacks]) {
		const key = route.toLowerCase();
		if (seen.has(key)) continue;
		seen.add(key);
		routes.push(route);
	}
	return routes;
}
/**
 * Resolve the next configured model route after a provider/model failure.
 *
 * Purpose: keep autonomous mode recovery inside the user's explicit primary/fallback
 * chain, skip routes already failed for this unit, and avoid returning the same
 * provider/model again.
 *
 * Consumer: bootstrap/agent-end-recovery.ts when a provider returns quota,
 * rate-limit, server, stream, or connection failures during a unit.
 */
export function resolveNextConfiguredModelRoute(args) {
	const routes = dedupeConfiguredRoutes(args.modelConfig);
	const currentKey = args.current ? modelRouteKey(args.current) : undefined;
	const failedKeys = new Set(
		args.failedRoutes.map((failure) =>
			modelRouteKey({ provider: failure.provider, id: failure.modelId }),
		),
	);
	const resolvedRoutes = routes.map((configuredRoute) => ({
		configuredRoute,
		model: resolveModelId(
			configuredRoute,
			args.availableModels,
			args.current?.provider,
		),
	}));
	const currentIndex =
		currentKey === undefined
			? -1
			: resolvedRoutes.findIndex(
					(route) => route.model && modelRouteKey(route.model) === currentKey,
				);
	const candidates =
		currentIndex >= 0 ? resolvedRoutes.slice(currentIndex + 1) : resolvedRoutes;
	for (const candidate of candidates) {
		if (!candidate.model) continue;
		const candidateKey = modelRouteKey(candidate.model);
		if (candidateKey === currentKey) continue;
		if (failedKeys.has(candidateKey)) continue;
		if (args.isBlocked?.(candidate.model)) continue;
		return {
			model: candidate.model,
			route: candidate.configuredRoute,
			source: "configured",
		};
	}
	return undefined;
}
/**
 * Resolve another currently available provider/model route when configured
 * fallbacks are missing or exhausted.
 *
 * Purpose: keep autonomous mode moving on quota/rate-limit/server failures instead
 * of pausing just because the configured fallback chain did not cover every
 * live provider route.
 *
 * Consumer: bootstrap/agent-end-recovery.ts after configured fallback lookup
 * fails for a model-route failure.
 */
export function resolveNextAvailableModelRoute(args) {
	const currentKey = args.current ? modelRouteKey(args.current) : undefined;
	const failedKeys = new Set(
		args.failedRoutes.map((failure) =>
			modelRouteKey({ provider: failure.provider, id: failure.modelId }),
		),
	);
	const candidates = args.availableModels.filter((model) => {
		const key = modelRouteKey(model);
		if (key === currentKey) return false;
		if (failedKeys.has(key)) return false;
		if (args.isBlocked?.(model)) return false;
		return true;
	});
	if (candidates.length === 0) return undefined;
	const differentProvider =
		args.current &&
		candidates.find(
			(model) =>
				model.provider.toLowerCase() !== args.current.provider.toLowerCase(),
		);
	const model = differentProvider ?? candidates[0];
	return {
		model,
		route: `${model.provider}/${model.id}`,
		source: "available",
	};
}
/**
 * Resolve the next model route by trying configured policy first, then any
 * other live route.
 *
 * Purpose: preserve configured fallback ordering when it exists while still
 * enforcing the no-pause contract for transient provider/model failures.
 *
 * Consumer: bootstrap/agent-end-recovery.ts during provider-route recovery.
 */
export function resolveNextModelRoute(args) {
	if (args.modelConfig) {
		const configured = resolveNextConfiguredModelRoute({
			current: args.current,
			modelConfig: args.modelConfig,
			availableModels: args.availableModels,
			failedRoutes: args.failedRoutes,
			isBlocked: args.isBlocked,
		});
		if (configured) return configured;
	}
	return resolveNextAvailableModelRoute({
		current: args.current,
		availableModels: args.availableModels,
		failedRoutes: args.failedRoutes,
		isBlocked: args.isBlocked,
	});
}
