/**
 * MCP Server Capabilities - Resources and Prompts
 *
 * Extends MCP server functionality with resource and prompt handling
 * according to MCP specification. This module provides:
 * - Resource registration and management
 * - Prompt template registration and execution
 * - Resource subscription support
 *
 * @module mcp/serverCapabilities
 * @since 8.39.0
 */
import { EventEmitter } from "events";
import { logger } from "../utils/logger.js";
import { ErrorFactory } from "../utils/errorHandling.js";
import { withTimeout } from "../utils/async/withTimeout.js";
/**
 * Server Capabilities Manager
 *
 * Manages resources and prompts for MCP servers.
 *
 * @example
 * ```typescript
 * const capabilities = new ServerCapabilitiesManager({
 *   resources: true,
 *   prompts: true,
 * });
 *
 * // Register a resource
 * capabilities.registerResource({
 *   uri: "file:///data/config.json",
 *   name: "Configuration",
 *   mimeType: "application/json",
 *   reader: async (uri) => ({
 *     uri,
 *     mimeType: "application/json",
 *     text: JSON.stringify({ key: "value" }),
 *   }),
 * });
 *
 * // Register a prompt
 * capabilities.registerPrompt({
 *   name: "summarize",
 *   description: "Summarize text content",
 *   arguments: [{ name: "text", required: true }],
 *   generator: async (args) => ({
 *     messages: [
 *       { role: "user", content: { type: "text", text: `Summarize: ${args.text}` } },
 *     ],
 *   }),
 * });
 * ```
 */
