/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import { parseSlashCommand } from './utils/commands.js';
import { Logger, uiTelemetryService, createDebugLogger, } from '@hoptrendy/hopcode-core';
import { CommandService } from './services/CommandService.js';
import { BuiltinCommandLoader } from './services/BuiltinCommandLoader.js';
import { BundledSkillLoader } from './services/BundledSkillLoader.js';
import { FileCommandLoader } from './services/FileCommandLoader.js';
import { SkillCommandLoader } from './services/SkillCommandLoader.js';
import {} from './ui/commands/types.js';
import { createNonInteractiveUI } from './ui/noninteractive/nonInteractiveUi.js';
import { t } from './i18n/index.js';
const debugLogger = createDebugLogger('NON_INTERACTIVE_COMMANDS');
/**
 * Converts a SlashCommandActionReturn to a NonInteractiveSlashCommandResult.
 *
 * Only the following result types are supported in non-interactive mode:
 * - submit_prompt: Submits content to the model (all modes)
 * - message: Returns a single message (non-interactive JSON/text only)
 * - stream_messages: Streams multiple messages (ACP only)
 *
 * All other result types are converted to 'unsupported'.
 *
 * @param result The result from executing a slash command action
 * @returns A NonInteractiveSlashCommandResult describing the outcome
 */
function handleCommandResult(result) {
    switch (result.type) {
        case 'submit_prompt':
            return {
                type: 'submit_prompt',
                content: result.content,
            };
        case 'message':
            return {
                type: 'message',
                messageType: result.messageType === 'warning' || result.messageType === 'success'
                    ? 'info'
                    : result.messageType,
                content: result.content,
            };
        case 'stream_messages':
            return {
                type: 'stream_messages',
                messages: result.messages,
            };
        /**
         * Currently return types below are never generated due to the
         * whitelist of allowed slash commands in ACP and non-interactive mode.
         * We'll try to add more supported return types in the future.
         */
        case 'tool':
            return {
                type: 'unsupported',
                reason: 'Tool execution from slash commands is not supported in non-interactive mode.',
                originalType: 'tool',
            };
        case 'quit':
            return {
                type: 'unsupported',
                reason: 'Quit command is not supported in non-interactive mode. The process will exit naturally after completion.',
                originalType: 'quit',
            };
        case 'dialog':
            return {
                type: 'unsupported',
                reason: `Dialog '${result.dialog}' cannot be opened in non-interactive mode.`,
                originalType: 'dialog',
            };
        case 'load_history':
            return {
                type: 'unsupported',
                reason: 'Loading history is not supported in non-interactive mode. Each invocation starts with a fresh context.',
                originalType: 'load_history',
            };
        case 'confirm_shell_commands':
            return {
                type: 'unsupported',
                reason: 'Shell command confirmation is not supported in non-interactive mode. Use YOLO mode or pre-approve commands.',
                originalType: 'confirm_shell_commands',
            };
        case 'startImmediateSubagent':
            return {
                type: 'unsupported',
                reason: 'Immediate subagent launch is not supported in non-interactive mode.',
                originalType: 'startImmediateSubagent',
            };
        case 'confirm_action':
            return {
                type: 'unsupported',
                reason: 'Action confirmation is not supported in non-interactive mode. Commands requiring confirmation cannot be executed.',
                originalType: 'confirm_action',
            };
        default: {
            // Exhaustiveness check
            const _exhaustive = result;
            return {
                type: 'unsupported',
                reason: `Unknown command result type: ${_exhaustive.type}`,
                originalType: 'unknown',
            };
        }
    }
}
/**
 * Processes a slash command in a non-interactive environment.
 *
 * @param rawQuery The raw query string (should start with '/')
 * @param abortController Controller to cancel the operation
 * @param config The configuration object
 * @param settings The loaded settings
 * @returns A Promise that resolves to a `NonInteractiveSlashCommandResult` describing
 *   the outcome of the command execution.
 */
