/**
 * @license
 * Copyright 2025 Vybestack LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import { ProviderManager, OpenAIProvider, OpenAIResponsesProvider, OpenAIVercelProvider, AnthropicProvider, GeminiProvider, sanitizeForByteString, needsSanitization, SettingsService, createProviderRuntimeContext, getSettingsService, DebugLogger, } from '@vybestack/llxprt-code-core';
const logger = new DebugLogger('llxprt:provider:manager:instance');
import { NodeFileSystem } from './IFileSystem.js';
import { LoadedSettings, USER_SETTINGS_PATH, } from '../config/settings.js';
import stripJsonComments from 'strip-json-comments';
import { OAuthManager } from '../auth/oauth-manager.js';
import { MultiProviderTokenStore } from '../auth/types.js';
import { ensureOAuthProviderRegistered } from './oauth-provider-registration.js';
import { loadProviderAliasEntries, } from './providerAliases.js';
/**
 * Sanitizes API keys to remove problematic characters that cause ByteString errors.
 * This handles cases where API key files have encoding issues or contain
 * Unicode replacement characters (U+FFFD).
 */
function sanitizeApiKey(key) {
    const sanitized = sanitizeForByteString(key);
    if (needsSanitization(key)) {
        console.warn('[ProviderManager] API key contained non-ASCII or control characters that were removed. ' +
            'Please check your API key file encoding (should be UTF-8 without BOM).');
    }
    return sanitized;
}
let fileSystemInstance = null;
let singletonManager = null;
let singletonOAuthManager = null;
let openAIContexts = new WeakMap();
/**
 * Set a custom file system implementation (mainly for testing).
 */
export function setFileSystem(fs) {
    fileSystemInstance = fs;
}
/**
 * Get the file system implementation to use.
 */
