"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MCPServerManager = void 0;
const serverLogger_js_1 = require("./serverLogger.js");
const express_1 = __importDefault(require("express"));
const http = __importStar(require("node:http"));
const node_crypto_1 = require("node:crypto");
const zod_1 = require("zod");
// Create a server-specific logger
const serverLogger = new serverLogger_js_1.ServerLogger('MCPServer');
class MCPServerManager {
    static instance;
    serverInstance = null;
    mcpServer = null;
    configRepo = null;
    // Maintain separate transport maps for each type
    transports = {
        streamable: {},
        sse: {}
    };
    // Store registered handlers for health endpoint
    handlers = {};
    // Private constructor for singleton pattern
    constructor() { }
    // Get singleton instance
    static getInstance() {
        if (!MCPServerManager.instance) {
            MCPServerManager.instance = new MCPServerManager();
        }
        return MCPServerManager.instance;
    }
    // Start the server
    async startServer() {
        try {
            // Dynamic imports for modularity
            const { ConfigRepository } = await Promise.resolve().then(() => __importStar(require('../repositories/configRepository.js')));
            this.configRepo = new ConfigRepository();
            serverLogger.log('ConfigRepository imported successfully');
            if (this.serverInstance) {
                serverLogger.log('MCP Server is already running.');
                return;
            }
            const enabledConfig = await this.configRepo.getValue('mcpServer.enabled');
            const isEnabled = enabledConfig === 'true';
            if (!isEnabled) {
                serverLogger.log('MCP Server is disabled in settings, not starting');
                return;
            }
            const portConfig = await this.configRepo.getValue('mcpServer.port');
            const port = portConfig ? parseInt(portConfig, 10) : 3000;
            // Import MCP server and transport types
            const { McpServer } = await Promise.resolve().then(() => __importStar(require('@modelcontextprotocol/sdk/server/mcp.js')));
            const { StreamableHTTPServerTransport } = await Promise.resolve().then(() => __importStar(require('@modelcontextprotocol/sdk/server/streamableHttp.js')));
            const { SSEServerTransport } = await Promise.resolve().then(() => __importStar(require('@modelcontextprotocol/sdk/server/sse.js')));
            const { isInitializeRequest } = await Promise.resolve().then(() => __importStar(require('@modelcontextprotocol/sdk/types.js')));
            this.mcpServer = new McpServer({
                name: 'one-logger-mcp-server',
                version: '1.0.0',
            });
            await this.registerTools();
            const app = (0, express_1.default)();
            app.use(express_1.default.json());
            // Health check endpoint
            app.get('/health', (_, res) => {
                const toolCount = Object.keys(this.handlers).length;
                res.json({
                    status: 'healthy',
                    tools: toolCount,
                    tool_names: Object.keys(this.handlers),
                    transports: {
                        sse: Object.keys(this.transports.sse).length,
                        streamable: Object.keys(this.transports.streamable).length
                    }
                });
            });
            // Modern endpoint - Streamable HTTP transport
            app.all('/mcp', async (req, res) => {
                const sessionId = req.headers['mcp-session-id'];
                let transport = sessionId ? this.transports.streamable[sessionId] : undefined;
                if (req.method === 'POST') {
                    if (sessionId && transport) {
                        // Reuse existing transport
                    }
                    else if (!sessionId && isInitializeRequest(req.body)) {
                        // New session
                        transport = new StreamableHTTPServerTransport({
                            sessionIdGenerator: () => (0, node_crypto_1.randomUUID)(),
                            onsessioninitialized: (id) => {
                                this.transports.streamable[id] = transport;
                                serverLogger.log(`Streamable HTTP session initialized: ${id}`);
                            }
                        });
                        transport.onclose = () => {
                            if (transport && 'sessionId' in transport && transport.sessionId) {
                                delete this.transports.streamable[transport.sessionId];
                                serverLogger.log(`Streamable HTTP session closed: ${transport.sessionId}`);
                            }
                        };
                        await this.mcpServer.connect(transport);
                    }
                    else {
                        res.status(400).json({
                            jsonrpc: '2.0',
                            error: { code: -32000, message: 'Bad Request: No valid session ID provided' },
                            id: null,
                        });
                        return;
                    }
                    await transport.handleRequest(req, res, req.body);
                }
                else if (req.method === 'GET' || req.method === 'DELETE') {
                    if (!sessionId || !transport) {
                        res.status(400).send('Invalid or missing session ID');
                        return;
                    }
                    await transport.handleRequest(req, res);
                }
                else {
                    res.status(405).send('Method Not Allowed');
                }
            });
            // Legacy endpoint for SSE connections (for Cursor)
            app.get('/sse', async (_req, res) => {
                // Set explicit SSE headers at the very top before any other logic
                res.setHeader('Content-Type', 'text/event-stream');
                res.setHeader('Cache-Control', 'no-cache');
                res.setHeader('Connection', 'keep-alive');
                if (typeof res.flushHeaders === 'function')
                    res.flushHeaders();
                try {
                    // Create new SSE transport for this connection
                    const transport = new SSEServerTransport('/messages', res);
                    const sessionId = transport.sessionId;
                    serverLogger.log(`New SSE connection established with session ID: ${sessionId}`);
                    this.transports.sse[sessionId] = transport;
                    // Remove transport when connection is closed
                    res.on('close', () => {
                        serverLogger.log(`SSE connection closed: ${sessionId}`);
                        delete this.transports.sse[sessionId];
                    });
                    // Connect the transport to the MCP server
                    await this.mcpServer.connect(transport);
                }
                catch (error) {
                    serverLogger.error('Error establishing SSE connection:', error);
                    if (!res.headersSent) {
                        res.status(500).json({ error: 'Internal server error' });
                    }
                }
            });
            // Legacy endpoint for message posting (for Cursor)
            app.post('/messages', async (req, res) => {
                try {
                    // Get session ID from query parameter (not headers like in Streamable HTTP)
                    const sessionId = req.query.sessionId;
                    if (!sessionId || !this.transports.sse[sessionId]) {
                        serverLogger.error(`Invalid session ID: ${sessionId}`);
                        res.status(400).send({ message: 'Invalid session ID' });
                        return;
                    }
                    const transport = this.transports.sse[sessionId];
                    await transport.handlePostMessage(req, res, req.body);
                }
                catch (error) {
                    serverLogger.error('Error handling message:', error);
                    if (!res.headersSent) {
                        res.status(500).json({ error: 'Internal server error' });
                    }
                }
            });
            this.serverInstance = http.createServer(app).listen(port);
            serverLogger.log(`MCP server running with dual transport support:`);
            serverLogger.log(`- Modern clients: http://localhost:${port}/mcp`);
            serverLogger.log(`- Legacy clients (Cursor): http://localhost:${port}/sse`);
            serverLogger.log(`- Health check: http://localhost:${port}/health`);
        }
        catch (error) {
            serverLogger.error('Failed to start MCP server:', error);
        }
    }
    // Register tools that expose our API endpoints
    async registerTools() {
        if (!this.mcpServer)
            return;
        try {
            const { ProjectRepository } = await Promise.resolve().then(() => __importStar(require('../repositories/projectRepository.js')));
            const { LogRepository } = await Promise.resolve().then(() => __importStar(require('../repositories/logRepository.js')));
            // Import LogFilters types for Zod schema if not already available broadly
            // For simplicity, we'll define inline or assume they are compatible with Zod types here.
            const projectRepo = new ProjectRepository();
            const logRepo = new LogRepository();
            // Define Zod schema for LogLevel if not already defined elsewhere usable by Zod
            const LogLevelSchema = zod_1.z.enum(["error", "warn", "info", "debug", "trace"]);
            // Define Zod schema for MetadataFilter
            const MetadataFilterSchema = zod_1.z.object({
                key: zod_1.z.string(),
                value: zod_1.z.string(),
            });
            // Define Zod schema for Cursor
            const CursorSchema = zod_1.z.object({
                id: zod_1.z.string(),
                timestamp: zod_1.z.string(), // Assuming timestamp is passed as string (ISO format)
            });
            this.handlers = {
                listProjects: async () => {
                    try {
                        const projects = await projectRepo.getAllProjects();
                        return {
                            content: [{ type: 'text', text: JSON.stringify(projects, null, 2) }],
                        };
                    }
                    catch (error) {
                        return {
                            content: [{ type: 'text', text: `Error listing projects: ${error}` }],
                        };
                    }
                },
                getProject: async (args) => {
                    serverLogger.log('getProject args:', args);
                    try {
                        const project = await projectRepo.getProjectById(args.id);
                        if (!project) {
                            return {
                                content: [{ type: 'text', text: `Project with ID ${args.id} not found` }],
                            };
                        }
                        return {
                            content: [{ type: 'text', text: JSON.stringify(project, null, 2) }],
                        };
                    }
                    catch (error) {
                        return {
                            content: [{ type: 'text', text: `Error getting project: ${error}` }],
                        };
                    }
                },
                getProjectLogs: async (args) => {
                    serverLogger.log('getProjectLogs args:', args);
                    try {
                        const logs = await logRepo.getLogsWithFilters({
                            projectId: args.projectId,
                            limit: args.limit, // Pass through limit
                            // Pass through all newly added optional filters
                            level: args.level, // Cast to any if LogLevel type from DB doesn't match Zod enum directly
                            messageContains: args.messageContains,
                            fromDate: args.fromDate,
                            toDate: args.toDate,
                            metadata: args.metadata,
                            cursor: args.cursor,
                            sortDirection: args.sortDirection,
                        });
                        return {
                            content: [{ type: 'text', text: JSON.stringify(logs, null, 2) }],
                        };
                    }
                    catch (error) {
                        const errorMessage = error instanceof Error ? error.message : String(error);
                        serverLogger.error('Error in getProjectLogs tool:', errorMessage, error);
                        return {
                            content: [{ type: 'text', text: `Error getting logs: ${errorMessage}` }],
                        };
                    }
                },
                searchLogs: async (args) => {
                    serverLogger.log('searchLogs args:', args);
                    try {
                        // This is a placeholder - you would need to implement text search in your LogRepository
                        const logs = await logRepo.getLogsWithFilters({
                            projectId: '*',
                            limit: args.limit || 10,
                        });
                        return {
                            content: [{ type: 'text', text: `Search results for "${args.query}":\n${JSON.stringify(logs, null, 2)}` }],
                        };
                    }
                    catch (error) {
                        return {
                            content: [{ type: 'text', text: `Error searching logs: ${error}` }],
                        };
                    }
                },
            };
            this.mcpServer.tool('listProjects', {}, this.handlers.listProjects);
            this.mcpServer.tool('getProject', {
                id: zod_1.z.string().describe('Project ID'),
            }, this.handlers.getProject);
            this.mcpServer.tool('getProjectLogs', {
                projectId: zod_1.z.string().describe('Project ID'),
                limit: zod_1.z.number().optional().describe('Limit number of logs'),
                level: zod_1.z.union([LogLevelSchema, zod_1.z.array(LogLevelSchema)]).optional().describe('Log level or array of log levels'),
                messageContains: zod_1.z.string().optional().describe('Text to search in log messages'),
                fromDate: zod_1.z.string().datetime({ message: "Invalid datetime string for fromDate, must be ISO 8601" }).optional().describe('Start date/time (ISO 8601 format)'),
                toDate: zod_1.z.string().datetime({ message: "Invalid datetime string for toDate, must be ISO 8601" }).optional().describe('End date/time (ISO 8601 format)'),
                metadata: zod_1.z.array(MetadataFilterSchema).optional().describe('Array of metadata key-value pairs to filter by'),
                cursor: CursorSchema.optional().describe('Cursor for pagination (id and timestamp)'),
                sortDirection: zod_1.z.enum(['asc', 'desc']).optional().describe('Sort direction for logs (asc or desc by timestamp)'),
            }, this.handlers.getProjectLogs);
            this.mcpServer.tool('searchLogs', {
                query: zod_1.z.string().describe('Search query'),
                limit: zod_1.z.number().optional().describe('Limit number of logs'),
            }, this.handlers.searchLogs);
            serverLogger.log(`Registered ${Object.keys(this.handlers).length} tools with MCP server`);
            serverLogger.log(`Tool names: ${Object.keys(this.handlers).join(', ')}`);
        }
        catch (error) {
            serverLogger.error('Error in registerTools:', error);
            throw error;
        }
    }
    // Stop the server
    async stopServer() {
        try {
            if (this.serverInstance) {
                await new Promise((resolve, reject) => {
                    this.serverInstance.close((err) => {
                        if (err)
                            reject(err);
                        else
                            resolve();
                    });
                });
                // Clear all transports
                this.transports = {
                    streamable: {},
                    sse: {}
                };
                this.mcpServer = null;
                this.serverInstance = null;
                serverLogger.log('MCP Server stopped due to configuration change');
                return { success: true };
            }
            serverLogger.log('MCP Server was not running');
            return { success: false };
        }
        catch (error) {
            serverLogger.error('Failed to stop MCP server:', error);
            return { success: false };
        }
    }
    // Restart the server
    async restartServer() {
        try {
            // First stop the server if it's running
            await this.stopServer();
            // Then start it again with new config
            serverLogger.log('Restarting MCP server due to configuration change');
            await this.startServer();
            return { success: true };
        }
        catch (error) {
            serverLogger.error('Failed to restart MCP server:', error);
            return { success: false };
        }
    }
    // Get logs by type - exposed for IPC handlers
    getLogs(type) {
        return serverLogger.getLogs(type);
    }
    // Clear logs by type - exposed for IPC handlers
    clearLogs(type) {
        return serverLogger.clearLogs(type);
    }
}
exports.MCPServerManager = MCPServerManager;
//# sourceMappingURL=mcpServerManager.js.map