/**
 * Tool Discovery Service
 * Automatically discovers and registers tools from external MCP servers
 * Handles tool validation, transformation, and lifecycle management
 */
import { EventEmitter } from "events";
import { mcpLogger } from "../utils/logger.js";
import { globalCircuitBreakerManager, CircuitBreakerOpenError, } from "./mcpCircuitBreaker.js";
import { isObject, isNullish } from "../utils/typeUtils.js";
import { validateToolName, validateToolDescription, } from "../utils/parameterValidation.js";
import { withTimeout } from "../utils/errorHandling.js";
import { extractMcpErrorText } from "../utils/mcpErrorText.js";
import { SpanKind, SpanStatusCode } from "@opentelemetry/api";
import { tracers } from "../telemetry/tracers.js";
import { withSpan } from "../telemetry/withSpan.js";
const mcpTracer = tracers.mcp;
/**
 * JSON-stringify a value for a Langfuse input/output preview attribute,
 * truncated to a hard cap to stay under span attribute size limits. The
 * returned string is guaranteed to be ≤ maxLen characters; when truncated,
 * the last character is replaced with an ellipsis.
 */
function safeJsonStringify(value, maxLen) {
    if (maxLen <= 0) {
        return "";
    }
    try {
        const str = JSON.stringify(value);
        if (typeof str !== "string") {
            return "";
        }
        if (str.length <= maxLen) {
            return str;
        }
        return str.slice(0, Math.max(0, maxLen - 1)) + "…";
    }
    catch {
        return "";
    }
}
/**
 * Match property names that commonly hold secrets. Values under these keys
 * are replaced with `[REDACTED]` before serialization. Case-insensitive.
 * Conservative list — anything matching *here* is masked; the rest of the
 * structure is preserved so Langfuse still gets a meaningful preview.
 */
const SENSITIVE_KEY_PATTERN = /^(password|passwd|secret|token|api[_-]?key|apikey|access[_-]?key|authorization|auth|bearer|credential|cookie|session[_-]?id|private[_-]?key|client[_-]?secret|refresh[_-]?token|x-api-key)$/i;
/**
 * Walk a value, producing a structurally-equivalent copy with sensitive-key
 * values masked. Unlike `transformParamsForLogging` (which collapses objects
 * to a "N params" string), this preserves non-sensitive content so Langfuse
 * input/output previews stay useful. Bounded depth guards against cycles.
 */
function redactForPreview(value, depth = 0) {
    if (depth > 10) {
        return "[...]";
    }
    if (value === null || value === undefined) {
        return value;
    }
    if (typeof value !== "object") {
        return value;
    }
    if (Array.isArray(value)) {
        return value.map((v) => redactForPreview(v, depth + 1));
    }
    const out = {};
    for (const [k, v] of Object.entries(value)) {
        if (SENSITIVE_KEY_PATTERN.test(k)) {
            out[k] = "[REDACTED]";
        }
        else {
            out[k] = redactForPreview(v, depth + 1);
        }
    }
    return out;
}
/**
 * Default timeout for MCP tool execution operations in milliseconds.
 * Configurable via MCP_TOOL_TIMEOUT env var.
 */
const DEFAULT_TOOL_TIMEOUT = Math.max(5000, Number(process.env.MCP_TOOL_TIMEOUT) || 60000);
/**
 * ToolDiscoveryService
 * Handles automatic tool discovery and registration from external MCP servers
 */