function getFileSystem() {
    if (!fileSystemInstance) {
        fileSystemInstance = new NodeFileSystem();
    }
    return fileSystemInstance;
}
function resolveLoadedSettings(fs, settings) {
    if (settings) {
        return settings;
    }
    let userSettings;
    try {
        if (fs.existsSync(USER_SETTINGS_PATH)) {
            const userContent = fs.readFileSync(USER_SETTINGS_PATH, 'utf-8');
            userSettings = JSON.parse(stripJsonComments(userContent));
        }
    }
    catch (_error) {
        // Failed to load user settings, ignore and fall back to defaults.
    }
    return userSettings
        ? new LoadedSettings({ path: '', settings: {} }, { path: '', settings: {} }, { path: USER_SETTINGS_PATH, settings: userSettings }, { path: '', settings: {} }, true)
        : undefined;
}
function attachAddItemToOAuthProviders(oauthManager, addItem) {
    if (!addItem) {
        return;
    }
    const providersMap = oauthManager.providers;
    if (!(providersMap instanceof Map)) {
        return;
    }
    for (const provider of providersMap.values()) {
        const candidate = provider;
        candidate.setAddItem?.(addItem);
    }
}
function coerceAuthOnly(value) {
    if (typeof value === 'boolean') {
        return value;
    }
    if (typeof value === 'string') {
        const normalized = value.trim().toLowerCase();
        if (normalized === 'true') {
            return true;
        }
        if (normalized === 'false') {
            return false;
        }
    }
    return undefined;
}
function resolveAuthOnlyFlag(config, loadedSettings) {
    if (config && typeof config.getEphemeralSettings === 'function') {
        const authOnlyValue = config.getEphemeralSettings().authOnly;
        if (authOnlyValue !== undefined) {
            const coerced = coerceAuthOnly(authOnlyValue);
            if (typeof coerced === 'boolean') {
                return coerced;
            }
        }
    }
    if (loadedSettings?.merged) {
        const mergedAuthOnly = loadedSettings.merged
            .authOnly;
        if (mergedAuthOnly !== undefined) {
            const coerced = coerceAuthOnly(mergedAuthOnly);
            if (typeof coerced === 'boolean') {
                return coerced;
            }
        }
    }
    if (typeof getSettingsService === 'function') {
        try {
            const settingsService = getSettingsService();
            if (settingsService && typeof settingsService.get === 'function') {
                const serviceValue = settingsService.get('authOnly');
                if (serviceValue !== undefined) {
                    const coerced = coerceAuthOnly(serviceValue);
                    if (typeof coerced === 'boolean') {
                        return coerced;
                    }
                }
            }
        }
        catch (_error) {
            // Ignore SettingsService lookup failures and fall back to default
        }
    }
    return false;
}
export function createProviderManager(context, options = {}) {
    const fs = getFileSystem();
    const loadedSettings = resolveLoadedSettings(fs, options.settings);
    const ManagerCtor = ProviderManager;
    const manager = new ManagerCtor(context);
    const tokenStore = new MultiProviderTokenStore();
    const oauthManager = new OAuthManager(tokenStore, loadedSettings);
    const { config, allowBrowserEnvironment = false, addItem } = options;
    logger.debug('createProviderManager config check', {
        hasConfig: !!config,
        configType: config?.constructor?.name,
    });
    if (config) {
        manager.setConfig(config);
        config.setProviderManager(manager);
        // Set message bus getter on OAuthManager for interactive TUI prompts
        // Uses a getter function to enable lazy resolution after TUI is initialized
        oauthManager.setMessageBus(() => config.getMessageBus());
        // Set config getter on OAuthManager for bucket failover handler setup
        // @plan PLAN-20251213issue490
        oauthManager.setConfigGetter(() => config);
        logger.debug('OAuthManager message bus getter configured');
    }
    else {
        logger.debug('No config provided, message bus getter NOT configured');
    }
    const authOnlyEnabled = resolveAuthOnlyFlag(config, loadedSettings);
    const geminiProvider = getGeminiProvider(oauthManager, config);
    manager.registerProvider(geminiProvider);
    void ensureOAuthProviderRegistered('gemini', oauthManager, tokenStore, addItem);
    const settingsData = loadedSettings?.merged || {};
    const ephemeralSettings = config?.getEphemeralSettings?.() ?? {};
    const effectiveOpenaiResponsesEnabled = ephemeralSettings.openaiResponsesEnabled !== undefined
        ? Boolean(ephemeralSettings.openaiResponsesEnabled)
        : authOnlyEnabled
            ? true
            : typeof settingsData.openaiResponsesEnabled === 'boolean'
                ? settingsData.openaiResponsesEnabled
                : undefined;
    // Check for CLI-provided API key first (highest priority)
    // NOTE: Bootstrap args (--key, --keyfile) should be applied to ephemeral settings
    // by calling applyCliArgumentOverrides() BEFORE creating the provider manager.
    // We check ephemeralSettings['auth-key'] which should already contain the resolved value.
    const ephemeralAuthKey = ephemeralSettings['auth-key'];
    // Also check provider-specific settings if no ephemeral auth-key
    const settingsProviders = settingsData;
    const openaiProviderSettings = settingsProviders.providers;
    const openaiSettings = openaiProviderSettings?.openai;
    const openaiProviderApiKey = openaiSettings?.apiKey ||
        openaiSettings?.['auth-key'];
    let openaiApiKey;
    if (ephemeralAuthKey &&
        typeof ephemeralAuthKey === 'string' &&
        ephemeralAuthKey.trim() !== '') {
        openaiApiKey = sanitizeApiKey(ephemeralAuthKey);
    }
    else if (openaiProviderApiKey &&
        typeof openaiProviderApiKey === 'string' &&
        openaiProviderApiKey.trim() !== '') {
        openaiApiKey = sanitizeApiKey(openaiProviderApiKey);
    }
    else if (process.env.OPENAI_API_KEY && !authOnlyEnabled) {
        openaiApiKey = sanitizeApiKey(process.env.OPENAI_API_KEY);
    }
    // Check for CLI-provided baseUrl in ephemerals or provider settings
    // NOTE: Bootstrap args (--baseurl) should be applied to ephemeral settings
    // by calling applyCliArgumentOverrides() BEFORE creating the provider manager.
    const ephemeralBaseUrl = ephemeralSettings['base-url'];
    const providerBaseUrl = openaiSettings?.baseUrl ||
        openaiSettings?.baseURL;
    const openaiBaseUrl = ephemeralBaseUrl && typeof ephemeralBaseUrl === 'string'
        ? ephemeralBaseUrl
        : providerBaseUrl && typeof providerBaseUrl === 'string'
            ? providerBaseUrl
            : process.env.OPENAI_BASE_URL;
    // Debug logging removed - was using console.log which violates project guidelines
    // Use DebugLogger if detailed logging is needed here
    const openaiProviderConfig = {
        enableTextToolCallParsing: settingsData.enableTextToolCallParsing,
        textToolCallModels: settingsData.textToolCallModels,
        providerToolFormatOverrides: settingsData.providerToolFormatOverrides,
        openaiResponsesEnabled: effectiveOpenaiResponsesEnabled,
        toolCallProcessingMode: settingsData.toolCallProcessingMode,
        allowBrowserEnvironment,
        getEphemeralSettings: config
            ? () => config.getEphemeralSettings()
            : undefined,
    };
    manager.registerProvider(getOpenAIProvider(openaiApiKey, openaiBaseUrl, openaiProviderConfig, oauthManager));
    manager.registerProvider(new OpenAIVercelProvider(openaiApiKey, openaiBaseUrl, openaiProviderConfig, oauthManager));
    const aliasEntries = loadProviderAliasEntries();
    registerAliasProviders(manager, aliasEntries, openaiApiKey, openaiBaseUrl, openaiProviderConfig, oauthManager);
    void ensureOAuthProviderRegistered('qwen', oauthManager, tokenStore, addItem);
    // Always register OpenAI Responses provider (openaiResponsesEnabled setting is obsolete)
    // Pass oauthManager for Codex mode support
    manager.registerProvider(getOpenAIResponsesProvider(openaiApiKey, openaiBaseUrl, allowBrowserEnvironment, oauthManager));
    manager.registerProvider(getAnthropicProvider(authOnlyEnabled, oauthManager, allowBrowserEnvironment));
    void ensureOAuthProviderRegistered('anthropic', oauthManager, tokenStore, addItem);
    void ensureOAuthProviderRegistered('codex', oauthManager, tokenStore, addItem);
    manager.setActiveProvider('gemini');
    attachAddItemToOAuthProviders(oauthManager, addItem);
    const openAIContext = {
        apiKey: openaiApiKey ?? undefined,
        baseUrl: openaiBaseUrl ?? undefined,
        providerConfig: openaiProviderConfig,
        oauthManager,
    };
    openAIContexts.set(manager, openAIContext);
    return { manager, oauthManager };
}
export function getProviderManager(config, allowBrowserEnvironment = false, settings, addItem) {
    if (singletonManager && addItem && singletonOAuthManager) {
        attachAddItemToOAuthProviders(singletonOAuthManager, addItem);
    }
    if (!singletonManager) {
        const runtime = createProviderRuntimeContext({
            settingsService: config?.getSettingsService() ?? new SettingsService(),
            config,
            runtimeId: 'provider-manager-singleton',
            metadata: { source: 'providerManagerInstance.getProviderManager' },
        });
        const { manager, oauthManager } = createProviderManager(runtime, {
            config,
            allowBrowserEnvironment,
            settings,
            addItem,
        });
        singletonManager = manager;
        singletonOAuthManager = oauthManager;
        if (config) {
            config.setProviderManager(manager);
        }
    }
    return singletonManager;
}
export function resetProviderManager() {
    singletonManager = null;
    singletonOAuthManager = null;
    openAIContexts = new WeakMap();
}
export function getOAuthManager() {
    return singletonOAuthManager;
}
export function refreshAliasProviders() {
    if (!singletonManager) {
        return;
    }
    const context = openAIContexts.get(singletonManager);
    if (!context) {
        return;
    }
    const aliasEntries = loadProviderAliasEntries();
    registerAliasProviders(singletonManager, aliasEntries, context.apiKey, context.baseUrl, context.providerConfig, context.oauthManager);
}
export { getProviderManager as providerManager };
function getOpenAIProvider(openaiApiKey, openaiBaseUrl, openaiProviderConfig, oauthManager) {
    return new OpenAIProvider(openaiApiKey || undefined, openaiBaseUrl, openaiProviderConfig, oauthManager);
}
function registerAliasProviders(providerManagerInstance, aliasEntries, openaiApiKey, openaiBaseUrl, openaiProviderConfig, oauthManager) {
    for (const entry of aliasEntries) {
        switch (entry.config.baseProvider.toLowerCase()) {
            case 'openai': {
                const provider = createOpenAIAliasProvider(entry, openaiApiKey, openaiBaseUrl, openaiProviderConfig, oauthManager);
                if (provider) {
                    providerManagerInstance.registerProvider(provider);
                }
                break;
            }
            case 'openai-responses': {
                const provider = createOpenAIResponsesAliasProvider(entry, openaiApiKey, openaiBaseUrl, openaiProviderConfig, oauthManager);
                if (provider) {
                    providerManagerInstance.registerProvider(provider);
                }
                break;
            }
            case 'openaivercel': {
                const provider = createOpenAIVercelAliasProvider(entry, openaiApiKey, openaiBaseUrl, openaiProviderConfig, oauthManager);
                if (provider) {
                    providerManagerInstance.registerProvider(provider);
                }
                break;
            }
            default: {
                console.warn(`[ProviderManager] Unsupported base provider '${entry.config.baseProvider}' for alias '${entry.alias}', skipping.`);
            }
        }
    }
}
/**
 * Ensure alias providers use their own identifier when resolving authentication.
 * Without this, API keys saved via `/key` are stored under the alias name,
 * but the OpenAI provider would continue to look up credentials under `openai`.
 */
