/**
 * Elicitation Manager
 *
 * Manager for handling elicitation requests during tool execution.
 * Enables MCP tools to request interactive user input mid-execution.
 *
 * @module mcp/elicitation/elicitationManager
 * @since 8.39.0
 */
import { EventEmitter } from "events";
import { randomUUID } from "crypto";
import { logger } from "../../utils/logger.js";
/**
 * Manager for handling elicitation requests during tool execution
 *
 * The elicitation protocol allows MCP tools to request interactive user input
 * mid-execution. This is useful for:
 * - Confirming destructive operations
 * - Requesting missing information
 * - Getting user preferences
 * - Handling authentication challenges
 *
 * @example
 * ```typescript
 * const elicitationManager = new ElicitationManager({
 *   defaultTimeout: 60000,
 *   handler: async (request) => {
 *     // Implement UI prompt based on request type
 *     if (request.type === "confirmation") {
 *       const confirmed = await showConfirmDialog(request.message);
 *       return {
 *         requestId: request.id,
 *         responded: true,
 *         value: confirmed,
 *         timestamp: Date.now(),
 *       };
 *     }
 *     // Handle other types...
 *   },
 * });
 *
 * // Use in a tool
 * const response = await elicitationManager.request({
 *   type: "confirmation",
 *   message: "Are you sure you want to delete this file?",
 *   toolName: "deleteFile",
 * });
 *
 * if (response.value === true) {
 *   // Proceed with deletion
 * }
 * ```
 */