export class ServerCapabilitiesManager extends EventEmitter {
    config;
    resources = new Map();
    prompts = new Map();
    subscriptions = new Map();
    resourceTemplates = new Map();
    constructor(config = {}) {
        super();
        this.config = {
            resources: config.resources ?? true,
            prompts: config.prompts ?? true,
            resourceSubscriptions: config.resourceSubscriptions ?? true,
        };
    }
    // ========================================
    // RESOURCE MANAGEMENT
    // ========================================
    /**
     * Register a resource
     */
    registerResource(resource) {
        if (!this.config.resources) {
            throw ErrorFactory.invalidConfiguration("resources", "Resource support is not enabled");
        }
        this.validateResourceUri(resource.uri);
        this.resources.set(resource.uri, resource);
        this.emit("resourceRegistered", {
            uri: resource.uri,
            name: resource.name,
            timestamp: new Date(),
        });
        logger.debug(`[ServerCapabilities] Registered resource: ${resource.uri}`);
        return this;
    }
    /**
     * Register a resource template (with URI pattern)
     */
    registerResourceTemplate(pattern, template) {
        if (!this.config.resources) {
            throw ErrorFactory.invalidConfiguration("resources", "Resource support is not enabled");
        }
        this.resourceTemplates.set(pattern, {
            ...template,
            uri: template.uriPattern,
        });
        this.emit("resourceTemplateRegistered", {
            pattern,
            timestamp: new Date(),
        });
        return this;
    }
    /**
     * Unregister a resource
     */
    unregisterResource(uri) {
        const removed = this.resources.delete(uri);
        if (removed) {
            // Clear subscriptions
            this.subscriptions.delete(uri);
            this.emit("resourceUnregistered", {
                uri,
                timestamp: new Date(),
            });
        }
        return removed;
    }
    /**
     * List all resources
     */
    listResources() {
        return Array.from(this.resources.values()).map((r) => ({
            uri: r.uri,
            name: r.name,
            description: r.description,
            mimeType: r.mimeType,
            size: r.size,
            dynamic: r.dynamic,
            annotations: r.annotations,
        }));
    }
    /**
     * Read a resource
     */
    async readResource(uri, context) {
        // Check direct resources
        let resource = this.resources.get(uri);
        // Check templates if not found
        if (!resource) {
            resource = this.findResourceTemplate(uri);
        }
        if (!resource) {
            throw ErrorFactory.invalidConfiguration("resource", `Resource not found: ${uri}`);
        }
        const startTime = Date.now();
        const resourceTimeoutMs = 30_000;
        try {
            const content = await withTimeout(resource.reader(uri, context), resourceTimeoutMs, `Resource read timed out after ${resourceTimeoutMs}ms for URI: ${uri}`);
            const duration = Date.now() - startTime;
            this.emit("resourceRead", {
                uri,
                duration,
                success: true,
                timestamp: new Date(),
            });
            return content;
        }
        catch (error) {
            const duration = Date.now() - startTime;
            this.emit("resourceRead", {
                uri,
                duration,
                success: false,
                error: error instanceof Error ? error.message : String(error),
                timestamp: new Date(),
            });
            throw error;
        }
    }
    /**
     * Subscribe to resource changes
     */
    subscribeToResource(uri, callback) {
        if (!this.config.resourceSubscriptions) {
            throw ErrorFactory.invalidConfiguration("resourceSubscriptions", "Resource subscriptions are not enabled");
        }
        if (!this.subscriptions.has(uri)) {
            this.subscriptions.set(uri, new Set());
        }
        const subs = this.subscriptions.get(uri);
        if (subs) {
            subs.add(callback);
        }
        this.emit("resourceSubscribed", {
            uri,
            timestamp: new Date(),
        });
        // Return unsubscribe function
        return () => {
            const subs = this.subscriptions.get(uri);
            if (subs) {
                subs.delete(callback);
                if (subs.size === 0) {
                    this.subscriptions.delete(uri);
                }
            }
            this.emit("resourceUnsubscribed", {
                uri,
                timestamp: new Date(),
            });
        };
    }
    /**
     * Notify subscribers of resource change
     */
    async notifyResourceChanged(uri) {
        const subscribers = this.subscriptions.get(uri);
        if (!subscribers || subscribers.size === 0) {
            return;
        }
        try {
            const content = await this.readResource(uri);
            for (const callback of subscribers) {
                try {
                    const result = callback(uri, content);
                    // Handle async callbacks that return promises
                    if (result && typeof result.catch === "function") {
                        result.catch((error) => {
                            logger.error(`[ServerCapabilities] Async error notifying subscriber for ${uri}:`, error);
                        });
                    }
                }
                catch (error) {
                    logger.error(`[ServerCapabilities] Error notifying subscriber for ${uri}:`, error);
                }
            }
            this.emit("resourceChanged", {
                uri,
                subscriberCount: subscribers.size,
                timestamp: new Date(),
            });
        }
        catch (error) {
            logger.error(`[ServerCapabilities] Error reading resource for notification: ${uri}`, error);
        }
    }
    /**
     * Get resource by URI
     */
    getResource(uri) {
        return this.resources.get(uri) ?? this.findResourceTemplate(uri);
    }
    /**
     * Validate resource URI
     */
    validateResourceUri(uri) {
        try {
            new URL(uri);
        }
        catch {
            // Allow non-URL URIs but warn
            logger.warn(`[ServerCapabilities] Resource URI is not a valid URL: ${uri}`);
        }
    }
    /**
     * Find matching resource template
     */
    findResourceTemplate(uri) {
        for (const [pattern, template] of this.resourceTemplates) {
            if (this.matchesPattern(uri, pattern)) {
                return {
                    ...template,
                    uri,
                };
            }
        }
        return undefined;
    }
    /**
     * Check if URI matches a pattern
     */
    matchesPattern(uri, pattern) {
        // Guard against excessively long patterns that could cause ReDoS
        if (pattern.length > 200) {
            return false;
        }
        // Escape regex metacharacters, then replace glob wildcards
        const regexPattern = pattern
            .replace(/[.+^${}()|[\]\\]/g, "\\$&")
            .replace(/\*/g, ".*")
            .replace(/\?/g, ".")
            // Restore URI template placeholders: \{...\} -> [^/]+
            .replace(/\\\{[^}]*\\\}/g, "[^/]+");
        try {
            return new RegExp(`^${regexPattern}$`).test(uri);
        }
        catch {
            // Invalid regex pattern — treat as non-match
            return false;
        }
    }
    // ========================================
    // PROMPT MANAGEMENT
    // ========================================
    /**
     * Register a prompt
     */
    registerPrompt(prompt) {
        if (!this.config.prompts) {
            throw ErrorFactory.invalidConfiguration("prompts", "Prompt support is not enabled");
        }
        this.validatePromptName(prompt.name);
        this.prompts.set(prompt.name, prompt);
        this.emit("promptRegistered", {
            name: prompt.name,
            timestamp: new Date(),
        });
        logger.debug(`[ServerCapabilities] Registered prompt: ${prompt.name}`);
        return this;
    }
    /**
     * Unregister a prompt
     */
    unregisterPrompt(name) {
        const removed = this.prompts.delete(name);
        if (removed) {
            this.emit("promptUnregistered", {
                name,
                timestamp: new Date(),
            });
        }
        return removed;
    }
    /**
     * List all prompts
     */
    listPrompts() {
        return Array.from(this.prompts.values()).map((p) => ({
            name: p.name,
            description: p.description,
            arguments: p.arguments,
        }));
    }
    /**
     * Get a prompt
     */
    async getPrompt(name, args = {}, context) {
        const prompt = this.prompts.get(name);
        if (!prompt) {
            throw ErrorFactory.invalidConfiguration("prompt", `Prompt not found: ${name}`);
        }
        // Validate required arguments
        for (const arg of prompt.arguments ?? []) {
            if (arg.required && args[arg.name] === undefined) {
                throw ErrorFactory.invalidConfiguration("promptArgument", `Missing required argument: ${arg.name}`);
            }
        }
        const startTime = Date.now();
        const promptTimeoutMs = 30_000;
        try {
            const result = await withTimeout(prompt.generator(args, context), promptTimeoutMs, `Prompt generation timed out after ${promptTimeoutMs}ms for prompt: ${name}`);
            const duration = Date.now() - startTime;
            this.emit("promptGenerated", {
                name,
                duration,
                success: true,
                messageCount: result.messages.length,
                timestamp: new Date(),
            });
            return result;
        }
        catch (error) {
            const duration = Date.now() - startTime;
            this.emit("promptGenerated", {
                name,
                duration,
                success: false,
                error: error instanceof Error ? error.message : String(error),
                timestamp: new Date(),
            });
            throw error;
        }
    }
    /**
     * Get prompt by name
     */
    getPromptDefinition(name) {
        return this.prompts.get(name);
    }
    /**
     * Validate prompt name
     */
    validatePromptName(name) {
        if (!name || typeof name !== "string") {
            throw ErrorFactory.invalidConfiguration("promptName", "Prompt name is required and must be a string");
        }
        if (!/^[a-zA-Z_][a-zA-Z0-9_-]*$/.test(name)) {
            throw ErrorFactory.invalidConfiguration("promptName", "Prompt name must start with a letter or underscore and contain only alphanumeric characters, underscores, and hyphens");
        }
        if (this.prompts.has(name)) {
            throw ErrorFactory.invalidConfiguration("promptName", `Prompt '${name}' is already registered`);
        }
    }
    // ========================================
    // UTILITY METHODS
    // ========================================
    /**
     * Get capabilities object for MCP protocol
     */
    getCapabilities() {
        const capabilities = {};
        if (this.config.resources) {
            capabilities.resources = {
                subscribe: this.config.resourceSubscriptions,
                listChanged: true,
            };
        }
        if (this.config.prompts) {
            capabilities.prompts = {
                listChanged: true,
            };
        }
        return capabilities;
    }
    /**
     * Get statistics
     */
    getStatistics() {
        let subscriptionCount = 0;
        for (const subs of this.subscriptions.values()) {
            subscriptionCount += subs.size;
        }
        return {
            resourceCount: this.resources.size,
            templateCount: this.resourceTemplates.size,
            promptCount: this.prompts.size,
            subscriptionCount,
        };
    }
    /**
     * Clear all resources and prompts
     */
    clear() {
        this.resources.clear();
        this.resourceTemplates.clear();
        this.prompts.clear();
        this.subscriptions.clear();
        this.emit("cleared", { timestamp: new Date() });
    }
}
/**
 * Create a simple text resource
 */