export function bindOpenAIAliasIdentity(provider, alias) {
    bindProviderAliasIdentity(provider, alias);
}
function bindProviderAliasIdentity(provider, alias) {
    const aliasName = alias?.trim();
    if (!aliasName) {
        return;
    }
    Object.defineProperty(provider, 'name', {
        value: aliasName,
        writable: false,
        enumerable: true,
        configurable: true,
    });
    const aliasAwareProvider = provider;
    if (aliasAwareProvider.baseProviderConfig) {
        aliasAwareProvider.baseProviderConfig.name = aliasName;
    }
    aliasAwareProvider.authResolver?.updateConfig?.({
        providerId: aliasName,
    });
}
function createOpenAIAliasProvider(entry, openaiApiKey, openaiBaseUrl, openaiProviderConfig, oauthManager) {
    const resolvedBaseUrl = entry.config.baseUrl || openaiBaseUrl;
    if (!resolvedBaseUrl) {
        console.warn(`[ProviderManager] Alias '${entry.alias}' is missing a baseUrl and no default is available, skipping.`);
        return null;
    }
    const aliasProviderConfig = {
        ...openaiProviderConfig,
        baseUrl: resolvedBaseUrl,
    };
    if (entry.config.providerConfig) {
        Object.assign(aliasProviderConfig, entry.config.providerConfig);
    }
    if (entry.config.defaultModel) {
        aliasProviderConfig.defaultModel = entry.config.defaultModel;
    }
    let aliasApiKey;
    if (entry.config.apiKeyEnv) {
        const envValue = process.env[entry.config.apiKeyEnv];
        if (envValue && envValue.trim() !== '') {
            aliasApiKey = sanitizeApiKey(envValue);
        }
    }
    if (!aliasApiKey && openaiApiKey) {
        aliasApiKey = openaiApiKey;
    }
    const provider = new OpenAIProvider(aliasApiKey || undefined, resolvedBaseUrl, aliasProviderConfig, oauthManager);
    if (entry.config.defaultModel &&
        typeof provider.getDefaultModel === 'function') {
        const configuredDefaultModel = entry.config.defaultModel;
        const originalGetDefaultModel = provider.getDefaultModel.bind(provider);
        provider.getDefaultModel = () => configuredDefaultModel || originalGetDefaultModel();
    }
    bindOpenAIAliasIdentity(provider, entry.alias);
    return provider;
}
function createOpenAIResponsesAliasProvider(entry, openaiApiKey, openaiBaseUrl, openaiProviderConfig, oauthManager) {
    const resolvedBaseUrl = entry.config.baseUrl || openaiBaseUrl;
    if (!resolvedBaseUrl) {
        console.warn(`[ProviderManager] Alias '${entry.alias}' is missing a baseUrl and no default is available, skipping.`);
        return null;
    }
    const aliasProviderConfig = {
        ...openaiProviderConfig,
        baseUrl: resolvedBaseUrl,
    };
    if (entry.config.providerConfig) {
        Object.assign(aliasProviderConfig, entry.config.providerConfig);
    }
    if (entry.config.defaultModel) {
        aliasProviderConfig.defaultModel = entry.config.defaultModel;
    }
    let aliasApiKey;
    if (entry.config.apiKeyEnv) {
        const envValue = process.env[entry.config.apiKeyEnv];
        if (envValue && envValue.trim() !== '') {
            aliasApiKey = sanitizeApiKey(envValue);
        }
    }
    if (!aliasApiKey && openaiApiKey) {
        aliasApiKey = openaiApiKey;
    }
    const provider = new OpenAIResponsesProvider(aliasApiKey || undefined, resolvedBaseUrl, aliasProviderConfig, oauthManager);
    // Override the provider name to match the alias
    Object.defineProperty(provider, 'name', {
        value: entry.alias,
        writable: false,
        enumerable: true,
        configurable: true,
    });
    if (entry.config.defaultModel &&
        typeof provider.getDefaultModel === 'function') {
        const configuredDefaultModel = entry.config.defaultModel;
        const originalGetDefaultModel = provider.getDefaultModel.bind(provider);
        provider.getDefaultModel = () => configuredDefaultModel || originalGetDefaultModel();
    }
    return provider;
}
function createOpenAIVercelAliasProvider(entry, openaiApiKey, openaiBaseUrl, openaiProviderConfig, oauthManager) {
    const resolvedBaseUrl = entry.config.baseUrl || openaiBaseUrl;
    if (!resolvedBaseUrl) {
        console.warn(`[ProviderManager] Alias '${entry.alias}' is missing a baseUrl and no default is available, skipping.`);
        return null;
    }
    const aliasProviderConfig = {
        ...openaiProviderConfig,
        baseUrl: resolvedBaseUrl,
    };
    if (entry.config.providerConfig) {
        Object.assign(aliasProviderConfig, entry.config.providerConfig);
    }
    if (entry.config.defaultModel) {
        aliasProviderConfig.defaultModel = entry.config.defaultModel;
    }
    let aliasApiKey;
    if (entry.config.apiKeyEnv) {
        const envValue = process.env[entry.config.apiKeyEnv];
        if (envValue && envValue.trim() !== '') {
            aliasApiKey = sanitizeApiKey(envValue);
        }
    }
    if (!aliasApiKey && openaiApiKey) {
        aliasApiKey = openaiApiKey;
    }
    const provider = new OpenAIVercelProvider(aliasApiKey || undefined, resolvedBaseUrl, aliasProviderConfig, oauthManager);
    if (entry.config.defaultModel &&
        typeof provider.getDefaultModel === 'function') {
        const configuredDefaultModel = entry.config.defaultModel;
        const originalGetDefaultModel = provider.getDefaultModel.bind(provider);
        provider.getDefaultModel = () => configuredDefaultModel || originalGetDefaultModel();
    }
    bindProviderAliasIdentity(provider, entry.alias);
    return provider;
}
function getOpenAIResponsesProvider(openaiApiKey, openaiBaseUrl, allowBrowserEnvironment, oauthManager) {
    const openaiResponsesProvider = new OpenAIResponsesProvider(openaiApiKey || undefined, openaiBaseUrl, { allowBrowserEnvironment }, oauthManager);
    return openaiResponsesProvider;
}
function getGeminiProvider(oauthManager, config) {
    const geminiProvider = new GeminiProvider(undefined, undefined, config, oauthManager);
    if (config && typeof geminiProvider.setConfig === 'function') {
        geminiProvider.setConfig(config);
    }
    return geminiProvider;
}
function getAnthropicProvider(authOnlyEnabled, oauthManager, allowBrowserEnvironment) {
    let anthropicApiKey;
    if (!authOnlyEnabled && process.env.ANTHROPIC_API_KEY) {
        anthropicApiKey = sanitizeApiKey(process.env.ANTHROPIC_API_KEY);
    }
    const anthropicBaseUrl = process.env.ANTHROPIC_BASE_URL;
    const anthropicProvider = new AnthropicProvider(anthropicApiKey || undefined, anthropicBaseUrl, { allowBrowserEnvironment }, oauthManager);
    return anthropicProvider;
}
//# sourceMappingURL=providerManagerInstance.js.map