export class ElicitationManager extends EventEmitter {
    config;
    pendingRequests = new Map();
    constructor(config = {}) {
        super();
        this.config = {
            defaultTimeout: config.defaultTimeout ?? 60000,
            enabled: config.enabled ?? true,
            handler: config.handler ?? this.defaultHandler.bind(this),
            fallbackBehavior: config.fallbackBehavior ?? "timeout",
        };
    }
    /**
     * Set the elicitation handler
     */
    setHandler(handler) {
        this.config.handler = handler;
    }
    /**
     * Enable or disable elicitation
     */
    setEnabled(enabled) {
        this.config.enabled = enabled;
        if (!enabled) {
            // Resolve all pending requests with timeout/default
            for (const [requestId, pending] of this.pendingRequests) {
                if (pending.settled) {
                    continue;
                }
                pending.settled = true;
                this.handleDisabled(pending.request, pending.resolve);
                if (pending.timer) {
                    clearTimeout(pending.timer);
                }
                this.pendingRequests.delete(requestId);
            }
        }
    }
    /**
     * Check if elicitation is enabled
     */
    isEnabled() {
        return this.config.enabled;
    }
    /**
     * Request user input
     */
    async request(elicitation) {
        const request = {
            ...elicitation,
            id: elicitation.id ?? randomUUID(),
        };
        // If disabled, handle according to fallback behavior
        if (!this.config.enabled) {
            return this.handleDisabledRequest(request);
        }
        const timeout = request.timeout ?? this.config.defaultTimeout;
        return new Promise((resolve) => {
            // Set up timeout
            const timer = setTimeout(() => {
                const pending = this.pendingRequests.get(request.id);
                if (!pending || pending.settled) {
                    return;
                }
                pending.settled = true;
                this.handleTimeout(request, resolve);
            }, timeout);
            // Store pending request with shared settled flag
            this.pendingRequests.set(request.id, {
                request,
                resolve,
                timer,
                settled: false,
            });
            // Emit request event
            this.emit("elicitationRequested", request);
            // Call handler (wrapped to catch synchronous throws)
            Promise.resolve()
                .then(() => this.config.handler(request))
                .then((response) => {
                const pending = this.pendingRequests.get(request.id);
                if (!pending || pending.settled) {
                    return;
                }
                pending.settled = true;
                clearTimeout(timer);
                this.pendingRequests.delete(request.id);
                this.emit("elicitationResponded", response);
                resolve(response);
            })
                .catch((error) => {
                const pending = this.pendingRequests.get(request.id);
                if (!pending || pending.settled) {
                    return;
                }
                pending.settled = true;
                clearTimeout(timer);
                this.pendingRequests.delete(request.id);
                const errorResponse = {
                    requestId: request.id,
                    responded: false,
                    error: error instanceof Error ? error.message : String(error),
                    timestamp: Date.now(),
                };
                this.emit("elicitationError", { request, error });
                resolve(errorResponse);
            });
        });
    }
    /**
     * Convenience method for confirmation requests
     */
    async confirm(message, options) {
        const request = {
            type: "confirmation",
            message,
            toolName: options?.toolName ?? "unknown",
            serverId: options?.serverId,
            confirmLabel: options?.confirmLabel,
            cancelLabel: options?.cancelLabel,
            timeout: options?.timeout,
        };
        const response = await this.request(request);
        return response.value === true;
    }
    /**
     * Convenience method for text input
     */
    async getText(message, options) {
        const request = {
            type: "text",
            message,
            toolName: options?.toolName ?? "unknown",
            placeholder: options?.placeholder,
            defaultValue: options?.defaultValue,
            timeout: options?.timeout,
        };
        const response = await this.request(request);
        return response.value;
    }
    /**
     * Convenience method for selection
     */
    async select(message, options, config) {
        const request = {
            type: "select",
            message,
            toolName: config?.toolName ?? "unknown",
            options: options,
            timeout: config?.timeout,
        };
        const response = await this.request(request);
        return response.value;
    }
    /**
     * Convenience method for multiple selection
     */
    async multiSelect(message, options, config) {
        const request = {
            type: "multiselect",
            message,
            toolName: config?.toolName ?? "unknown",
            options: options,
            timeout: config?.timeout,
            minSelections: config?.minSelections,
            maxSelections: config?.maxSelections,
        };
        const response = await this.request(request);
        return response.value;
    }
    /**
     * Convenience method for form input
     */
    async form(message, fields, config) {
        const request = {
            type: "form",
            message,
            toolName: config?.toolName ?? "unknown",
            serverId: config?.serverId,
            fields,
            submitLabel: config?.submitLabel,
            timeout: config?.timeout,
        };
        const response = await this.request(request);
        return response.value;
    }
    /**
     * Convenience method for secret input
     */
    async getSecret(message, options) {
        const request = {
            type: "secret",
            message,
            toolName: options?.toolName ?? "unknown",
            hint: options?.hint,
            timeout: options?.timeout,
        };
        const response = await this.request(request);
        return response.value;
    }
    /**
     * Cancel a pending request
     */
    cancel(requestId, reason) {
        const pending = this.pendingRequests.get(requestId);
        if (pending) {
            if (pending.settled) {
                return;
            }
            pending.settled = true;
            if (pending.timer) {
                clearTimeout(pending.timer);
            }
            const response = {
                requestId,
                responded: false,
                cancelled: true,
                error: reason,
                timestamp: Date.now(),
            };
            pending.resolve(response);
            this.pendingRequests.delete(requestId);
            this.emit("elicitationCancelled", { requestId, reason });
        }
    }
    /**
     * Default handler when none is provided
     */
    async defaultHandler(request) {
        logger.warn(`[ElicitationManager] No handler for elicitation request: ${request.id}`);
        // If there's a default value, use it
        if (request.defaultValue !== undefined) {
            return {
                requestId: request.id,
                responded: true,
                value: request.defaultValue,
                timestamp: Date.now(),
            };
        }
        // Otherwise, return not responded
        return {
            requestId: request.id,
            responded: false,
            error: "No elicitation handler configured",
            timestamp: Date.now(),
        };
    }
    /**
     * Handle timeout
     */
    handleTimeout(request, resolve) {
        this.pendingRequests.delete(request.id);
        const response = {
            requestId: request.id,
            responded: false,
            timedOut: true,
            value: request.defaultValue,
            timestamp: Date.now(),
        };
        this.emit("elicitationTimeout", { request });
        resolve(response);
    }
    /**
     * Handle disabled elicitation
     */
    handleDisabled(request, resolve) {
        resolve(this.handleDisabledRequest(request));
    }
    /**
     * Handle disabled request based on fallback behavior
     */
    handleDisabledRequest(request) {
        switch (this.config.fallbackBehavior) {
            case "default":
                return {
                    requestId: request.id,
                    responded: request.defaultValue !== undefined,
                    value: request.defaultValue,
                    timestamp: Date.now(),
                };
            case "error":
                return {
                    requestId: request.id,
                    responded: false,
                    error: "Elicitation is disabled",
                    timestamp: Date.now(),
                };
            case "timeout":
            default:
                return {
                    requestId: request.id,
                    responded: false,
                    timedOut: true,
                    value: request.defaultValue,
                    timestamp: Date.now(),
                };
        }
    }
    /**
     * Get pending request count
     */
    getPendingCount() {
        return this.pendingRequests.size;
    }
    /**
     * Get all pending requests
     */
    getPendingRequests() {
        return Array.from(this.pendingRequests.values()).map((p) => p.request);
    }
    /**
     * Clear all pending requests
     */
    clearPending(reason) {
        for (const [requestId] of this.pendingRequests) {
            this.cancel(requestId, reason ?? "Cleared");
        }
    }
}
/**
 * Global elicitation manager instance
 */
export const globalElicitationManager = new ElicitationManager();
//# sourceMappingURL=elicitationManager.js.map