import { GATEWAY_ROUTABLE_PROVIDERS_SET } from "../constants/models.js";
import { getLogger } from "./logger.js";
import { createMockModel } from "./mock-provider.js";
import { gatewayCostMiddleware } from "./usage-cost-middleware.js";
import { wrapLanguageModel } from "ai";
import { anthropic, createAnthropic } from "@ai-sdk/anthropic";
import { createAzure } from "@ai-sdk/azure";
import { createGateway, gateway } from "@ai-sdk/gateway";
import { createGoogleGenerativeAI, google } from "@ai-sdk/google";
import { createOpenAI, openai } from "@ai-sdk/openai";
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
import { createOpenRouter, openrouter } from "@openrouter/ai-sdk-provider";

//#region src/utils/model-factory.ts
const logger = getLogger("ModelFactory");
const nimDefault = createOpenAICompatible({
	name: "nim",
	baseURL: "https://integrate.api.nvidia.com/v1",
	headers: { Authorization: `Bearer ${process.env.NIM_API_KEY}` }
});
/**
* Factory for creating AI SDK language models from configuration
* Supports multiple providers and AI Gateway integration
*/
var ModelFactory = class ModelFactory {
	/**
	* Create a provider instance with custom configuration
	* Returns a provider with at least languageModel method
	*/
	static createProvider(provider, config) {
		switch (provider) {
			case "anthropic": return createAnthropic(config);
			case "azure":
				if (!config.resourceName && !config.baseURL) {
					const errorMessage = !!process.env.AZURE_OPENAI_API_KEY ? "Azure provider requires either resourceName or baseURL in provider options. Provide resourceName for standard Azure OpenAI, or baseURL for custom endpoints." : "Azure provider requires either resourceName or baseURL in provider options, and AZURE_OPENAI_API_KEY environment variable must be set. Provide resourceName for standard Azure OpenAI, or baseURL for custom endpoints.";
					throw new Error(errorMessage);
				}
				return createAzure(config);
			case "openai": return createOpenAI(config);
			case "google": return createGoogleGenerativeAI(config);
			case "openrouter": return createOpenRouter(config);
			case "gateway": return createGateway(config);
			case "nim": return createOpenAICompatible({
				name: "nim",
				baseURL: "https://integrate.api.nvidia.com/v1",
				headers: { Authorization: `Bearer ${process.env.NIM_API_KEY}` },
				...config
			});
			case "custom": {
				if (!config.baseURL && !config.baseUrl) throw new Error("Custom provider requires baseURL. Please provide it in providerOptions.baseURL or providerOptions.baseUrl");
				const customConfig = {
					name: "custom",
					baseURL: config.baseURL || config.baseUrl,
					headers: {
						...process.env.CUSTOM_LLM_API_KEY && { Authorization: `Bearer ${process.env.CUSTOM_LLM_API_KEY}` },
						...config.headers || {}
					},
					...config
				};
				logger.info({ config: {
					baseURL: customConfig.baseURL,
					hasApiKey: !!process.env.CUSTOM_LLM_API_KEY,
					apiKeyPrefix: `${process.env.CUSTOM_LLM_API_KEY?.substring(0, 10)}...`,
					headers: Object.keys(customConfig.headers || {})
				} }, "Creating custom OpenAI-compatible provider");
				return createOpenAICompatible(customConfig);
			}
			default: throw new Error(`Unsupported provider: ${provider}`);
		}
	}
	/**
	* Extract provider configuration from providerOptions
	* Only includes settings that go to the provider constructor (baseURL, headers, etc.)
	*/
	static extractProviderConfig(providerOptions) {
		if (!providerOptions) return {};
		const providerConfig = {};
		if (providerOptions.baseUrl || providerOptions.baseURL) providerConfig.baseURL = providerOptions.baseUrl || providerOptions.baseURL;
		if (providerOptions.headers) providerConfig.headers = providerOptions.headers;
		if (providerOptions.resourceName) providerConfig.resourceName = providerOptions.resourceName;
		if (providerOptions.apiVersion) providerConfig.apiVersion = providerOptions.apiVersion;
		return providerConfig;
	}
	/**
	* Extract per-call provider options to pass as providerOptions in streamText/generateText.
	* Any object-valued key (except constructor config keys like headers) is treated as
	* a provider-specific per-call option, e.g. anthropic.thinking, gateway.models.
	*/
	static extractStreamProviderOptions(providerOptions) {
		if (!providerOptions) return;
		const constructorObjectKeys = new Set(["headers"]);
		const result = {};
		for (const [key, value] of Object.entries(providerOptions)) if (value !== null && typeof value === "object" && !Array.isArray(value) && !constructorObjectKeys.has(key)) result[key] = value;
		return Object.keys(result).length > 0 ? result : void 0;
	}
	/**
	* Whether the given model settings will route through the Vercel AI Gateway
	* vs. instantiating a direct provider client. Mirrors the gate used inside
	* createModel so callers (e.g. the prompt-caching actuator) can detect mode
	* without recreating the model instance.
	*/
	static shouldRouteViaGateway(modelSettings) {
		const modelString = modelSettings.model?.trim();
		if (!modelString) return false;
		const { provider } = ModelFactory.parseModelString(modelString);
		const providerConfig = ModelFactory.extractProviderConfig(modelSettings.providerOptions);
		return !!process.env.AI_GATEWAY_API_KEY && GATEWAY_ROUTABLE_PROVIDERS_SET.has(provider) && Object.keys(providerConfig).length === 0;
	}
	/**
	* Create a language model instance from configuration
	* Throws error if no config provided - models must be configured at project level
	*/
	static createModel(config) {
		if (!config?.model?.trim()) throw new Error("Model configuration is required. Please configure models at the project level.");
		const modelSettings = config;
		if (!modelSettings.model) throw new Error("Model configuration is required");
		const modelString = modelSettings.model.trim();
		const { provider, modelName } = ModelFactory.parseModelString(modelString);
		logger.debug({
			provider,
			model: modelName,
			fullModelString: modelSettings.model,
			hasProviderOptions: !!modelSettings.providerOptions
		}, "Creating language model from config");
		const providerConfig = ModelFactory.extractProviderConfig(modelSettings.providerOptions);
		const routeViaGateway = ModelFactory.shouldRouteViaGateway({
			model: modelSettings.model,
			providerOptions: modelSettings.providerOptions
		});
		let model;
		if (routeViaGateway) model = gateway(!!modelSettings.allowedProviders?.length ? modelName : `${provider}/${modelName}`);
		else if (provider !== "mock" && (provider === "azure" || Object.keys(providerConfig).length > 0)) {
			logger.info({ config: providerConfig }, `Applying custom ${provider} provider configuration`);
			model = ModelFactory.createProvider(provider, providerConfig).languageModel(modelName);
		} else switch (provider) {
			case "anthropic":
				model = anthropic(modelName);
				break;
			case "openai":
				model = openai(modelName);
				break;
			case "google":
				model = google(modelName);
				break;
			case "openrouter":
				model = openrouter(modelName);
				break;
			case "gateway":
				model = gateway(modelName);
				break;
			case "nim":
				model = nimDefault(modelName);
				break;
			case "mock": return createMockModel(modelName);
			case "custom": throw new Error("Custom provider requires configuration. Please provide baseURL in providerOptions.baseURL");
			default: throw new Error(`Unsupported provider: ${provider}. Supported providers are: ${ModelFactory.BUILT_IN_PROVIDERS.join(", ")}. To access other models, use OpenRouter (openrouter/model-id), Vercel AI Gateway (gateway/model-id), NVIDIA NIM (nim/model-id), or Custom OpenAI-compatible (custom/model-id).`);
		}
		return wrapLanguageModel({
			model,
			middleware: gatewayCostMiddleware
		});
	}
	/**
	* Built-in providers that have special handling
	*/
	static BUILT_IN_PROVIDERS = [
		"anthropic",
		"azure",
		"openai",
		"google",
		"openrouter",
		"gateway",
		"nim",
		"custom",
		"mock"
	];
	/**
	* Parse model string to extract provider and model name
	* Examples: "anthropic/claude-sonnet-4" -> { provider: "anthropic", modelName: "claude-sonnet-4" }
	*          "openrouter/anthropic/claude-sonnet-4" -> { provider: "openrouter", modelName: "anthropic/claude-sonnet-4" }
	*          "claude-sonnet-4" -> { provider: "anthropic", modelName: "claude-sonnet-4" } (default to anthropic)
	*/
	static parseModelString(modelString) {
		if (modelString.includes("/")) {
			const [provider, ...modelParts] = modelString.split("/");
			const normalizedProvider = provider.toLowerCase();
			if (!ModelFactory.BUILT_IN_PROVIDERS.includes(normalizedProvider)) throw new Error(`Unsupported provider: ${normalizedProvider}. Supported providers are: ${ModelFactory.BUILT_IN_PROVIDERS.join(", ")}. To access other models, use OpenRouter (openrouter/model-id), Vercel AI Gateway (gateway/model-id), NVIDIA NIM (nim/model-id), or Custom OpenAI-compatible (custom/model-id).`);
			return {
				provider: normalizedProvider,
				modelName: modelParts.join("/")
			};
		}
		throw new Error(`No provider specified in model string: ${modelString}`);
	}
	/**
	* Get generation parameters from provider options
	* These are parameters that get passed to generateText/streamText calls
	*/
	static getGenerationParams(providerOptions) {
		if (!providerOptions) return {};
		const excludedKeys = new Set([
			"apiKey",
			"baseURL",
			"baseUrl",
			"resourceName",
			"apiVersion",
			"maxDuration",
			"headers"
		]);
		const params = {};
		for (const [key, value] of Object.entries(providerOptions)) if (!excludedKeys.has(key) && value !== void 0 && typeof value !== "object") params[key] = value;
		return params;
	}
	/**
	* Prepare complete generation configuration from model settings
	* Returns model instance and generation parameters ready to spread into generateText/streamText
	* Includes maxDuration if specified in provider options (in seconds, following Vercel standard)
	*/
	static prepareGenerationConfig(modelSettings) {
		const modelString = modelSettings?.model?.trim();
		const model = ModelFactory.createModel({
			model: modelString,
			providerOptions: modelSettings?.providerOptions,
			allowedProviders: modelSettings?.allowedProviders
		});
		const generationParams = ModelFactory.getGenerationParams(modelSettings?.providerOptions);
		let streamProviderOptions = ModelFactory.extractStreamProviderOptions(modelSettings?.providerOptions);
		const maxDuration = modelSettings?.providerOptions?.maxDuration;
		if (process.env.AI_GATEWAY_API_KEY) {
			const hasAllowedProviders = !!modelSettings?.allowedProviders?.length;
			const hasFallbackModels = !!modelSettings?.fallbackModels?.length;
			if (hasAllowedProviders || hasFallbackModels) {
				const existingGateway = streamProviderOptions?.gateway ?? {};
				streamProviderOptions = {
					...streamProviderOptions,
					gateway: {
						...existingGateway,
						...hasFallbackModels && { models: modelSettings.fallbackModels },
						...hasAllowedProviders && {
							order: modelSettings.allowedProviders,
							only: modelSettings.allowedProviders
						}
					}
				};
			}
		}
		return {
			model,
			...generationParams,
			...maxDuration !== void 0 && { maxDuration },
			...streamProviderOptions !== void 0 && { providerOptions: streamProviderOptions }
		};
	}
	/**
	* Validate model settingsuration
	* Basic validation only - let AI SDK handle parameter-specific validation
	*/
	static validateConfig(config) {
		const errors = [];
		if (!config.model) errors.push("Model name is required");
		if (config.providerOptions) {
			if (config.providerOptions.apiKey) errors.push("API keys should not be stored in provider options. Use environment variables (ANTHROPIC_API_KEY, OPENAI_API_KEY) or credential store instead.");
			if (config.providerOptions.maxDuration !== void 0) {
				const maxDuration = config.providerOptions.maxDuration;
				if (typeof maxDuration !== "number" || maxDuration <= 0) errors.push("maxDuration must be a positive number (in seconds)");
			}
		}
		return errors;
	}
};

//#endregion
export { ModelFactory };