/**
 * @license
 * Copyright 2025 Vybestack LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import { getErrorMessage, getMCPServerPrompts, } from '@vybestack/llxprt-code-core';
import { CommandKind, } from '../ui/commands/types.js';
/**
 * Discovers and loads executable slash commands from prompts exposed by
 * Model-Context-Protocol (MCP) servers.
 */
export class McpPromptLoader {
    config;
    constructor(config) {
        this.config = config;
    }
    /**
     * Loads all available prompts from all configured MCP servers and adapts
     * them into executable SlashCommand objects.
     *
     * @param _signal An AbortSignal (unused for this synchronous loader).
     * @returns A promise that resolves to an array of loaded SlashCommands.
     */
    loadCommands(_signal) {
        const promptCommands = [];
        if (!this.config) {
            return Promise.resolve([]);
        }
        const mcpServers = this.config.getMcpServers() || {};
        for (const serverName in mcpServers) {
            const prompts = getMCPServerPrompts(this.config, serverName) || [];
            for (const prompt of prompts) {
                const commandName = `${prompt.name}`;
                const newPromptCommand = {
                    name: commandName,
                    description: prompt.description || `Invoke prompt ${prompt.name}`,
                    kind: CommandKind.MCP_PROMPT,
                    subCommands: [
                        {
                            name: 'help',
                            description: 'Show help for this prompt',
                            kind: CommandKind.MCP_PROMPT,
                            action: async () => {
                                if (!prompt.arguments || prompt.arguments.length === 0) {
                                    return {
                                        type: 'message',
                                        messageType: 'info',
                                        content: `Prompt "${prompt.name}" has no arguments.`,
                                    };
                                }
                                let helpMessage = `Arguments for "${prompt.name}":\n\n`;
                                if (prompt.arguments && prompt.arguments.length > 0) {
                                    helpMessage += `You can provide arguments by name (e.g., --argName="value") or by position.\n\n`;
                                    helpMessage += `e.g., ${prompt.name} ${prompt.arguments?.map((_) => `"foo"`)} is equivalent to ${prompt.name} ${prompt.arguments?.map((arg) => `--${arg.name}="foo"`)}\n\n`;
                                }
                                for (const arg of prompt.arguments) {
                                    helpMessage += `  --${arg.name}\n`;
                                    if (arg.description) {
                                        helpMessage += `    ${arg.description}\n`;
                                    }
                                    helpMessage += `    (required: ${arg.required ? 'yes' : 'no'})\n\n`;
                                }
                                return {
                                    type: 'message',
                                    messageType: 'info',
                                    content: helpMessage,
                                };
                            },
                        },
                    ],
                    action: async (context, args) => {
                        if (!this.config) {
                            return {
                                type: 'message',
                                messageType: 'error',
                                content: 'Config not loaded.',
                            };
                        }
                        const promptInputs = this.parseArgs(args, prompt.arguments);
                        if (promptInputs instanceof Error) {
                            return {
                                type: 'message',
                                messageType: 'error',
                                content: promptInputs.message,
                            };
                        }
                        try {
                            const mcpServers = this.config.getMcpServers() || {};
                            const mcpServerConfig = mcpServers[serverName];
                            if (!mcpServerConfig) {
                                return {
                                    type: 'message',
                                    messageType: 'error',
                                    content: `MCP server config not found for '${serverName}'.`,
                                };
                            }
                            const result = await prompt.invoke(promptInputs);
                            if (result.error) {
                                return {
                                    type: 'message',
                                    messageType: 'error',
                                    content: `Error invoking prompt: ${result.error}`,
                                };
                            }
                            const responseText = McpPromptLoader.extractFirstTextContent(result.messages);
                            if (!responseText) {
                                return {
                                    type: 'message',
                                    messageType: 'error',
                                    content: 'Received an empty or invalid prompt response from the server.',
                                };
                            }
                            return {
                                type: 'submit_prompt',
                                content: JSON.stringify(responseText),
                            };
                        }
                        catch (error) {
                            return {
                                type: 'message',
                                messageType: 'error',
                                content: `Error: ${getErrorMessage(error)}`,
                            };
                        }
                    },
                    // TODO: Re-enable schema-based argument completion when SlashCommand interface supports it
                    // schema: [
                    //   {
                    //     kind: 'value',
                    //     name: 'argument',
                    //     description: 'Provide prompt argument (--name="value")',
                    //     completer: async (_ctx: CommandContext, partialArg: string, tokenInfo: TokenInfo) => {
                    //       if (!prompt || !prompt.arguments) {
                    //         return [];
                    //       }
                    //       const usedArgNames = new Set(
                    //         [...tokenInfo.tokens, tokenInfo.partialToken]
                    //           .filter(
                    //             (token): token is string =>
                    //               typeof token === 'string' && token.startsWith('--'),
                    //           )
                    //           .map((token) => token.slice(2).split('=')[0]),
                    //       );
                    //       const normalizedPartial = partialArg.toLowerCase();
                    //       return prompt.arguments
                    //         .filter((arg) => !usedArgNames.has(arg.name))
                    //         .map((arg) => {
                    //           const value = `--${arg.name}=""`;
                    //           return {
                    //             value,
                    //             description:
                    //               arg.description || 'Prompt argument placeholder',
                    //           };
                    //         })
                    //         .filter((option) =>
                    //           normalizedPartial.length === 0
                    //             ? true
                    //             : option.value
                    //                 .toLowerCase()
                    //                 .startsWith(normalizedPartial),
                    //         );
                    //     },
                    //   },
                    // ],
                };
                promptCommands.push(newPromptCommand);
            }
        }
        return Promise.resolve(promptCommands);
    }
    static extractFirstTextContent(messages) {
        const firstContent = messages?.[0]?.content;
        if (firstContent && firstContent.type === 'text') {
            return firstContent.text;
        }
        return null;
    }
    /**
     * Parses the `userArgs` string representing the prompt arguments (all the text
     * after the command) into a record matching the shape of the `promptArgs`.
     *
     * @param userArgs
     * @param promptArgs
     * @returns A record of the parsed arguments
     * @visibleForTesting
     */
    parseArgs(userArgs, promptArgs) {
        const argValues = {};
        const promptInputs = {};
        // arg parsing: --key="value" or --key=value
        const namedArgRegex = /--([^=]+)=(?:"((?:\\.|[^"\\])*)"|([^ ]+))/g;
        let match;
        let lastIndex = 0;
        const positionalParts = [];
        while ((match = namedArgRegex.exec(userArgs)) !== null) {
            const key = match[1];
            // Extract the quoted or unquoted argument and remove escape chars.
            const value = (match[2] ?? match[3]).replace(/\\(.)/g, '$1');
            argValues[key] = value;
            // Capture text between matches as potential positional args
            if (match.index > lastIndex) {
                positionalParts.push(userArgs.substring(lastIndex, match.index));
            }
            lastIndex = namedArgRegex.lastIndex;
        }
        // Capture any remaining text after the last named arg
        if (lastIndex < userArgs.length) {
            positionalParts.push(userArgs.substring(lastIndex));
        }
        const positionalArgsString = positionalParts.join('').trim();
        // extracts either quoted strings or non-quoted sequences of non-space characters.
        const positionalArgRegex = /(?:"((?:\\.|[^"\\])*)"|([^ ]+))/g;
        const positionalArgs = [];
        while ((match = positionalArgRegex.exec(positionalArgsString)) !== null) {
            // Extract the quoted or unquoted argument and remove escape chars.
            positionalArgs.push((match[1] ?? match[2]).replace(/\\(.)/g, '$1'));
        }
        if (!promptArgs) {
            return promptInputs;
        }
        for (const arg of promptArgs) {
            if (argValues[arg.name]) {
                promptInputs[arg.name] = argValues[arg.name];
            }
        }
        const unfilledArgs = promptArgs.filter((arg) => arg.required && !promptInputs[arg.name]);
        if (unfilledArgs.length === 1) {
            // If we have only one unfilled arg, we don't require quotes we just
            // join all the given arguments together as if they were quoted.
            promptInputs[unfilledArgs[0].name] = positionalArgs.join(' ');
        }
        else {
            const missingArgs = [];
            for (let i = 0; i < unfilledArgs.length; i++) {
                if (positionalArgs.length > i) {
                    promptInputs[unfilledArgs[i].name] = positionalArgs[i];
                }
                else {
                    missingArgs.push(unfilledArgs[i].name);
                }
            }
            if (missingArgs.length > 0) {
                const missingArgNames = missingArgs
                    .map((name) => `--${name}`)
                    .join(', ');
                return new Error(`Missing required argument(s): ${missingArgNames}`);
            }
        }
        return promptInputs;
    }
}
//# sourceMappingURL=McpPromptLoader.js.map