import { __esmMin } from "../../_virtual/_rolldown/runtime.js";
import { createLogger } from "../utils/logger/index.js";
import { init_logger } from "../utils/logger.js";
import { getApiKeyFromEnv, init_env_keys } from "./env-keys.js";
import { resolveModelsJsonPath } from "../config/paths.js";
import { init_resolve_config_value, resolveConfigValue, resolveHeaders } from "../config/resolve-config-value.js";
import { getDefaultModelValues, init_models_json, validateModelsConfig } from "../config/models-json.js";
import { existsSync, readFileSync } from "fs";
import { getModels, getProviders } from "@earendil-works/pi-ai";
//#region src/providers/model-registry.ts
/**
* Model Registry - manages built-in and custom models from models.json
* 
* Features:
* - Loads built-in models from @earendil-works/pi-ai
* - Loads custom models from ~/.xopc/models.json
* - Supports provider baseUrl/headers overrides
* - Supports per-model overrides (modelOverrides)
* - API key resolution with shell commands, env vars, and literals
* - Hot reload support
*/
function emptyCustomModelsResult(error) {
	return {
		models: [],
		overrides: /* @__PURE__ */ new Map(),
		modelOverrides: /* @__PURE__ */ new Map(),
		apiKeyConfigs: /* @__PURE__ */ new Map(),
		error
	};
}
/**
* Deep merge compat settings
*/
function mergeCompat(baseCompat, overrideCompat) {
	if (!overrideCompat) return baseCompat;
	const base = baseCompat;
	const override = overrideCompat;
	const merged = {
		...base,
		...override
	};
	const baseCompletions = base;
	const overrideCompletions = override;
	if (baseCompletions?.openRouterRouting || overrideCompletions?.openRouterRouting) merged.openRouterRouting = {
		...baseCompletions?.openRouterRouting || {},
		...overrideCompletions?.openRouterRouting || {}
	};
	if (baseCompletions?.vercelGatewayRouting || overrideCompletions?.vercelGatewayRouting) merged.vercelGatewayRouting = {
		...baseCompletions?.vercelGatewayRouting || {},
		...overrideCompletions?.vercelGatewayRouting || {}
	};
	return merged;
}
/**
* Apply a model override to a model
*/
function applyModelOverride(model, override) {
	const result = { ...model };
	if (override.name !== void 0) result.name = override.name;
	if (override.reasoning !== void 0) result.reasoning = override.reasoning;
	if (override.input !== void 0) result.input = override.input;
	if (override.contextWindow !== void 0) result.contextWindow = override.contextWindow;
	if (override.maxTokens !== void 0) result.maxTokens = override.maxTokens;
	if (override.cost) result.cost = {
		input: override.cost.input ?? model.cost.input,
		output: override.cost.output ?? model.cost.output,
		cacheRead: override.cost.cacheRead ?? model.cost.cacheRead,
		cacheWrite: override.cost.cacheWrite ?? model.cost.cacheWrite
	};
	if (override.headers) {
		const resolvedHeaders = resolveHeaders(override.headers);
		result.headers = resolvedHeaders ? {
			...model.headers,
			...resolvedHeaders
		} : model.headers;
	}
	result.compat = mergeCompat(model.compat, override.compat);
	return result;
}
function getModelRegistry() {
	if (!globalRegistry) globalRegistry = new ModelRegistry();
	return globalRegistry;
}
function resetModelRegistry() {
	globalRegistry = void 0;
}
/** Schedule catalog load on a background turn (gateway post-ready sidecar). */
function prewarmModelRegistry() {
	return getModelRegistry().prewarm();
}
var log, ModelRegistry, globalRegistry;
var init_model_registry = __esmMin((() => {
	init_resolve_config_value();
	init_models_json();
	init_logger();
	init_env_keys();
	log = createLogger("ModelRegistry");
	ModelRegistry = class {
		models = [];
		customProviderApiKeys = /* @__PURE__ */ new Map();
		loadError = void 0;
		loaded = false;
		prewarmPromise = null;
		_availableModelsCache;
		_providersCache;
		_authStatusCache = /* @__PURE__ */ new Map();
		constructor(modelsJsonPath = resolveModelsJsonPath()) {
			this.modelsJsonPath = modelsJsonPath;
		}
		isLoaded() {
			return this.loaded;
		}
		/** Synchronously materialize the catalog (idempotent). */
		ensureLoaded() {
			if (this.loaded) return;
			this.loadModels();
			this.loaded = true;
		}
		/** Background-friendly catalog load (idempotent). */
		prewarm() {
			if (this.loaded) return Promise.resolve();
			if (this.prewarmPromise) return this.prewarmPromise;
			this.prewarmPromise = Promise.resolve().then(() => {
				this.ensureLoaded();
			});
			return this.prewarmPromise;
		}
		/**
		* Reload models from disk (built-in + custom from models.json)
		* Clears all caches
		*/
		refresh() {
			this.customProviderApiKeys.clear();
			this.loadError = void 0;
			this._availableModelsCache = void 0;
			this._providersCache = void 0;
			this._authStatusCache.clear();
			this.prewarmPromise = null;
			this.loadModels();
			this.loaded = true;
			log.info("Model registry refreshed");
		}
		/**
		* Get any error from loading models.json (undefined if no error)
		*/
		getError() {
			return this.loadError;
		}
		/**
		* Get all models (built-in + custom)
		* Results are cached until refresh() is called
		*/
		getAll() {
			this.ensureLoaded();
			return this.models;
		}
		/**
		* Get only models that have auth configured
		* Results are cached on first call until refresh()
		*/
		getAvailable() {
			this.ensureLoaded();
			if (!this._availableModelsCache) this._availableModelsCache = this.models.filter((m) => this.hasAuth(m.provider));
			return this._availableModelsCache;
		}
		/**
		* Find a model by provider and ID
		*/
		find(provider, modelId) {
			this.ensureLoaded();
			return this.models.find((m) => m.provider === provider && m.id === modelId);
		}
		/**
		* Resolve a model reference (provider/modelId or just modelId)
		*/
		resolve(ref) {
			this.ensureLoaded();
			if (ref.includes("/")) {
				const [provider, modelId] = ref.split("/");
				return this.find(provider, modelId);
			}
			for (const model of this.models) if (model.id === ref) return model;
		}
		/**
		* Get API key for a provider (for custom providers from models.json)
		*/
		getApiKey(provider) {
			this.ensureLoaded();
			const keyConfig = this.customProviderApiKeys.get(provider);
			if (keyConfig) return resolveConfigValue(keyConfig);
		}
		/**
		* Check if a provider has auth configured
		* Results are cached until refresh() is called
		*/
		hasAuth(provider) {
			if (this._authStatusCache.has(provider)) return this._authStatusCache.get(provider);
			let result;
			if (this.customProviderApiKeys.has(provider)) result = !!this.getApiKey(provider);
			else result = !!getApiKeyFromEnv(provider);
			this._authStatusCache.set(provider, result);
			return result;
		}
		/**
		* Load all models
		* Clears caches to ensure fresh data
		*/
		loadModels() {
			this._availableModelsCache = void 0;
			this._providersCache = void 0;
			this._authStatusCache.clear();
			const { models: customModels, overrides, modelOverrides, apiKeyConfigs, error } = this.loadCustomModels();
			if (error) {
				this.loadError = error;
				log.warn({ error }, "Failed to load models.json, using built-in models only");
			}
			this.customProviderApiKeys = apiKeyConfigs;
			const builtInModels = this.loadBuiltInModels(overrides, modelOverrides);
			this.models = this.mergeCustomModels(builtInModels, customModels);
			log.debug({
				builtIn: builtInModels.length,
				custom: customModels.length,
				total: this.models.length
			}, "Models loaded");
		}
		/**
		* Load built-in models and apply provider/model overrides
		*/
		loadBuiltInModels(overrides, modelOverrides) {
			return getProviders().flatMap((provider) => {
				try {
					const models = getModels(provider);
					const providerOverride = overrides.get(provider);
					const perModelOverrides = modelOverrides.get(provider);
					return models.map((m) => {
						let model = m;
						if (providerOverride) {
							const resolvedHeaders = resolveHeaders(providerOverride.headers);
							model = {
								...model,
								baseUrl: providerOverride.baseUrl ?? model.baseUrl,
								headers: resolvedHeaders ? {
									...model.headers,
									...resolvedHeaders
								} : model.headers
							};
						}
						const modelOverride = perModelOverrides?.get(m.id);
						if (modelOverride) model = applyModelOverride(model, modelOverride);
						return model;
					});
				} catch (err) {
					const errorMsg = err instanceof Error ? err.message : String(err);
					log.error({
						provider,
						error: errorMsg
					}, "Failed to load built-in models for provider");
					return [];
				}
			});
		}
		/**
		* Merge custom models into built-in list (custom wins on conflicts)
		*/
		mergeCustomModels(builtInModels, customModels) {
			const merged = [...builtInModels];
			for (const customModel of customModels) {
				const existingIndex = merged.findIndex((m) => m.provider === customModel.provider && m.id === customModel.id);
				if (existingIndex >= 0) merged[existingIndex] = customModel;
				else merged.push(customModel);
			}
			return merged;
		}
		/**
		* Load custom models from models.json
		*/
		loadCustomModels() {
			if (!existsSync(this.modelsJsonPath)) return emptyCustomModelsResult();
			try {
				const content = readFileSync(this.modelsJsonPath, "utf-8");
				const config = JSON.parse(content);
				const validation = validateModelsConfig(config);
				if (!validation.valid) return emptyCustomModelsResult(`Invalid models.json schema:\n${validation.errors.map((e) => `  - ${e.path}: ${e.message}`).join("\n")}\n\nFile: ${this.modelsJsonPath}`);
				const overrides = /* @__PURE__ */ new Map();
				const modelOverrides = /* @__PURE__ */ new Map();
				const apiKeyConfigs = /* @__PURE__ */ new Map();
				for (const [providerName, providerConfig] of Object.entries(config.providers)) {
					if (providerConfig.baseUrl || providerConfig.headers || providerConfig.apiKey) overrides.set(providerName, {
						baseUrl: providerConfig.baseUrl,
						headers: providerConfig.headers,
						apiKey: providerConfig.apiKey
					});
					if (providerConfig.apiKey) apiKeyConfigs.set(providerName, providerConfig.apiKey);
					if (providerConfig.modelOverrides) modelOverrides.set(providerName, new Map(Object.entries(providerConfig.modelOverrides)));
				}
				return {
					models: this.parseModels(config),
					overrides,
					modelOverrides,
					apiKeyConfigs,
					error: void 0
				};
			} catch (error) {
				if (error instanceof SyntaxError) return emptyCustomModelsResult(`Failed to parse models.json: ${error.message}\n\nFile: ${this.modelsJsonPath}`);
				return emptyCustomModelsResult(`Failed to load models.json: ${error instanceof Error ? error.message : error}\n\nFile: ${this.modelsJsonPath}`);
			}
		}
		/**
		* Parse custom models from config
		*/
		parseModels(config) {
			const models = [];
			const defaults = getDefaultModelValues();
			for (const [providerName, providerConfig] of Object.entries(config.providers)) {
				const modelDefs = providerConfig.models ?? [];
				if (modelDefs.length === 0) continue;
				for (const modelDef of modelDefs) {
					const api = modelDef.api || providerConfig.api;
					if (!api) continue;
					const providerHeaders = resolveHeaders(providerConfig.headers);
					const modelHeaders = resolveHeaders(modelDef.headers);
					let headers = providerHeaders || modelHeaders ? {
						...providerHeaders,
						...modelHeaders
					} : void 0;
					if (providerConfig.authHeader && providerConfig.apiKey) {
						const resolvedKey = resolveConfigValue(providerConfig.apiKey);
						if (resolvedKey) headers = {
							...headers,
							Authorization: `Bearer ${resolvedKey}`
						};
					}
					models.push({
						id: modelDef.id,
						name: modelDef.name ?? modelDef.id,
						api,
						provider: providerName,
						baseUrl: providerConfig.baseUrl,
						reasoning: modelDef.reasoning ?? false,
						input: modelDef.input ?? defaults.input,
						cost: modelDef.cost ?? defaults.cost,
						contextWindow: modelDef.contextWindow ?? defaults.contextWindow,
						maxTokens: modelDef.maxTokens ?? defaults.maxTokens,
						headers,
						compat: modelDef.compat
					});
				}
			}
			return models;
		}
	};
}));
//#endregion
init_model_registry();
export { ModelRegistry, getModelRegistry, init_model_registry, prewarmModelRegistry, resetModelRegistry };

//# sourceMappingURL=model-registry.js.map