export function createTextResource(uri, name, content, options) {
    return {
        uri,
        name,
        description: options?.description,
        mimeType: "text/plain",
        dynamic: options?.dynamic ?? typeof content === "function",
        reader: async (requestUri) => ({
            uri: requestUri,
            mimeType: "text/plain",
            text: typeof content === "function" ? await content() : content,
        }),
    };
}
/**
 * Create a JSON resource
 */
export function createJsonResource(uri, name, content, options) {
    return {
        uri,
        name,
        description: options?.description,
        mimeType: "application/json",
        dynamic: options?.dynamic ?? typeof content === "function",
        reader: async (requestUri) => ({
            uri: requestUri,
            mimeType: "application/json",
            text: JSON.stringify(typeof content === "function" ? await content() : content, null, 2),
        }),
    };
}
/**
 * Create a simple prompt template
 */
export function createPrompt(name, template, options) {
    return {
        name,
        description: options?.description,
        arguments: options?.arguments,
        generator: async (args) => {
            // Simple template substitution
            let text = template;
            for (const [key, value] of Object.entries(args)) {
                const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
                text = text.replace(new RegExp(`\\{${escapedKey}\\}`, "g"), () => String(value));
            }
            return {
                messages: [
                    {
                        role: "user",
                        content: { type: "text", text },
                    },
                ],
            };
        },
    };
}
