import { Client as SDKClient } from '@modelcontextprotocol/sdk/client/index.js';
import { createNoOpLogger } from '../lib/logger.js';
import { ServerError, NetworkError, isShutdownError } from '../lib/errors.js';
export class McpClient {
    client;
    logger;
    negotiatedProtocolVersion;
    mcpSessionId;
    transport;
    hasConnected = false;
    requestTimeout;
    constructor(clientInfo, options = {}) {
        this.logger = options.logger || createNoOpLogger();
        if (options.requestTimeout !== undefined) {
            this.requestTimeout = options.requestTimeout;
        }
        this.client = new SDKClient(clientInfo, {
            capabilities: options.capabilities || {},
            ...options,
        });
        this.client.onerror = (error) => {
            if (isShutdownError(error)) {
                this.logger.debug('Client aborted (expected during close)');
                return;
            }
            this.logger.log(this.hasConnected ? 'error' : 'debug', 'Client error:', error);
        };
    }
    getRequestOptions() {
        return this.requestTimeout ? { timeout: this.requestTimeout } : undefined;
    }
    async connect(transport) {
        try {
            this.logger.debug('Connecting to MCP server...');
            this.transport = transport;
            transport.onerror = (error) => {
                if (isShutdownError(error)) {
                    this.logger.debug('Transport aborted (expected during close)');
                    return;
                }
                this.logger.log(this.hasConnected ? 'error' : 'debug', 'Transport error:', error);
            };
            transport.onclose = () => {
                this.logger.debug('Transport closed');
            };
            await this.client.connect(transport);
            this.hasConnected = true;
            const transportWithVersion = transport;
            if (transportWithVersion.protocolVersion) {
                this.negotiatedProtocolVersion = transportWithVersion.protocolVersion;
                this.logger.debug(`Negotiated protocol version: ${this.negotiatedProtocolVersion}`);
            }
            const transportWithMcpSessionId = transport;
            if (transportWithMcpSessionId.sessionId) {
                this.mcpSessionId = transportWithMcpSessionId.sessionId;
                this.logger.debug(`MCP session ID: ${this.mcpSessionId}`);
            }
            const serverVersion = this.client.getServerVersion();
            const serverCapabilities = this.client.getServerCapabilities();
            this.logger.debug(`Connected to ${serverVersion?.name || 'unknown'} v${serverVersion?.version || 'unknown'}`);
            this.logger.debug('Server capabilities:', serverCapabilities);
        }
        catch (error) {
            this.logger.error('Failed to connect:', error);
            throw new NetworkError(`Failed to connect to MCP server: ${error.message}`, {
                originalError: error,
            });
        }
    }
    async close() {
        this.logger.debug('Closing connection...');
        try {
            if (this.transport?.terminateSession) {
                this.logger.debug('Terminating session (sending DELETE)...');
                try {
                    await Promise.race([
                        this.transport.terminateSession(),
                        new Promise((resolve) => setTimeout(resolve, 2000)),
                    ]);
                    this.logger.debug('Session terminated');
                }
                catch (error) {
                    this.logger.debug('Error terminating session:', error);
                }
            }
            await Promise.race([
                this.client.close(),
                new Promise((resolve) => setTimeout(resolve, 1000)),
            ]);
            this.logger.debug('Connection closed');
        }
        catch (error) {
            this.logger.debug('Error during close (ignored):', error);
        }
    }
    getServerDetails() {
        const details = {};
        const serverInfo = this.client.getServerVersion();
        const capabilities = this.client.getServerCapabilities();
        const instructions = this.client.getInstructions();
        if (this.negotiatedProtocolVersion)
            details.protocolVersion = this.negotiatedProtocolVersion;
        if (capabilities)
            details.capabilities = capabilities;
        if (serverInfo)
            details.serverInfo = serverInfo;
        if (instructions)
            details.instructions = instructions;
        return Promise.resolve(details);
    }
    getMcpSessionId() {
        return this.mcpSessionId;
    }
    async ping() {
        try {
            this.logger.debug('Sending ping...');
            await this.client.ping(this.getRequestOptions());
            this.logger.debug('Ping successful');
        }
        catch (error) {
            this.logger.error('Ping failed:', error);
            throw new NetworkError(`Ping failed: ${error.message}`, { originalError: error });
        }
    }
    async listTools(cursor) {
        try {
            this.logger.debug('Listing tools...', cursor ? { cursor } : {});
            const result = await this.client.listTools({ cursor }, this.getRequestOptions());
            this.logger.debug(`Found ${result.tools.length} tools`);
            return result;
        }
        catch (error) {
            this.logger.error('Failed to list tools:', error);
            throw new ServerError(`Failed to list tools: ${error.message}`, {
                originalError: error,
            });
        }
    }
    async callTool(name, args) {
        try {
            this.logger.debug(`Calling tool: ${name}`, args);
            const result = (await this.client.callTool({
                name,
                arguments: args || {},
            }, undefined, this.getRequestOptions()));
            this.logger.debug(`Tool ${name} completed`);
            return result;
        }
        catch (error) {
            this.logger.error(`Failed to call tool ${name}:`, error);
            throw new ServerError(`Failed to call tool ${name}: ${error.message}`, {
                originalError: error,
            });
        }
    }
    async listResources(cursor) {
        try {
            this.logger.debug('Listing resources...', cursor ? { cursor } : {});
            const result = await this.client.listResources({ cursor }, this.getRequestOptions());
            this.logger.debug(`Found ${result.resources.length} resources`);
            return result;
        }
        catch (error) {
            this.logger.error('Failed to list resources:', error);
            throw new ServerError(`Failed to list resources: ${error.message}`, {
                originalError: error,
            });
        }
    }
    async listResourceTemplates(cursor) {
        try {
            this.logger.debug('Listing resource templates...', cursor ? { cursor } : {});
            const result = await this.client.listResourceTemplates({ cursor }, this.getRequestOptions());
            this.logger.debug(`Found ${result.resourceTemplates.length} resource templates`);
            return result;
        }
        catch (error) {
            this.logger.error('Failed to list resource templates:', error);
            throw new ServerError(`Failed to list resource templates: ${error.message}`, {
                originalError: error,
            });
        }
    }
    async readResource(uri) {
        try {
            this.logger.debug(`Reading resource: ${uri}`);
            const result = await this.client.readResource({ uri }, this.getRequestOptions());
            this.logger.debug(`Resource ${uri} read successfully`);
            return result;
        }
        catch (error) {
            this.logger.error(`Failed to read resource ${uri}:`, error);
            throw new ServerError(`Failed to read resource ${uri}: ${error.message}`, {
                originalError: error,
            });
        }
    }
    async subscribeResource(uri) {
        try {
            this.logger.debug(`Subscribing to resource: ${uri}`);
            await this.client.subscribeResource({ uri }, this.getRequestOptions());
            this.logger.debug(`Subscribed to resource ${uri}`);
        }
        catch (error) {
            this.logger.error(`Failed to subscribe to resource ${uri}:`, error);
            throw new ServerError(`Failed to subscribe to resource ${uri}: ${error.message}`, {
                originalError: error,
            });
        }
    }
    async unsubscribeResource(uri) {
        try {
            this.logger.debug(`Unsubscribing from resource: ${uri}`);
            await this.client.unsubscribeResource({ uri }, this.getRequestOptions());
            this.logger.debug(`Unsubscribed from resource ${uri}`);
        }
        catch (error) {
            this.logger.error(`Failed to unsubscribe from resource ${uri}:`, error);
            throw new ServerError(`Failed to unsubscribe from resource ${uri}: ${error.message}`, { originalError: error });
        }
    }
    async listPrompts(cursor) {
        try {
            this.logger.debug('Listing prompts...', cursor ? { cursor } : {});
            const result = await this.client.listPrompts({ cursor }, this.getRequestOptions());
            this.logger.debug(`Found ${result.prompts.length} prompts`);
            return result;
        }
        catch (error) {
            this.logger.error('Failed to list prompts:', error);
            throw new ServerError(`Failed to list prompts: ${error.message}`, {
                originalError: error,
            });
        }
    }
    async getPrompt(name, args) {
        try {
            this.logger.debug(`Getting prompt: ${name}`, args);
            const result = (await this.client.getPrompt({
                name,
                arguments: args,
            }, this.getRequestOptions()));
            this.logger.debug(`Prompt ${name} retrieved`);
            return result;
        }
        catch (error) {
            this.logger.error(`Failed to get prompt ${name}:`, error);
            throw new ServerError(`Failed to get prompt ${name}: ${error.message}`, {
                originalError: error,
            });
        }
    }
    async setLoggingLevel(level) {
        try {
            this.logger.debug(`Setting log level to: ${level}`);
            await this.client.setLoggingLevel(level, this.getRequestOptions());
            this.logger.debug('Log level set successfully');
        }
        catch (error) {
            this.logger.error(`Failed to set log level:`, error);
            throw new ServerError(`Failed to set log level: ${error.message}`, {
                originalError: error,
            });
        }
    }
    getSDKClient() {
        return this.client;
    }
}
//# sourceMappingURL=mcp-client.js.map