import { logger } from "../utils/logger.js";
// Pure factory pattern with no hardcoded imports
// All providers loaded dynamically via registry to avoid circular dependencies
/**
 * True Factory Pattern implementation for AI Providers
 * Uses registration-based approach to eliminate switch statements
 * and enable dynamic provider registration
 */
export class ProviderFactory {
    static providers = new Map();
    static initialized = false;
    /**
     * Register a provider with the factory
     */
    static registerProvider(name, constructor, defaultModel, // Optional - provider can read from env
    aliases = []) {
        const registration = {
            constructor,
            defaultModel,
            aliases,
        };
        // Register main name
        ProviderFactory.providers.set(name.toLowerCase(), registration);
        // Register aliases
        aliases.forEach((alias) => {
            ProviderFactory.providers.set(alias.toLowerCase(), registration);
        });
        logger.debug(`Registered provider: ${name} with model ${defaultModel || "from-env"}`);
    }
    /**
     * Create a provider instance
     * @param providerName - Provider name (optional, uses NEUROLINK_PROVIDER env var or 'vertex' as default)
     * @param modelName - Model name (optional, uses provider-specific env var or registry default)
     */
    static async createProvider(providerName, modelName, sdk, region, credentials) {
        // Note: Providers are registered explicitly by ProviderRegistry to avoid circular dependencies
        // Use environment variable or default if not specified
        const resolvedProviderName = providerName ||
            process.env.NEUROLINK_PROVIDER ||
            process.env.AI_PROVIDER ||
            "vertex";
        const normalizedName = resolvedProviderName.toLowerCase();
        const registration = ProviderFactory.providers.get(normalizedName);
        if (!registration) {
            throw new Error(`Unknown provider: ${resolvedProviderName}. Available providers: ${ProviderFactory.getAvailableProviders().join(", ")}`);
        }
        // Respect environment variables before falling back to registry default
        let model = modelName;
        if (!model) {
            // Check for provider-specific environment variables
            if (resolvedProviderName.toLowerCase().includes("vertex")) {
                // Use gemini-2.5-flash as default - latest GA model with best price-performance
                model = process.env.VERTEX_MODEL || "gemini-2.5-flash";
            }
            else if (resolvedProviderName.toLowerCase().includes("bedrock")) {
                model = process.env.BEDROCK_MODEL || process.env.BEDROCK_MODEL_ID;
            }
            // Fallback to registry default if no env var
            model = model || registration.defaultModel;
        }
        // Map registered provider names to NeurolinkCredentials keys.
        // Most names match (openai, anthropic, vertex, bedrock, etc.)
        // but every kebab-case provider whose canonical credentials key is
        // camelCase MUST be mapped here — otherwise per-call credential
        // overrides silently get dropped (the factory looks up
        // credentials["lm-studio"] which is undefined while the user wrote
        // credentials.lmStudio).
        const credentialKeyMap = {
            "google-ai": "googleAiStudio",
            "openai-compatible": "openaiCompatible",
            huggingface: "huggingFace",
            "lm-studio": "lmStudio",
            "nvidia-nim": "nvidiaNim",
        };
        const credKey = credentialKeyMap[normalizedName] ?? normalizedName;
        // Extract provider-scoped credential slice (e.g. credentials.openai for OpenAI)
        const scopedCredentials = credentials
            ? credentials[credKey]
            : undefined;
        try {
            if (typeof registration.constructor !== "function") {
                throw new Error(`Invalid constructor for provider ${providerName}: not a function`);
            }
            let result;
            try {
                const factoryResult = registration.constructor(model, resolvedProviderName, sdk, region, scopedCredentials);
                // Handle both sync and async results
                result =
                    factoryResult instanceof Promise
                        ? await factoryResult
                        : factoryResult;
            }
            catch (factoryError) {
                if (registration.constructor.prototype &&
                    registration.constructor.prototype.constructor ===
                        registration.constructor) {
                    try {
                        result = new registration.constructor(model, resolvedProviderName, sdk, region, scopedCredentials);
                    }
                    catch (constructorError) {
                        throw new Error(`Both factory function and constructor failed. Factory error: ${factoryError}. Constructor error: ${constructorError}`, { cause: constructorError });
                    }
                }
                else {
                    throw factoryError;
                }
            }
            return result;
        }
        catch (error) {
            logger.error(`Failed to create provider ${resolvedProviderName}:`, error);
            throw new Error(`Failed to create provider ${resolvedProviderName}: ${error}`, { cause: error });
        }
    }
    /**
     * Check if a provider is registered
     */
    static hasProvider(providerName) {
        return ProviderFactory.providers.has(providerName.toLowerCase());
    }
    /**
     * Get list of available providers
     */
    static getAvailableProviders() {
        return Array.from(ProviderFactory.providers.keys()).filter((name, index, arr) => arr.indexOf(name) === index);
    }
    /**
     * Get provider registration info
     */
    static getProviderInfo(providerName) {
        return ProviderFactory.providers.get(providerName.toLowerCase());
    }
    /**
     * Normalize provider names using aliases (PHASE 1: Factory Pattern)
     */
    static normalizeProviderName(providerName) {
        const normalized = providerName.toLowerCase();
        // Check direct registration
        if (ProviderFactory.providers.has(normalized)) {
            return normalized;
        }
        // Check aliases from all registrations
        for (const [name, registration] of ProviderFactory.providers.entries()) {
            if (registration.aliases?.includes(normalized)) {
                return name;
            }
        }
        return null;
    }
    /**
     * Clear all registrations (mainly for testing)
     */
    static clearRegistrations() {
        ProviderFactory.providers.clear();
        ProviderFactory.initialized = false;
    }
    /**
     * Ensure providers are initialized
     */
    static ensureInitialized() {
        if (!ProviderFactory.initialized) {
            ProviderFactory.initializeDefaultProviders();
            ProviderFactory.initialized = true;
        }
    }
    /**
     * Initialize default providers
     * NOTE: Providers are now registered by ProviderRegistry to avoid circular dependencies
     */
    static initializeDefaultProviders() {
        logger.debug("BaseProvider factory pattern ready - providers registered by ProviderRegistry");
        // No hardcoded registrations - all done dynamically by ProviderRegistry
    }
    /**
     * Create the best available provider for the given name
     * Used by NeuroLink SDK for streaming and generation
     */
    static async createBestProvider(providerName, modelName, enableMCP, sdk, credentials) {
        return await ProviderFactory.createProvider(providerName, modelName, sdk, undefined, credentials);
    }
}
/**
 * Helper function to create providers with backward compatibility
 */
export async function createAIProvider(providerName, modelName, credentials) {
    return await ProviderFactory.createProvider(providerName, modelName, undefined, undefined, credentials);
}