export class ToolDiscoveryService extends EventEmitter {
    serverToolStorage = new Map();
    toolRegistry = new Map();
    serverTools = new Map();
    discoveryInProgress = new Set();
    /** Optional normalizer applied to every tool output before it is returned. */
    outputNormalizer;
    constructor() {
        super();
    }
    /**
     * Attach a McpOutputNormalizer.
     * When set, every raw callTool() result is passed through the normalizer
     * before being returned. Oversized outputs are replaced with compact
     * surrogates according to the configured strategy.
     */
    setOutputNormalizer(normalizer) {
        this.outputNormalizer = normalizer;
    }
    /**
     * Discover tools from an external MCP server
     */
    async discoverTools(serverId, client, timeout = DEFAULT_TOOL_TIMEOUT) {
        return withSpan({
            name: "neurolink.mcp.discoverTools",
            tracer: tracers.mcp,
            attributes: { "mcp.server_id": serverId },
        }, async (span) => {
            const startTime = Date.now();
            try {
                // Prevent concurrent discovery for same server
                if (this.discoveryInProgress.has(serverId)) {
                    return {
                        success: false,
                        error: `Discovery already in progress for server: ${serverId}`,
                        toolCount: 0,
                        tools: [],
                        duration: Date.now() - startTime,
                        serverId,
                    };
                }
                this.discoveryInProgress.add(serverId);
                mcpLogger.info(`[ToolDiscoveryService] Starting tool discovery for server: ${serverId}`);
                // Create circuit breaker for tool discovery
                const circuitBreaker = globalCircuitBreakerManager.getBreaker(`tool-discovery-${serverId}`, {
                    failureThreshold: 2,
                    resetTimeout: 60000,
                    operationTimeout: timeout,
                });
                // Discover tools with circuit breaker protection
                const tools = await circuitBreaker.execute(async () => {
                    return await this.performToolDiscovery(serverId, client, timeout);
                });
                // Register discovered tools
                const registeredTools = await this.registerDiscoveredTools(serverId, tools);
                span.setAttribute("mcp.tools_discovered", registeredTools.length);
                const result = {
                    success: true,
                    toolCount: registeredTools.length,
                    tools: registeredTools,
                    duration: Date.now() - startTime,
                    serverId,
                };
                // Emit discovery completed event
                this.emit("discoveryCompleted", {
                    serverId,
                    toolCount: registeredTools.length,
                    duration: result.duration,
                    timestamp: new Date(),
                });
                mcpLogger.info(`[ToolDiscoveryService] Discovery completed for ${serverId}: ${registeredTools.length} tools`);
                return result;
            }
            catch (error) {
                const errorMessage = error instanceof Error ? error.message : String(error);
                span.setStatus({
                    code: SpanStatusCode.ERROR,
                    message: errorMessage,
                });
                span.recordException(error instanceof Error ? error : new Error(errorMessage));
                mcpLogger.error(`[ToolDiscoveryService] Discovery failed for ${serverId}:`, error);
                // Emit discovery failed event
                this.emit("discoveryFailed", {
                    serverId,
                    error: errorMessage,
                    timestamp: new Date(),
                });
                return {
                    success: false,
                    error: errorMessage,
                    toolCount: 0,
                    tools: [],
                    duration: Date.now() - startTime,
                    serverId,
                };
            }
            finally {
                this.discoveryInProgress.delete(serverId);
            }
        });
    }
    /**
     * Perform the actual tool discovery
     */
    async performToolDiscovery(serverId, client, timeout) {
        // List tools from the MCP server
        const listToolsPromise = client.listTools();
        const timeoutPromise = this.createTimeoutPromise(timeout, "Tool discovery timeout");
        const result = await Promise.race([listToolsPromise, timeoutPromise]);
        if (!result || !result.tools) {
            throw new Error("No tools returned from server");
        }
        mcpLogger.debug(`[ToolDiscoveryService] Discovered ${result.tools.length} tools from ${serverId}`);
        return result.tools;
    }
    /**
     * Register discovered tools
     */
    async registerDiscoveredTools(serverId, tools) {
        const registeredTools = [];
        // Clear existing tools for this server
        this.clearServerTools(serverId);
        for (const tool of tools) {
            try {
                const toolInfo = await this.createToolInfo(serverId, tool);
                const validation = this.validateTool(toolInfo);
                if (!validation.isValid) {
                    mcpLogger.warn(`[ToolDiscoveryService] Skipping invalid tool ${tool.name} from ${serverId}:`, validation.errors);
                    continue;
                }
                // Apply validation metadata
                if (validation.metadata) {
                    toolInfo.metadata = {
                        ...toolInfo.metadata,
                        ...validation.metadata,
                    };
                }
                // Register the tool
                const toolKey = this.createToolKey(serverId, tool.name);
                this.toolRegistry.set(toolKey, toolInfo);
                if (!this.serverToolStorage.has(serverId)) {
                    this.serverToolStorage.set(serverId, []);
                }
                const serverTools = this.serverToolStorage.get(serverId);
                if (!serverTools) {
                    throw new Error(`Server tools storage not found for ${serverId}`);
                }
                // Add tool if not already present
                if (!serverTools.find((t) => t.name === tool.name)) {
                    serverTools.push({
                        name: tool.name,
                        description: tool.description || "",
                        inputSchema: tool.inputSchema,
                    });
                }
                // Track server tools (legacy)
                if (!this.serverTools.has(serverId)) {
                    this.serverTools.set(serverId, new Set());
                }
                const serverToolSet = this.serverTools.get(serverId);
                if (serverToolSet) {
                    serverToolSet.add(tool.name);
                }
                registeredTools.push(toolInfo);
                // Emit tool registered event
                this.emit("toolRegistered", {
                    serverId,
                    toolName: tool.name,
                    toolInfo,
                    timestamp: new Date(),
                });
                mcpLogger.debug(`[ToolDiscoveryService] Registered tool: ${tool.name} from ${serverId}`);
            }
            catch (error) {
                mcpLogger.error(`[ToolDiscoveryService] Failed to register tool ${tool.name} from ${serverId}:`, error);
            }
        }
        return registeredTools;
    }
    /**
     * Create tool info from MCP tool definition
     */
    async createToolInfo(serverId, tool) {
        return {
            name: tool.name,
            description: tool.description || "No description provided",
            serverId,
            inputSchema: tool.inputSchema,
            isAvailable: true,
            stats: {
                totalCalls: 0,
                successfulCalls: 0,
                failedCalls: 0,
                averageExecutionTime: 0,
                lastExecutionTime: 0,
            },
            metadata: {
                category: this.inferToolCategory(tool),
                version: "1.0.0",
                deprecated: false,
            },
        };
    }
    /**
     * Infer tool category from tool definition
     */
    inferToolCategory(tool) {
        const name = tool.name.toLowerCase();
        const description = (tool.description || "").toLowerCase();
        // Common patterns for categorization
        if (name.includes("git") || description.includes("git")) {
            return "version-control";
        }
        if (name.includes("file") ||
            name.includes("read") ||
            name.includes("write")) {
            return "file-system";
        }
        if (name.includes("api") ||
            name.includes("http") ||
            name.includes("request")) {
            return "api";
        }
        if (name.includes("data") ||
            name.includes("query") ||
            name.includes("search")) {
            return "data";
        }
        if (name.includes("auth") ||
            name.includes("login") ||
            name.includes("token")) {
            return "authentication";
        }
        if (name.includes("deploy") ||
            name.includes("build") ||
            name.includes("ci")) {
            return "deployment";
        }
        return "general";
    }
    /**
     * Validate a tool
     */
    validateTool(toolInfo) {
        const errors = [];
        const warnings = [];
        // Use centralized validation for name
        const nameError = validateToolName(toolInfo.name);
        if (nameError) {
            errors.push(nameError.message);
        }
        // Use centralized validation for description
        const descriptionError = validateToolDescription(toolInfo.description);
        if (descriptionError) {
            warnings.push(descriptionError.message);
        }
        if (!toolInfo.serverId) {
            errors.push("Server ID is required");
        }
        // Schema validation
        if (toolInfo.inputSchema) {
            try {
                JSON.stringify(toolInfo.inputSchema);
            }
            catch {
                errors.push("Input schema is not valid JSON");
            }
        }
        // Infer metadata
        const metadata = {
            category: typeof toolInfo.metadata?.category === "string"
                ? toolInfo.metadata.category
                : "general",
            complexity: this.inferComplexity(toolInfo),
            requiresAuth: this.inferAuthRequirement(toolInfo),
            isDeprecated: typeof toolInfo.metadata?.deprecated === "boolean"
                ? toolInfo.metadata.deprecated
                : false,
        };
        return {
            isValid: errors.length === 0,
            errors,
            warnings,
            metadata,
        };
    }
    /**
     * Infer tool complexity
     */
    inferComplexity(toolInfo) {
        const schema = toolInfo.inputSchema;
        if (!schema || !schema.properties) {
            return "simple";
        }
        const propertyCount = Object.keys(schema.properties).length;
        if (propertyCount <= 2) {
            return "simple";
        }
        else if (propertyCount <= 5) {
            return "moderate";
        }
        else {
            return "complex";
        }
    }
    /**
     * Infer if tool requires authentication
     */
    inferAuthRequirement(toolInfo) {
        const name = toolInfo.name.toLowerCase();
        const description = toolInfo.description.toLowerCase();
        return (name.includes("auth") ||
            name.includes("login") ||
            name.includes("token") ||
            description.includes("authentication") ||
            description.includes("credentials") ||
            description.includes("permission"));
    }
    /**
     * Execute a tool
     */
    async executeTool(toolName, serverId, client, parameters, options = {}) {
        const startTime = Date.now();
        try {
            const toolKey = this.createToolKey(serverId, toolName);
            const toolInfo = this.toolRegistry.get(toolKey);
            if (!toolInfo) {
                throw new Error(`Tool '${toolName}' not found for server '${serverId}'`);
            }
            if (!toolInfo.isAvailable) {
                throw new Error(`Tool '${toolName}' is not available`);
            }
            // Validate input parameters if requested
            if (options.validateInput !== false) {
                this.validateToolParameters(toolInfo, parameters);
            }
            mcpLogger.debug(`[ToolDiscoveryService] Executing tool: ${toolName} on ${serverId}`, {
                parameters,
            });
            // Create circuit breaker for tool execution
            const effectiveTimeout = options.timeout || DEFAULT_TOOL_TIMEOUT;
            const circuitBreaker = globalCircuitBreakerManager.getBreaker(`tool-execution-${serverId}-${toolName}`, {
                failureThreshold: 3,
                resetTimeout: 30000,
                operationTimeout: effectiveTimeout,
            });
            // Execute tool with circuit breaker protection
            const result = await circuitBreaker.execute(async () => {
                return mcpTracer.startActiveSpan("neurolink.mcp.callTool", {
                    kind: SpanKind.CLIENT,
                    attributes: {
                        "mcp.server_id": serverId,
                        "mcp.tool_name": toolName,
                        "mcp.timeout_ms": effectiveTimeout,
                        // Curator P1-4: Langfuse observations rely on ai.*/gen_ai.*
                        // attributes for tool name and I/O previews. Provide them so
                        // the SPAN observation in Langfuse is legible without
                        // timestamp-joining against the parent ai.toolCall. Redact
                        // parameters via the existing secret-stripping helper so
                        // tokens/credentials/paths don't leave the process.
                        "ai.tool.name": toolName,
                        "gen_ai.tool.name": toolName,
                        "gen_ai.request": safeJsonStringify({
                            name: toolName,
                            arguments: redactForPreview(parameters),
                        }, 2048),
                    },
                }, async (callSpan) => {
                    try {
                        const timeout = effectiveTimeout;
                        const callResult = await withTimeout(client.callTool({
                            name: toolName,
                            arguments: parameters,
                        }), timeout, new Error(`Tool execution timeout: ${toolName}`));
                        // Curator P0-1/P0-2: the MCP client does NOT throw on protocol
                        // errors — it returns { isError: true, content: [...] }. Detect
                        // that pattern so the span status reflects reality.
                        const resultObj = callResult;
                        if (resultObj && resultObj.isError === true) {
                            const errorText = extractMcpErrorText(resultObj);
                            callSpan.setStatus({
                                code: SpanStatusCode.ERROR,
                                message: errorText || `Tool ${toolName} returned isError`,
                            });
                        }
                        else {
                            callSpan.setStatus({ code: SpanStatusCode.OK });
                        }
                        // ── MCP output normalization ──────────────────────────────────
                        // Intercept here — after receive, before cache, before memory,
                        // before LLM context injection. Returns a compact surrogate when
                        // the payload exceeds mcp.outputLimits.maxBytes.
                        let resultForPreview = callResult;
                        let resultForReturn = callResult;
                        if (this.outputNormalizer) {
                            try {
                                const normalized = await this.outputNormalizer.normalize(callResult, { toolName, serverId });
                                callSpan.setAttribute("mcp.output.strategy", normalized.isExternalized ? "externalize" : "inline");
                                if (normalized.isExternalized) {
                                    callSpan.setAttribute("mcp.output.original_bytes", normalized.originalBytes);
                                }
                                resultForPreview = normalized.result;
                                resultForReturn = normalized.result;
                            }
                            catch (normErr) {
                                mcpLogger.warn(`[ToolDiscoveryService] McpOutputNormalizer failed for ` +
                                    `${toolName}: ${normErr instanceof Error ? normErr.message : String(normErr)} ` +
                                    `— returning raw result`);
                            }
                        }
                        // ── end normalization ─────────────────────────────────────────
                        // Curator P1-4: build gen_ai.response AFTER normalization so
                        // large payloads use the compact surrogate instead of the raw
                        // result (avoids redundant stringify + memory hit on payloads
                        // that were specifically externalized to Redis). Redact via the
                        // same secret-stripping path used for request parameters.
                        callSpan.setAttribute("gen_ai.response", safeJsonStringify(redactForPreview(resultForPreview), 2048));
                        return resultForReturn;
                    }
                    catch (err) {
                        callSpan.setStatus({
                            code: SpanStatusCode.ERROR,
                            message: err.message,
                        });
                        callSpan.recordException(err);
                        throw err;
                    }
                    finally {
                        callSpan.end();
                    }
                });
            });
            const duration = Date.now() - startTime;
            // Update tool statistics
            this.updateToolStats(toolKey, true, duration);
            // Validate output if requested
            if (options.validateOutput !== false) {
                this.validateToolOutput(result);
            }
            mcpLogger.debug(`[ToolDiscoveryService] Tool execution completed: ${toolName}`, {
                duration,
                hasContent: !!result?.content,
            });
            return {
                success: true,
                data: result,
                duration,
                metadata: {
                    toolName,
                    serverId,
                    timestamp: Date.now(),
                },
            };
        }
        catch (error) {
            const duration = Date.now() - startTime;
            const errorMessage = error instanceof Error ? error.message : String(error);
            // Update tool statistics
            const toolKey = this.createToolKey(serverId, toolName);
            this.updateToolStats(toolKey, false, duration);
            // Circuit breaker open errors: return a structured isError result with
            // actionable details so AI models understand the tool is temporarily
            // unavailable and should NOT retry until the cooldown expires.
            if (error instanceof CircuitBreakerOpenError) {
                mcpLogger.warn(`[ToolDiscoveryService] Tool blocked by circuit breaker: ${toolName} on ${serverId}`, {
                    breakerState: error.breakerState,
                    retryAfter: error.retryAfter,
                    retryAfterMs: error.retryAfterMs,
                    failureCount: error.failureCount,
                });
                return {
                    success: false,
                    error: error.message,
                    data: {
                        isError: true,
                        content: [
                            {
                                type: "text",
                                text: `TOOL TEMPORARILY UNAVAILABLE: "${toolName}" has been disabled after ` +
                                    `${error.failureCount} failures. ` +
                                    `This is a circuit breaker protection — do NOT retry this tool. ` +
                                    `It will become available again after ${Math.ceil(error.retryAfterMs / 1000)} seconds ` +
                                    `(at ${error.retryAfter}). ` +
                                    `Instead, inform the user that the operation failed and suggest trying again later.`,
                            },
                        ],
                    },
                    duration,
                    metadata: {
                        toolName,
                        serverId,
                        timestamp: Date.now(),
                        circuitBreaker: {
                            state: error.breakerState,
                            retryAfter: error.retryAfter,
                            retryAfterMs: error.retryAfterMs,
                            failureCount: error.failureCount,
                        },
                    },
                };
            }
            mcpLogger.error(`[ToolDiscoveryService] Tool execution failed: ${toolName}`, error);
            return {
                success: false,
                error: errorMessage,
                duration,
                metadata: {
                    toolName,
                    serverId,
                    timestamp: Date.now(),
                },
            };
        }
    }
    /**
     * Validate tool parameters
     */
    validateToolParameters(toolInfo, parameters) {
        if (!toolInfo.inputSchema) {
            return; // No schema to validate against
        }
        // Basic validation - check required properties
        const schema = toolInfo.inputSchema;
        if (schema.required && Array.isArray(schema.required)) {
            for (const requiredProp of schema.required) {
                if (typeof requiredProp === "string" && !(requiredProp in parameters)) {
                    throw new Error(`Missing required parameter: ${requiredProp}`);
                }
            }
        }
        // Type validation for properties
        if (schema.properties) {
            for (const [propName, propSchema] of Object.entries(schema.properties)) {
                if (propName in parameters) {
                    this.validateParameterType(propName, parameters[propName], propSchema);
                }
            }
        }
    }
    /**
     * Validate parameter type
     */
    validateParameterType(name, value, schema) {
        if (!schema.type) {
            return; // No type constraint
        }
        const expectedType = schema.type;
        const actualType = typeof value;
        switch (expectedType) {
            case "string":
                if (actualType !== "string") {
                    throw new Error(`Parameter '${name}' must be a string, got ${actualType}`);
                }
                break;
            case "number":
                if (actualType !== "number") {
                    throw new Error(`Parameter '${name}' must be a number, got ${actualType}`);
                }
                break;
            case "boolean":
                if (actualType !== "boolean") {
                    throw new Error(`Parameter '${name}' must be a boolean, got ${actualType}`);
                }
                break;
            case "array":
                if (!Array.isArray(value)) {
                    throw new Error(`Parameter '${name}' must be an array, got ${actualType}`);
                }
                break;
            case "object":
                if (actualType !== "object" || value === null || Array.isArray(value)) {
                    throw new Error(`Parameter '${name}' must be an object, got ${actualType}`);
                }
                break;
        }
    }
    /**
     * Validate tool output with enhanced type safety
     */
    validateToolOutput(result) {
        // GENERIC ERROR HANDLING FOR ALL MCP TOOLS
        // Different MCP servers return different error formats, so we should be permissive
        // and let the AI handle any response format instead of throwing errors
        // Only throw for truly invalid responses (null/undefined)
        if (isNullish(result)) {
            mcpLogger.debug("[ToolDiscoveryService] Tool returned null/undefined, treating as empty response");
            // Even null responses can be valid for some tools - don't throw
            return;
        }
        // Log what we received for debugging, but don't validate specific formats
        mcpLogger.debug("[ToolDiscoveryService] Tool response received", {
            type: typeof result,
            isArray: Array.isArray(result),
            isObject: isObject(result),
            hasKeys: isObject(result) ? Object.keys(result).length : 0,
            fullResponse: result, // Log the complete response, not a truncated sample
        });
        // COMPLETELY PERMISSIVE APPROACH:
        // - Any response format is valid (objects, strings, arrays, booleans, numbers)
        // - Even error responses are passed to the AI to handle
        // - The AI can interpret error messages and retry with different approaches
        // - This works with any MCP server regardless of their response format
        // No validation or throwing - let the AI handle everything
        return;
    }
    /**
     * Update tool statistics
     */
    updateToolStats(toolKey, success, duration) {
        const toolInfo = this.toolRegistry.get(toolKey);
        if (!toolInfo) {
            return;
        }
        toolInfo.stats.totalCalls++;
        toolInfo.lastCalled = new Date();
        toolInfo.stats.lastExecutionTime = duration;
        if (success) {
            toolInfo.stats.successfulCalls++;
        }
        else {
            toolInfo.stats.failedCalls++;
        }
        // Update average execution time
        const totalTime = toolInfo.stats.averageExecutionTime * (toolInfo.stats.totalCalls - 1) +
            duration;
        toolInfo.stats.averageExecutionTime = totalTime / toolInfo.stats.totalCalls;
    }
    /**
     * Get tool by name and server
     */
    getTool(toolName, serverId) {
        const toolKey = this.createToolKey(serverId, toolName);
        return this.toolRegistry.get(toolKey);
    }
    /**
     * Get all tools for a server
     */
    getServerTools(serverId) {
        const serverTools = this.serverToolStorage.get(serverId);
        if (serverTools) {
            return serverTools.map((tool) => ({
                name: tool.name,
                description: tool.description,
                serverId,
                inputSchema: tool.inputSchema,
                isAvailable: true,
                stats: {
                    totalCalls: 0,
                    successfulCalls: 0,
                    failedCalls: 0,
                    averageExecutionTime: 0,
                    lastExecutionTime: 0,
                },
            }));
        }
        // Fallback to legacy storage
        const tools = [];
        const serverToolNames = this.serverTools.get(serverId);
        if (serverToolNames) {
            for (const toolName of serverToolNames) {
                const toolKey = this.createToolKey(serverId, toolName);
                const toolInfo = this.toolRegistry.get(toolKey);
                if (toolInfo) {
                    tools.push(toolInfo);
                }
            }
        }
        return tools;
    }
    /**
     * Get all registered tools
     */
    getAllTools() {
        const allTools = [];
        // Add tools from server-based storage (preferred)
        for (const [serverId, serverTools] of this.serverToolStorage.entries()) {
            for (const tool of serverTools) {
                allTools.push({
                    name: tool.name,
                    description: tool.description,
                    serverId,
                    inputSchema: tool.inputSchema,
                    isAvailable: true,
                    stats: {
                        totalCalls: 0,
                        successfulCalls: 0,
                        failedCalls: 0,
                        averageExecutionTime: 0,
                        lastExecutionTime: 0,
                    },
                });
            }
        }
        // Fallback to legacy storage for any tools not in server-based storage
        const legacyTools = Array.from(this.toolRegistry.values()).filter((tool) => !allTools.some((t) => t.name === tool.name && t.serverId === tool.serverId));
        return [...allTools, ...legacyTools];
    }
    /**
     * Clear tools for a server
     */
    clearServerTools(serverId) {
        const serverTools = this.serverToolStorage.get(serverId);
        if (serverTools) {
            // Emit unregistered events for server-based tools
            for (const tool of serverTools) {
                this.emit("toolUnregistered", {
                    serverId,
                    toolName: tool.name,
                    timestamp: new Date(),
                });
            }
            this.serverToolStorage.delete(serverId);
        }
        // Legacy cleanup
        const serverToolNames = this.serverTools.get(serverId);
        if (serverToolNames) {
            for (const toolName of serverToolNames) {
                const toolKey = this.createToolKey(serverId, toolName);
                this.toolRegistry.delete(toolKey);
                // Emit tool unregistered event (only if not already emitted above)
                if (!serverTools || !serverTools.find((t) => t.name === toolName)) {
                    this.emit("toolUnregistered", {
                        serverId,
                        toolName,
                        timestamp: new Date(),
                    });
                }
            }
            this.serverTools.delete(serverId);
        }
        mcpLogger.debug(`[ToolDiscoveryService] Cleared tools for server: ${serverId}`);
    }
    /**
     * Update tool availability
     */
    updateToolAvailability(toolName, serverId, isAvailable) {
        const toolKey = this.createToolKey(serverId, toolName);
        const toolInfo = this.toolRegistry.get(toolKey);
        if (toolInfo) {
            toolInfo.isAvailable = isAvailable;
            mcpLogger.debug(`[ToolDiscoveryService] Updated availability for ${toolName}: ${isAvailable}`);
        }
    }
    /**
     * Create tool key for registry
     */
    createToolKey(serverId, toolName) {
        return `${serverId}:${toolName}`;
    }
    /**
     * Create timeout promise
     */
    createTimeoutPromise(timeout, message) {
        return new Promise((_, reject) => {
            setTimeout(() => {
                reject(new Error(message));
            }, timeout);
        });
    }
    /**
     * Destroy the tool discovery service and clean up all resources
     * This method should be called when the service is no longer needed
     * to prevent memory leaks from accumulated event listeners
     *
     * @example
     * ```typescript
     * const service = new ToolDiscoveryService();
     * // ... use the service ...
     * service.destroy(); // Clean up when done
     * ```
     */
    destroy() {
        mcpLogger.debug("[ToolDiscoveryService] Starting cleanup...");
        // Clear all event listeners to prevent memory leaks
        this.removeAllListeners();
        // Clear all internal data structures
        this.serverToolStorage.clear();
        this.toolRegistry.clear();
        this.serverTools.clear();
        this.discoveryInProgress.clear();
        mcpLogger.debug("[ToolDiscoveryService] Destroyed and cleaned up");
    }
    /**
     * Reset statistics for all tools
     * This clears execution counts, timing data, and other statistics
     * while preserving the tool registrations themselves
     */
    resetStatistics() {
        for (const toolInfo of this.toolRegistry.values()) {
            toolInfo.stats = {
                totalCalls: 0,
                successfulCalls: 0,
                failedCalls: 0,
                averageExecutionTime: 0,
                lastExecutionTime: 0,
            };
            toolInfo.lastCalled = undefined;
        }
        mcpLogger.debug("[ToolDiscoveryService] Statistics reset for all tools");
    }
    /**
     * Get the count of active event listeners
     * Useful for monitoring potential memory leaks
     */
    getListenerCount() {
        const events = [
            "discoveryCompleted",
            "discoveryFailed",
            "toolRegistered",
            "toolUnregistered",
        ];
        let total = 0;
        for (const event of events) {
            total += this.listenerCount(event);
        }
        return total;
    }
    /**
     * Get discovery statistics
     */
    getStatistics() {
        const toolsByServer = {};
        const toolsByCategory = {};
        let availableTools = 0;
        let unavailableTools = 0;
        for (const toolInfo of this.toolRegistry.values()) {
            // Count by server
            toolsByServer[toolInfo.serverId] =
                (toolsByServer[toolInfo.serverId] || 0) + 1;
            // Count by category
            const category = typeof toolInfo.metadata?.category === "string"
                ? toolInfo.metadata.category
                : "unknown";
            toolsByCategory[category] = (toolsByCategory[category] || 0) + 1;
            // Count availability
            if (toolInfo.isAvailable) {
                availableTools++;
            }
            else {
                unavailableTools++;
            }
        }
        return {
            totalTools: this.toolRegistry.size,
            availableTools,
            unavailableTools,
            totalServers: this.serverTools.size,
            toolsByServer,
            toolsByCategory,
        };
    }
}
//# sourceMappingURL=toolDiscoveryService.js.map