export const handleSlashCommand = async (rawQuery, abortController, config, settings) => {
    const trimmed = rawQuery.trim();
    if (!trimmed.startsWith('/')) {
        return { type: 'no_command' };
    }
    const isAcpMode = config.getExperimentalZedIntegration();
    const isInteractive = config.isInteractive();
    const executionMode = isAcpMode
        ? 'acp'
        : isInteractive
            ? 'interactive'
            : 'non_interactive';
    // Load all commands to check if the command exists but is not allowed
    const allLoaders = [
        new BuiltinCommandLoader(config),
        new BundledSkillLoader(config),
        new SkillCommandLoader(config),
        new FileCommandLoader(config),
    ];
    // Build the disabled-command set (case-insensitive).
    const disabledSlashCommandsRaw = config.getDisabledSlashCommands();
    const disabledNameSet = new Set();
    for (const name of disabledSlashCommandsRaw) {
        const trimmed = name.trim();
        if (trimmed)
            disabledNameSet.add(trimmed.toLowerCase());
    }
    const isDisabled = (cmd) => disabledNameSet.has(cmd.name.toLowerCase()) ||
        (cmd.altNames ?? []).some((a) => disabledNameSet.has(a.toLowerCase()));
    // Load the full command set (unfiltered by the denylist) so that the
    // fallback existence check below can distinguish a disabled command from a
    // truly unknown one. Without this, a disabled command would fall through to
    // `no_command` and be forwarded to the model as plain prompt text.
    const commandService = await CommandService.create(allLoaders, abortController.signal);
    // Register model-invocable commands provider so SkillTool description stays
    // up-to-date in non-interactive / ACP mode.
    config.setModelInvocableCommandsProvider(() => commandService.getModelInvocableCommands().map((cmd) => ({
        name: cmd.name,
        description: typeof cmd.description === 'string' ? cmd.description : cmd.description,
    })));
    // Register executor so SkillTool can invoke model-invocable commands
    // (e.g. MCP prompts) that are not file-based skills.
    config.setModelInvocableCommandsExecutor(async (name, args = '') => {
        const commands = commandService.getModelInvocableCommands();
        const cmd = commands.find((c) => c.name === name);
        if (!cmd?.action)
            return null;
        const minimalContext = {
            executionMode,
            invocation: {
                raw: args ? `/${name} ${args}` : `/${name}`,
                name,
                args,
            },
            services: { config, settings, git: undefined, logger: null },
        };
        const result = await cmd.action(minimalContext, args);
        if (!result || result.type !== 'submit_prompt')
            return null;
        const content = result.content;
        if (typeof content === 'string')
            return content;
        if (Array.isArray(content)) {
            return content
                .map((p) => typeof p === 'string' ? p : (p.text ?? ''))
                .join('');
        }
        return null;
    });
    const allCommands = commandService.getCommands();
    const filteredCommands = commandService
        .getCommandsForMode(executionMode)
        .filter((cmd) => !isDisabled(cmd));
    // First, try to parse with filtered commands
    const { commandToExecute, args } = parseSlashCommand(rawQuery, filteredCommands);
    if (!commandToExecute) {
        // Check if this is a known command that's just not allowed
        const { commandToExecute: knownCommand } = parseSlashCommand(rawQuery, allCommands);
        if (knownCommand) {
            // Derive the token the user actually typed (e.g. "about" when the
            // primary name is "status") to surface a helpful error message.
            const typedToken = rawQuery.trim().substring(1).trim().split(/\s+/)[0] ??
                knownCommand.name;
            if (isDisabled(knownCommand)) {
                return {
                    type: 'unsupported',
                    reason: t('The command "/{{command}}" is disabled by the current configuration.', { command: typedToken }),
                    originalType: 'filtered_command',
                };
            }
            // Command exists but is not allowed in this mode
            return {
                type: 'unsupported',
                reason: t('The command "/{{command}}" is not supported in this mode.', {
                    command: typedToken,
                }),
                originalType: 'filtered_command',
            };
        }
        return { type: 'no_command' };
    }
    if (!commandToExecute.action) {
        return { type: 'no_command' };
    }
    // Not used by custom commands but may be in the future.
    const sessionStats = {
        sessionId: config?.getSessionId(),
        sessionStartTime: new Date(),
        metrics: uiTelemetryService.getMetrics(),
        lastPromptTokenCount: 0,
        promptCount: 1,
    };
    const logger = new Logger(config?.getSessionId() || '', config?.storage);
    const context = {
        executionMode,
        services: {
            config,
            settings,
            git: undefined,
            logger,
        },
        ui: createNonInteractiveUI(),
        session: {
            stats: sessionStats,
            sessionShellAllowlist: new Set(),
        },
        invocation: {
            raw: trimmed,
            name: commandToExecute.name,
            args,
        },
    };
    const result = await commandToExecute.action(context, args);
    if (!result) {
        // Command executed but returned no result (e.g., void return)
        return {
            type: 'message',
            messageType: 'info',
            content: 'Command executed successfully.',
        };
    }
    // Handle different result types
    return handleCommandResult(result);
};
/**
 * Retrieves all available slash commands for the given execution mode.
 *
 * @param config The configuration object
 * @param abortSignal Signal to cancel the loading process
 * @param mode The execution mode to filter commands for. Defaults to 'acp'.
 * @returns A Promise that resolves to an array of SlashCommand objects
 */
export const getAvailableCommands = async (config, abortSignal, mode = 'acp') => {
    try {
        const loaders = [
            new BuiltinCommandLoader(config),
            new BundledSkillLoader(config),
            new SkillCommandLoader(config),
            new FileCommandLoader(config),
        ];
        const disabledSlashCommands = config.getDisabledSlashCommands();
        const commandService = await CommandService.create(loaders, abortSignal, disabledSlashCommands.length > 0
            ? new Set(disabledSlashCommands)
            : undefined);
        return commandService.getCommandsForMode(mode);
    }
    catch (error) {
        // Handle errors gracefully - log and return empty array
        debugLogger.error('Error loading available commands:', error);
        return [];
    }
};
//# sourceMappingURL=nonInteractiveCliCommands.js.map