import { FailoverError, classifyAttemptError } from "../failover-error.js";
import { parseCapabilityModelRef } from "./model-ref.js";
//#region src/agent/media-generation/runtime-shared.ts
/**
* Build the ordered candidate list from:
*   1. modelOverride
*   2. modelConfig.primary
*   3. modelConfig.fallbacks[]
*   4. (optional) every isConfigured() provider's defaultModel
*
* Duplicates are dropped (case-insensitive on provider, exact on model).
*/
function resolveCapabilityModelCandidates(params) {
	const parse = params.parseModelRef ?? parseCapabilityModelRef;
	const out = [];
	const seen = /* @__PURE__ */ new Set();
	const push = (raw) => {
		const parsed = parse(raw);
		if (!parsed) return;
		const key = `${parsed.provider}::${parsed.model}`;
		if (seen.has(key)) return;
		seen.add(key);
		out.push({
			provider: parsed.provider,
			model: parsed.model
		});
	};
	const normalizedModelConfig = normalizeModelConfig(params.modelConfig);
	push(params.modelOverride);
	push(normalizedModelConfig.primary);
	for (const fb of normalizedModelConfig.fallbacks) push(fb);
	if (params.autoProviderFallback) {
		const providers = safeListProviders(params.listProviders, params.cfg);
		for (const provider of providers) {
			if (!provider.defaultModel) continue;
			let configured = true;
			try {
				configured = provider.isConfigured?.({
					cfg: params.cfg,
					agentDir: params.agentDir
				}) ?? true;
			} catch {
				configured = false;
			}
			if (!configured) continue;
			push(`${provider.id}/${provider.defaultModel}`);
		}
	}
	return out;
}
function safeListProviders(fn, cfg) {
	try {
		return fn(cfg) ?? [];
	} catch {
		return [];
	}
}
/** Normalize an {@link AgentModelConfig} to `{ primary?, fallbacks }`. */
function normalizeModelConfig(cfg) {
	if (!cfg) return { fallbacks: [] };
	return {
		primary: typeof cfg.primary === "string" ? cfg.primary : void 0,
		fallbacks: Array.isArray(cfg.fallbacks) ? cfg.fallbacks.filter((s) => typeof s === "string") : []
	};
}
function resolveClosestSize(params) {
	const supported = (params.supportedSizes ?? []).filter((s) => parseSize(s) !== null);
	if (supported.length === 0) return void 0;
	const target = parseSize(params.requestedSize) ?? sizeFromAspectRatio(params.requestedAspectRatio, supported.map((s) => parseSize(s).totalPixels));
	if (!target) return supported[0];
	return supported.map((s) => ({
		s,
		parsed: parseSize(s)
	})).reduce((best, cur) => {
		const score = sizeDistance(target, cur.parsed);
		return score < best.score ? {
			s: cur.s,
			score
		} : best;
	}, {
		s: supported[0],
		score: Number.POSITIVE_INFINITY
	}).s;
}
function resolveClosestAspectRatio(params) {
	const supported = (params.supportedAspectRatios ?? []).filter((r) => parseAspectRatio(r) !== null);
	if (supported.length === 0) return void 0;
	const targetRatio = parseAspectRatio(params.requestedAspectRatio)?.value ?? (() => {
		const sz = parseSize(params.requestedSize);
		return sz ? sz.width / sz.height : void 0;
	})();
	if (typeof targetRatio !== "number" || !Number.isFinite(targetRatio)) return supported[0];
	return supported.reduce((best, cur) => {
		const v = parseAspectRatio(cur).value;
		const d = Math.abs(Math.log(v) - Math.log(targetRatio));
		return d < best.score ? {
			s: cur,
			score: d
		} : best;
	}, {
		s: supported[0],
		score: Number.POSITIVE_INFINITY
	}).s;
}
function resolveClosestResolution(params) {
	const supported = params.supportedResolutions ?? [];
	if (supported.length === 0) return void 0;
	const requested = params.requestedResolution;
	if (!requested) return void 0;
	if (supported.includes(requested)) return requested;
	const order = [
		"1K",
		"2K",
		"4K",
		"8K"
	];
	const want = order.indexOf(String(requested));
	if (want < 0) return supported[0];
	const ranked = supported.map((r) => ({
		r,
		idx: order.indexOf(String(r))
	})).filter((x) => x.idx >= 0).sort((a, b) => a.idx - b.idx);
	if (ranked.length === 0) return supported[0];
	let chosen = ranked[0].r;
	for (const x of ranked) if (x.idx <= want) chosen = x.r;
	return chosen;
}
function parseSize(raw) {
	if (typeof raw !== "string") return null;
	const m = raw.trim().toLowerCase().match(/^(\d{2,5})\s*[x×*]\s*(\d{2,5})$/);
	if (!m) return null;
	const width = Number(m[1]);
	const height = Number(m[2]);
	if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return null;
	return {
		width,
		height,
		totalPixels: width * height
	};
}
function parseAspectRatio(raw) {
	if (typeof raw !== "string") return null;
	const m = raw.trim().match(/^(\d{1,3})\s*[:x×/]\s*(\d{1,3})$/);
	if (!m) return null;
	const w = Number(m[1]);
	const h = Number(m[2]);
	if (!Number.isFinite(w) || !Number.isFinite(h) || w <= 0 || h <= 0) return null;
	return { value: w / h };
}
function sizeFromAspectRatio(ratioStr, pixelHints) {
	const r = parseAspectRatio(ratioStr);
	if (!r) return null;
	const pixels = pixelHints.length > 0 ? Math.round(pixelHints.reduce((a, b) => a + b, 0) / pixelHints.length) : 1024 * 1024;
	const height = Math.round(Math.sqrt(pixels / r.value));
	const width = Math.round(height * r.value);
	return {
		width,
		height,
		totalPixels: width * height
	};
}
function sizeDistance(a, b) {
	const areaDelta = Math.abs(Math.log(a.totalPixels) - Math.log(b.totalPixels));
	const ratioA = a.width / a.height;
	const ratioB = b.width / b.height;
	return areaDelta + Math.abs(Math.log(ratioA) - Math.log(ratioB)) * 1.5;
}
/** Push a structured {@link FallbackAttempt} entry derived from `error`. */
function recordCapabilityCandidateFailure(params) {
	const cls = classifyAttemptError(params.error);
	const attempt = {
		provider: params.provider,
		model: params.model,
		error: cls.message,
		reason: cls.reason,
		...cls.status !== void 0 ? { status: cls.status } : {},
		...cls.code !== void 0 ? { code: cls.code } : {},
		...params.durationMs !== void 0 ? { durationMs: params.durationMs } : {}
	};
	params.attempts.push(attempt);
	return attempt;
}
/** Throw a unified {@link FailoverError} after all candidates have been exhausted. */
function throwCapabilityGenerationFailure(params) {
	throw new FailoverError({
		capability: params.capabilityLabel.trim().toLowerCase().replace(/\s+/g, "-") || "capability",
		attempts: params.attempts,
		cause: params.lastError
	});
}
function buildNoCapabilityModelConfiguredMessage(params) {
	const lines = [`No ${params.capabilityLabel} model configured. Set agents.defaults.${params.modelConfigKey} to a "<provider>/<model>" value (or pass modelOverride at call site).`];
	if (params.providers.length > 0) {
		lines.push("Registered providers:");
		for (const p of params.providers) {
			const envs = params.getProviderEnvVars?.(p.id) ?? [];
			const env = envs.length > 0 ? ` (env: ${envs.join(" | ")})` : "";
			const def = p.defaultModel ? ` default=${p.defaultModel}` : "";
			lines.push(`  - ${p.id}${def}${env}`);
		}
	}
	return lines.join("\n");
}
/**
* Build a `metadata.normalization` snapshot from a completed normalization
* pass. Providers / runtime can spread it into their result metadata.
*/
function buildMediaGenerationNormalizationMetadata(params) {
	if (!params.normalization || Object.keys(params.normalization).length === 0) return {};
	return { normalization: params.normalization };
}
//#endregion
export { buildMediaGenerationNormalizationMetadata, buildNoCapabilityModelConfiguredMessage, recordCapabilityCandidateFailure, resolveCapabilityModelCandidates, resolveClosestAspectRatio, resolveClosestResolution, resolveClosestSize, throwCapabilityGenerationFailure };

//# sourceMappingURL=runtime-shared.js.map