/**
 * RPC mode: Headless operation with JSON stdin/stdout protocol.
 *
 * Used for embedding the agent in other applications.
 * Receives commands as JSON on stdin, outputs events and responses as JSON on stdout.
 *
 * Protocol:
 * - Commands: JSON objects with `type` field, optional `id` for correlation
 * - Responses: JSON objects with `type: "response"`, `command`, `success`, and optional `data`/`error`
 * - Events: AgentSessionEvent objects streamed as they occur
 * - Extension UI: Extension UI requests are emitted, client responds with extension_ui_response
 */
import * as crypto from 'node:crypto';
import * as readline from 'readline';
import { getLastAssistantText } from '../app/session/assistant-text.js';
import { exportSessionToHtml } from '../shell/export-html/index.js';
/**
 * Run in RPC mode.
 * Listens for JSON commands on stdin, outputs events and responses on stdout.
 */
export async function runRpcMode(session) {
    const output = (obj) => {
        console.log(JSON.stringify(obj));
    };
    const success = (id, command, data) => {
        if (data === undefined) {
            return { id, type: 'response', command, success: true };
        }
        return { id, type: 'response', command, success: true, data };
    };
    const error = (id, command, message) => {
        return { id, type: 'response', command, success: false, error: message };
    };
    // Pending extension UI requests waiting for response
    const pendingExtensionRequests = new Map();
    // Shutdown request flag
    let shutdownRequested = false;
    /** Helper for dialog methods with signal/timeout support */
    function createDialogPromise(opts, defaultValue, request, parseResponse) {
        if (opts?.signal?.aborted)
            return Promise.resolve(defaultValue);
        const id = crypto.randomUUID();
        return new Promise((resolve, reject) => {
            let timeoutId;
            const cleanup = () => {
                if (timeoutId)
                    clearTimeout(timeoutId);
                opts?.signal?.removeEventListener('abort', onAbort);
                pendingExtensionRequests.delete(id);
            };
            const onAbort = () => {
                cleanup();
                resolve(defaultValue);
            };
            opts?.signal?.addEventListener('abort', onAbort, { once: true });
            if (opts?.timeout) {
                timeoutId = setTimeout(() => {
                    cleanup();
                    resolve(defaultValue);
                }, opts.timeout);
            }
            pendingExtensionRequests.set(id, {
                resolve: (response) => {
                    cleanup();
                    resolve(parseResponse(response));
                },
                reject
            });
            output({ type: 'extension_ui_request', id, ...request });
        });
    }
    /**
     * Create an extension UI context that uses the RPC protocol.
     */
    const createExtensionUIContext = () => ({
        select: (title, options, opts) => createDialogPromise(opts, undefined, { method: 'select', title, options, timeout: opts?.timeout }, (r) => ('cancelled' in r && r.cancelled ? undefined : 'value' in r ? r.value : undefined)),
        confirm: (title, message, opts) => createDialogPromise(opts, false, { method: 'confirm', title, message, timeout: opts?.timeout }, (r) => ('cancelled' in r && r.cancelled ? false : 'confirmed' in r ? r.confirmed : false)),
        input: (title, placeholder, opts) => createDialogPromise(opts, undefined, { method: 'input', title, placeholder, timeout: opts?.timeout }, (r) => ('cancelled' in r && r.cancelled ? undefined : 'value' in r ? r.value : undefined)),
        notify(message, type) {
            // Fire and forget - no response needed
            output({
                type: 'extension_ui_request',
                id: crypto.randomUUID(),
                method: 'notify',
                message,
                notifyType: type
            });
        }
    });
    // Set up extensions with RPC-based UI context
    await session.bindExtensions({
        uiContext: createExtensionUIContext(),
        commandContextActions: {
            waitForIdle: () => session.agent.waitForIdle(),
            newSession: async (options) => {
                // Delegate to AgentSession (handles setup + agent state sync)
                const success = await session.newSession(options);
                return { cancelled: !success };
            },
            fork: async (entryId) => {
                const result = await session.fork(entryId);
                return { cancelled: result.cancelled };
            },
            navigateTree: async (targetId, options) => {
                const result = session.navigateTree(targetId, {
                    label: options?.label
                });
                return { cancelled: result.cancelled };
            },
            switchSession: async (sessionPath) => {
                const success = await session.switchSession(sessionPath);
                return { cancelled: !success };
            },
            reload: async () => {
                await session.reload();
            }
        },
        shutdownHandler: () => {
            shutdownRequested = true;
        },
        onError: (err) => {
            output({
                type: 'extension_error',
                extensionPath: err.extensionPath,
                event: err.event,
                error: err.error
            });
        }
    });
    // Output all agent events as JSON
    session.subscribe((event) => {
        output(event);
    });
    // Handle a single command
    const handleCommand = async (command) => {
        const id = command.id;
        switch (command.type) {
            // =================================================================
            // Prompting
            // =================================================================
            case 'prompt': {
                // Merge file uploads into session state before running the agent
                if (command.files && command.files.length > 0) {
                    session.addFileUploads(command.files);
                }
                // Don't await - events will stream
                // Extension commands are executed immediately, file prompt templates are expanded
                // If streaming, queues via steer by default; streamingBehavior can request follow-up.
                session
                    .prompt(command.message, {
                    images: command.images,
                    streamingBehavior: command.streamingBehavior,
                    source: 'rpc'
                })
                    .catch((e) => output(error(id, 'prompt', e.message)));
                return success(id, 'prompt');
            }
            case 'steer': {
                await session.steer(command.message, command.images);
                return success(id, 'steer');
            }
            case 'follow_up': {
                await session.followUp(command.message, command.images);
                return success(id, 'follow_up');
            }
            case 'abort': {
                await session.abort();
                return success(id, 'abort');
            }
            case 'new_session': {
                const options = command.parentSession
                    ? { parentSession: command.parentSession }
                    : undefined;
                const cancelled = !(await session.newSession(options));
                return success(id, 'new_session', { cancelled });
            }
            // =================================================================
            // State
            // =================================================================
            case 'get_state': {
                const state = {
                    model: session.model,
                    thinkingLevel: session.thinkingLevel,
                    isStreaming: session.isStreaming,
                    isCompacting: session.isCompacting,
                    steeringMode: session.steeringMode,
                    followUpMode: session.followUpMode,
                    sessionFile: session.sessionFile,
                    sessionId: session.sessionId,
                    sessionName: session.sessionName,
                    autoCompactionEnabled: true,
                    messageCount: session.messages.length,
                    pendingMessageCount: session.pendingMessageCount
                };
                return success(id, 'get_state', state);
            }
            // =================================================================
            // Model
            // =================================================================
            case 'set_model': {
                const models = await session.modelRegistry.getAvailable();
                const model = models.find((m) => m.provider === command.provider && m.id === command.modelId);
                if (!model) {
                    return error(id, 'set_model', `Model not found: ${command.provider}/${command.modelId}`);
                }
                await session.setModel(model);
                return success(id, 'set_model', model);
            }
            case 'cycle_model': {
                const result = await session.cycleModel();
                if (!result) {
                    return success(id, 'cycle_model', null);
                }
                return success(id, 'cycle_model', result);
            }
            case 'get_available_models': {
                const models = await session.modelRegistry.getAvailable();
                return success(id, 'get_available_models', { models });
            }
            // =================================================================
            // Thinking
            // =================================================================
            case 'set_thinking_level': {
                session.setThinkingLevel(command.level);
                return success(id, 'set_thinking_level');
            }
            case 'cycle_thinking_level': {
                const level = session.cycleThinkingLevel();
                if (!level) {
                    return success(id, 'cycle_thinking_level', null);
                }
                return success(id, 'cycle_thinking_level', { level });
            }
            // =================================================================
            // Compaction
            // =================================================================
            case 'compact': {
                const result = await session.compact(command.customInstructions);
                return success(id, 'compact', result);
            }
            // =================================================================
            // Retry
            // =================================================================
            case 'set_auto_retry': {
                session.setAutoRetryEnabled(command.enabled);
                return success(id, 'set_auto_retry');
            }
            case 'abort_retry': {
                session.abortRetry();
                return success(id, 'abort_retry');
            }
            // =================================================================
            // Bash
            // =================================================================
            case 'bash': {
                // Bash execution removed from session — use bash-executor directly if needed
                return error(id, 'bash', 'Bash execution via RPC is no longer supported');
            }
            case 'abort_bash': {
                return error(id, 'abort_bash', 'Bash execution via RPC is no longer supported');
            }
            // =================================================================
            // Session
            // =================================================================
            case 'get_session_stats': {
                const stats = session.getSessionStats();
                return success(id, 'get_session_stats', stats);
            }
            case 'export_html': {
                const path = await exportSessionToHtml(session, command.outputPath);
                return success(id, 'export_html', { path });
            }
            case 'switch_session': {
                const cancelled = !(await session.switchSession(command.sessionPath));
                return success(id, 'switch_session', { cancelled });
            }
            case 'fork': {
                const result = await session.fork(command.entryId);
                return success(id, 'fork', { text: result.selectedText, cancelled: result.cancelled });
            }
            case 'get_fork_messages': {
                const messages = session.getUserMessagesForForking();
                return success(id, 'get_fork_messages', { messages });
            }
            case 'get_last_assistant_text': {
                const text = getLastAssistantText(session.messages);
                return success(id, 'get_last_assistant_text', { text });
            }
            case 'set_session_name': {
                const name = command.name.trim();
                if (!name) {
                    return error(id, 'set_session_name', 'Session name cannot be empty');
                }
                session.setSessionName(name);
                return success(id, 'set_session_name');
            }
            // =================================================================
            // Messages
            // =================================================================
            case 'get_messages': {
                return success(id, 'get_messages', { messages: session.messages });
            }
            // =================================================================
            // Commands (available for invocation via prompt)
            // =================================================================
            case 'get_commands': {
                const commands = [];
                // Extension commands
                for (const { command, extensionPath } of session.extensionRunner?.getRegisteredCommandsWithPaths() ?? []) {
                    commands.push({
                        name: command.name,
                        description: command.description,
                        source: 'extension',
                        path: extensionPath
                    });
                }
                // Prompt templates (source is always "user" | "project" | "path" in coding-agent)
                for (const template of session.promptTemplates) {
                    commands.push({
                        name: template.name,
                        description: template.description,
                        source: 'prompt',
                        location: template.source,
                        path: template.filePath
                    });
                }
                // Skills (source is always "user" | "project" | "path" in coding-agent)
                for (const skill of session.resourceLoader.getSkills().skills) {
                    commands.push({
                        name: `skill:${skill.name}`,
                        description: skill.description,
                        source: 'skill',
                        location: skill.source,
                        path: skill.filePath
                    });
                }
                return success(id, 'get_commands', { commands });
            }
            default: {
                const unknownCommand = command;
                return error(undefined, unknownCommand.type, `Unknown command: ${unknownCommand.type}`);
            }
        }
    };
    /**
     * Check if shutdown was requested and perform shutdown if so.
     * Called after handling each command when waiting for the next command.
     */
    async function checkShutdownRequested() {
        if (!shutdownRequested)
            return;
        const currentRunner = session.extensionRunner;
        if (currentRunner?.hasHandlers('session_shutdown')) {
            await currentRunner.emit({ type: 'session_shutdown' });
        }
        // Close readline interface to stop waiting for input
        rl.close();
        process.exit(0);
    }
    // Listen for JSON input
    const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout,
        terminal: false
    });
    rl.on('line', async (line) => {
        try {
            const parsed = JSON.parse(line);
            // Handle extension UI responses
            if (parsed.type === 'extension_ui_response') {
                const response = parsed;
                const pending = pendingExtensionRequests.get(response.id);
                if (pending) {
                    pendingExtensionRequests.delete(response.id);
                    pending.resolve(response);
                }
                return;
            }
            // Handle regular commands
            const command = parsed;
            const response = await handleCommand(command);
            output(response);
            // Check for deferred shutdown request (idle between commands)
            await checkShutdownRequested();
        }
        catch (e) {
            output(error(undefined, 'parse', `Failed to parse command: ${e.message}`));
        }
    });
    // Keep process alive forever
    return new Promise(() => { });
}
//# sourceMappingURL=rpc-mode.js.map