/**
 * LSP Manager
 * Manages multiple language server connections with auto-discovery and routing
 */
import { EventEmitter } from 'events';
import { readFile } from 'fs/promises';
import { extname } from 'path';
import { fileURLToPath } from 'url';
import { LSPClient } from './lsp-client.js';
import { discoverLanguageServers, getLanguageId } from './server-discovery.js';
export class LSPManager extends EventEmitter {
    clients = new Map(); // serverName -> client
    languageToServer = new Map(); // extension -> serverName
    documentServers = new Map(); // uri -> serverName
    diagnosticsCache = new Map(); // uri -> diagnostics
    rootUri;
    initialized = false;
    constructor(config = {}) {
        super();
        this.rootUri = config.rootUri || `file://${process.cwd()}`;
    }
    /**
     * Initialize the LSP manager with auto-discovery and/or custom servers
     */
    async initialize(config = {}) {
        const results = [];
        const serversToStart = [];
        // Add custom servers first (they take priority)
        if (config.servers) {
            for (const server of config.servers) {
                serversToStart.push({
                    ...server,
                    rootUri: server.rootUri || this.rootUri,
                });
            }
        }
        // Auto-discover additional servers if enabled (default: true)
        if (config.autoDiscover !== false) {
            // Extract file path from rootUri for context-aware server discovery
            const projectRoot = this.rootUri.startsWith('file://')
                ? fileURLToPath(this.rootUri)
                : this.rootUri;
            const discovered = await discoverLanguageServers(projectRoot);
            // Only add discovered servers for languages not already covered
            const coveredLanguages = new Set();
            for (const server of serversToStart) {
                for (const lang of server.languages) {
                    coveredLanguages.add(lang);
                }
            }
            for (const server of discovered) {
                const hasNewLanguages = server.languages.some(lang => !coveredLanguages.has(lang));
                if (hasNewLanguages) {
                    serversToStart.push({
                        ...server,
                        rootUri: this.rootUri,
                    });
                    for (const lang of server.languages) {
                        coveredLanguages.add(lang);
                    }
                }
            }
        }
        // Start all servers in parallel
        const startPromises = serversToStart.map(async (serverConfig) => {
            const result = await this.startServer(serverConfig);
            config.onProgress?.(result);
            results.push(result);
            return result;
        });
        await Promise.all(startPromises);
        this.initialized = true;
        return results;
    }
    /**
     * Start a single language server
     */
    async startServer(config) {
        try {
            const client = new LSPClient(config);
            // Handle diagnostics from this server
            client.on('diagnostics', (params) => {
                this.diagnosticsCache.set(params.uri, params.diagnostics);
                this.emit('diagnostics', params);
            });
            client.on('exit', (_code) => {
                this.clients.delete(config.name);
                // Remove language mappings for this server
                for (const [lang, serverName] of this.languageToServer.entries()) {
                    if (serverName === config.name) {
                        this.languageToServer.delete(lang);
                    }
                }
            });
            await client.start();
            // Store client and language mappings
            this.clients.set(config.name, client);
            for (const lang of config.languages) {
                this.languageToServer.set(lang, config.name);
            }
            return {
                serverName: config.name,
                success: true,
                languages: config.languages,
            };
        }
        catch (error) {
            return {
                serverName: config.name,
                success: false,
                error: error instanceof Error ? error.message : String(error),
            };
        }
    }
    /**
     * Stop all language servers
     */
    async shutdown() {
        const stopPromises = Array.from(this.clients.values()).map(client => client.stop());
        await Promise.all(stopPromises);
        this.clients.clear();
        this.languageToServer.clear();
        this.documentServers.clear();
        this.diagnosticsCache.clear();
        this.initialized = false;
    }
    /**
     * Get the client for a file based on its extension
     */
    getClientForFile(filePath) {
        const ext = extname(filePath).slice(1); // Remove leading dot
        const serverName = this.languageToServer.get(ext);
        if (!serverName)
            return undefined;
        return this.clients.get(serverName);
    }
    /**
     * Convert file path to URI
     */
    fileToUri(filePath) {
        if (filePath.startsWith('file://'))
            return filePath;
        return `file://${filePath}`;
    }
    /**
     * Open a document in the appropriate language server
     */
    async openDocument(filePath, content) {
        const client = this.getClientForFile(filePath);
        if (!client || !client.isReady())
            return false;
        const uri = this.fileToUri(filePath);
        const ext = extname(filePath).slice(1);
        const languageId = getLanguageId(ext);
        // Read content if not provided
        const text = content ?? (await readFile(filePath, 'utf-8'));
        client.openDocument(uri, languageId, text);
        this.documentServers.set(uri, client.getCapabilities() ? 'active' : '');
        return true;
    }
    /**
     * Update a document in the language server
     */
    updateDocument(filePath, content) {
        const client = this.getClientForFile(filePath);
        if (!client || !client.isReady())
            return false;
        const uri = this.fileToUri(filePath);
        client.updateDocument(uri, content);
        return true;
    }
    /**
     * Close a document in the language server
     */
    closeDocument(filePath) {
        const client = this.getClientForFile(filePath);
        if (!client || !client.isReady())
            return false;
        const uri = this.fileToUri(filePath);
        client.closeDocument(uri);
        this.documentServers.delete(uri);
        this.diagnosticsCache.delete(uri);
        return true;
    }
    /**
     * Get diagnostics for a file
     */
    async getDiagnostics(filePath) {
        const uri = this.fileToUri(filePath);
        // First check cache (from push notifications)
        const cached = this.diagnosticsCache.get(uri);
        if (cached)
            return cached;
        // Try pull diagnostics
        const client = this.getClientForFile(filePath);
        if (!client || !client.isReady())
            return [];
        return client.getDiagnostics(uri);
    }
    /**
     * Get diagnostics for all open documents
     */
    getAllDiagnostics() {
        const results = [];
        for (const [uri, diagnostics] of this.diagnosticsCache.entries()) {
            if (diagnostics.length > 0) {
                results.push({ uri, diagnostics });
            }
        }
        return results;
    }
    /**
     * Get completions at a position in a file
     */
    async getCompletions(filePath, line, character) {
        const client = this.getClientForFile(filePath);
        if (!client || !client.isReady())
            return [];
        const uri = this.fileToUri(filePath);
        return client.getCompletions(uri, { line, character });
    }
    /**
     * Get code actions for a range in a file
     */
    async getCodeActions(filePath, startLine, startChar, endLine, endChar, diagnostics) {
        const client = this.getClientForFile(filePath);
        if (!client || !client.isReady())
            return [];
        const uri = this.fileToUri(filePath);
        // Use provided diagnostics or get from cache
        const diags = diagnostics ||
            this.diagnosticsCache.get(uri)?.filter(d => {
                // Filter diagnostics that overlap with the range
                return d.range.start.line <= endLine && d.range.end.line >= startLine;
            }) ||
            [];
        return client.getCodeActions(uri, diags, startLine, startChar, endLine, endChar);
    }
    /**
     * Format a document
     */
    async formatDocument(filePath, options) {
        const client = this.getClientForFile(filePath);
        if (!client || !client.isReady())
            return [];
        const uri = this.fileToUri(filePath);
        return client.formatDocument(uri, options);
    }
    /**
     * Check if LSP is available for a file type
     */
    hasLanguageSupport(filePath) {
        const ext = extname(filePath).slice(1);
        return this.languageToServer.has(ext);
    }
    /**
     * Get list of connected servers
     */
    getConnectedServers() {
        return Array.from(this.clients.keys());
    }
    /**
     * Get supported languages
     */
    getSupportedLanguages() {
        return Array.from(this.languageToServer.keys());
    }
    /**
     * Check if manager is initialized
     */
    isInitialized() {
        return this.initialized;
    }
    /**
     * Get server status
     */
    getStatus() {
        const servers = [];
        for (const [name, client] of this.clients.entries()) {
            const languages = [];
            for (const [lang, serverName] of this.languageToServer.entries()) {
                if (serverName === name) {
                    languages.push(lang);
                }
            }
            servers.push({
                name,
                ready: client.isReady(),
                languages,
            });
        }
        return {
            initialized: this.initialized,
            servers,
        };
    }
}
// Singleton instance
let lspManagerInstance = null;
let lspManagerInitPromise = null;
/**
 * Get or create the LSP manager singleton
 * Uses promise-based initialization to prevent race conditions
 */
export async function getLSPManager(config) {
    if (lspManagerInstance) {
        return lspManagerInstance;
    }
    if (lspManagerInitPromise) {
        return lspManagerInitPromise;
    }
    // Create manager synchronously to ensure instance is set immediately
    lspManagerInstance = new LSPManager(config);
    lspManagerInitPromise = Promise.resolve(lspManagerInstance);
    return lspManagerInitPromise;
}
/**
 * Reset the LSP manager (for testing)
 */
export async function resetLSPManager() {
    if (lspManagerInstance) {
        await lspManagerInstance.shutdown();
        lspManagerInstance = null;
    }
    lspManagerInitPromise = null;
}
//# sourceMappingURL=lsp-manager.js.map