/**
 * Transport-agnostic RPC handler.
 *
 * Extracted from rpc-mode.ts so the same RPC protocol can run over:
 * - stdin/stdout (original rpc-mode)
 * - WebSocket (serve-mode)
 * - Any other bidirectional transport
 */
import * as crypto from "node:crypto";
import { theme } from "../interactive/theme/theme.js";
/**
 * Run the full RPC protocol over an arbitrary transport.
 * Returns when the transport closes.
 */
export async function runRpcOverTransport(session, transport) {
    const output = (obj) => {
        transport.send(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 };
    };
    const pendingExtensionRequests = new Map();
    let _shutdownRequested = false;
    let closed = false;
    function createDialogPromise(opts, defaultValue, request, parseResponse) {
        if (opts?.signal?.aborted)
            return Promise.resolve(defaultValue);
        const id = crypto.randomUUID();
        return new Promise((resolve) => {
            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: () => {
                    cleanup();
                    resolve(defaultValue);
                },
            });
            output({ type: "extension_ui_request", id, ...request });
        });
    }
    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) {
            output({
                type: "extension_ui_request",
                id: crypto.randomUUID(),
                method: "notify",
                message,
                notifyType: type,
            });
        },
        onTerminalInput() {
            return () => { };
        },
        setStatus(key, text) {
            output({
                type: "extension_ui_request",
                id: crypto.randomUUID(),
                method: "setStatus",
                statusKey: key,
                statusText: text,
            });
        },
        setWorkingMessage() { },
        setHiddenThinkingLabel() { },
        setWidget(key, content, options) {
            if (content === undefined || Array.isArray(content)) {
                output({
                    type: "extension_ui_request",
                    id: crypto.randomUUID(),
                    method: "setWidget",
                    widgetKey: key,
                    widgetLines: content,
                    widgetPlacement: options?.placement,
                });
            }
        },
        setFooter() { },
        setHeader() { },
        setTitle(title) {
            output({
                type: "extension_ui_request",
                id: crypto.randomUUID(),
                method: "setTitle",
                title,
            });
        },
        async custom() {
            return undefined;
        },
        pasteToEditor(text) {
            this.setEditorText(text);
        },
        setEditorText(text) {
            output({
                type: "extension_ui_request",
                id: crypto.randomUUID(),
                method: "set_editor_text",
                text,
            });
        },
        getEditorText() {
            return "";
        },
        async editor(title, prefill) {
            const id = crypto.randomUUID();
            return new Promise((resolve) => {
                pendingExtensionRequests.set(id, {
                    resolve: (response) => {
                        if ("cancelled" in response && response.cancelled) {
                            resolve(undefined);
                        }
                        else if ("value" in response) {
                            resolve(response.value);
                        }
                        else {
                            resolve(undefined);
                        }
                    },
                    reject: () => resolve(undefined),
                });
                output({ type: "extension_ui_request", id, method: "editor", title, prefill });
            });
        },
        setEditorComponent() { },
        get theme() {
            return theme;
        },
        getAllThemes() {
            return [];
        },
        getTheme() {
            return undefined;
        },
        setTheme() {
            return { success: false, error: "Theme switching not supported in serve mode" };
        },
        getToolsExpanded() {
            return false;
        },
        setToolsExpanded() { },
    });
    // Set up extensions
    await session.bindExtensions({
        uiContext: createExtensionUIContext(),
        commandContextActions: {
            waitForIdle: () => session.agent.waitForIdle(),
            newSession: async (options) => {
                const ok = await session.newSession(options);
                return { cancelled: !ok };
            },
            fork: async (entryId) => {
                const result = await session.fork(entryId);
                return { cancelled: result.cancelled };
            },
            navigateTree: async (targetId, options) => {
                const result = await session.navigateTree(targetId, {
                    summarize: options?.summarize,
                    customInstructions: options?.customInstructions,
                    replaceInstructions: options?.replaceInstructions,
                    label: options?.label,
                });
                return { cancelled: result.cancelled };
            },
            switchSession: async (sessionPath) => {
                const ok = await session.switchSession(sessionPath);
                return { cancelled: !ok };
            },
            reload: async () => {
                await session.reload();
            },
        },
        shutdownHandler: () => {
            _shutdownRequested = true;
        },
        onError: (err) => {
            output({ type: "extension_error", extensionPath: err.extensionPath, event: err.event, error: err.error });
        },
    });
    // Forward all agent events
    session.subscribe((event) => {
        output(event);
    });
    // Handle commands (same as rpc-mode.ts handleCommand)
    const handleCommand = async (command) => {
        const id = command.id;
        switch (command.type) {
            case "prompt": {
                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 });
            }
            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: session.autoCompactionEnabled,
                    messageCount: session.messages.length,
                    pendingMessageCount: session.pendingMessageCount,
                };
                return success(id, "get_state", state);
            }
            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();
                return success(id, "cycle_model", result ?? null);
            }
            case "get_available_models": {
                const models = await session.modelRegistry.getAvailable();
                return success(id, "get_available_models", { models });
            }
            case "set_thinking_level":
                session.setThinkingLevel(command.level);
                return success(id, "set_thinking_level");
            case "cycle_thinking_level": {
                const level = session.cycleThinkingLevel();
                return success(id, "cycle_thinking_level", level ? { level } : null);
            }
            case "set_steering_mode":
                session.setSteeringMode(command.mode);
                return success(id, "set_steering_mode");
            case "set_follow_up_mode":
                session.setFollowUpMode(command.mode);
                return success(id, "set_follow_up_mode");
            case "compact": {
                const result = await session.compact(command.customInstructions);
                return success(id, "compact", result);
            }
            case "set_auto_compaction":
                session.setAutoCompactionEnabled(command.enabled);
                return success(id, "set_auto_compaction");
            case "set_auto_retry":
                session.setAutoRetryEnabled(command.enabled);
                return success(id, "set_auto_retry");
            case "abort_retry":
                session.abortRetry();
                return success(id, "abort_retry");
            case "bash": {
                const result = await session.executeBash(command.command);
                return success(id, "bash", result);
            }
            case "abort_bash":
                session.abortBash();
                return success(id, "abort_bash");
            case "get_session_stats":
                return success(id, "get_session_stats", session.getSessionStats());
            case "export_html": {
                const htmlPath = await session.exportToHtml(command.outputPath);
                return success(id, "export_html", { path: htmlPath });
            }
            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":
                return success(id, "get_fork_messages", { messages: session.getUserMessagesForForking() });
            case "get_last_assistant_text":
                return success(id, "get_last_assistant_text", { text: session.getLastAssistantText() });
            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");
            }
            case "get_messages":
                return success(id, "get_messages", { messages: session.messages });
            case "get_commands": {
                const commands = [];
                for (const cmd of session.extensionRunner?.getRegisteredCommands() ?? []) {
                    commands.push({
                        name: cmd.invocationName,
                        description: cmd.description,
                        source: "extension",
                        sourceInfo: cmd.sourceInfo,
                    });
                }
                for (const tmpl of session.promptTemplates) {
                    commands.push({
                        name: tmpl.name,
                        description: tmpl.description,
                        source: "prompt",
                        sourceInfo: tmpl.sourceInfo,
                    });
                }
                for (const skill of session.resourceLoader.getSkills().skills) {
                    commands.push({
                        name: `skill:${skill.name}`,
                        description: skill.description,
                        source: "skill",
                        sourceInfo: skill.sourceInfo,
                    });
                }
                return success(id, "get_commands", { commands });
            }
            default:
                return error(undefined, command.type, `Unknown command: ${command.type}`);
        }
    };
    // Wire up message handling
    transport.onMessage(async (data) => {
        if (closed)
            return;
        try {
            const parsed = JSON.parse(data);
            // Extension UI response
            if (parsed.type === "extension_ui_response") {
                const pending = pendingExtensionRequests.get(parsed.id);
                if (pending) {
                    pendingExtensionRequests.delete(parsed.id);
                    pending.resolve(parsed);
                }
                return;
            }
            const response = await handleCommand(parsed);
            output(response);
        }
        catch (e) {
            output(error(undefined, "parse", `Failed to parse command: ${e.message}`));
        }
    });
    // Wait for close
    return new Promise((resolve) => {
        transport.onClose(() => {
            closed = true;
            // Clean up pending requests
            for (const [, req] of pendingExtensionRequests) {
                req.reject(new Error("Connection closed"));
            }
            pendingExtensionRequests.clear();
            // Run shutdown handlers
            const runner = session.extensionRunner;
            if (runner?.hasHandlers("session_shutdown")) {
                runner.emit({ type: "session_shutdown" }).then(resolve).catch(resolve);
            }
            else {
                resolve();
            }
        });
    });
}
//# sourceMappingURL=rpc-transport.js.map