/**
 * Print mode (single-shot): Send prompts, output result, exit.
 *
 * Used for:
 * - `pi -p "prompt"` - text output
 * - `pi --mode json "prompt"` - JSON event stream
 */
import { flushRawStdout, writeRawStdout } from "../core/output-guard.js";
import { killTrackedDetachedChildren } from "../utils/shell.js";
function isCommandExtensionError(error) {
    return error.event === "command" || error.extensionPath.startsWith("command:");
}
function displayableCustomText(message) {
    if (message.display !== true)
        return undefined;
    if (typeof message.content === "string")
        return message.content;
    let text = "";
    let hasTextPart = false;
    for (const part of message.content) {
        if (part.type === "text") {
            hasTextPart = true;
            text += part.text;
        }
    }
    return hasTextPart ? text : undefined;
}
function isStructuredOutputTool(getToolDefinition, name) {
    return getToolDefinition(name)?.structuredOutput === true;
}
function isTerminatingStructuredOutputEvent(event, getToolDefinition) {
    if (event.type !== "tool_execution_end")
        return false;
    if (!isStructuredOutputTool(getToolDefinition, event.toolName))
        return false;
    if (event.isError)
        return false;
    const result = event.result;
    return result?.terminate === true;
}
function textFromToolResult(message) {
    let text = "";
    let hasTextPart = false;
    for (const part of message.content) {
        if (part.type === "text") {
            hasTextPart = true;
            text += part.text;
        }
    }
    return hasTextPart ? text : undefined;
}
function terminatingStructuredOutputText(message, terminatingStructuredOutputCallIds) {
    if (!terminatingStructuredOutputCallIds.has(message.toolCallId))
        return undefined;
    return textFromToolResult(message);
}
/**
 * Run in print (single-shot) mode.
 * Sends prompts to the agent and outputs the result.
 */
export async function runPrintMode(runtimeHost, options) {
    const { mode, messages = [], initialMessage, initialImages } = options;
    let exitCode = 0;
    let suppressFinalOutput = false;
    let activePromptHadCommandError = false;
    let session = runtimeHost.session;
    let unsubscribe;
    const terminatingStructuredOutputCallIds = new Set();
    let disposed = false;
    const signalCleanupHandlers = [];
    const disposeRuntime = async () => {
        if (disposed)
            return;
        disposed = true;
        unsubscribe?.();
        await runtimeHost.dispose();
    };
    const registerSignalHandlers = () => {
        const signals = ["SIGTERM"];
        if (process.platform !== "win32") {
            signals.push("SIGHUP");
        }
        for (const signal of signals) {
            const handler = () => {
                killTrackedDetachedChildren();
                void disposeRuntime().finally(() => {
                    process.exit(signal === "SIGHUP" ? 129 : 143);
                });
            };
            process.on(signal, handler);
            signalCleanupHandlers.push(() => process.off(signal, handler));
        }
    };
    registerSignalHandlers();
    const promptWithScopedCommandSuppression = async (text, promptOptions) => {
        activePromptHadCommandError = false;
        try {
            if (promptOptions === undefined) {
                await session.prompt(text);
            }
            else {
                await session.prompt(text, promptOptions);
            }
        }
        finally {
            // Final-output suppression is scoped to the most recent prompt so a
            // later successful prompt in the same invocation can still print its
            // result. The non-zero exit code remains sticky across prompts.
            suppressFinalOutput = activePromptHadCommandError;
        }
    };
    const rebindSession = async () => {
        session = runtimeHost.session;
        await session.bindExtensions({
            mode: mode === "json" ? "json" : "print",
            commandContextActions: {
                waitForIdle: () => session.agent.waitForIdle(),
                newSession: async (newSessionOptions) => runtimeHost.newSession(newSessionOptions),
                fork: async (entryId, forkOptions) => {
                    const result = await runtimeHost.fork(entryId, forkOptions);
                    return { cancelled: result.cancelled };
                },
                navigateTree: async (targetId, navigateOptions) => {
                    const result = await session.navigateTree(targetId, {
                        summarize: navigateOptions?.summarize,
                        customInstructions: navigateOptions?.customInstructions,
                        replaceInstructions: navigateOptions?.replaceInstructions,
                        label: navigateOptions?.label,
                    });
                    return { cancelled: result.cancelled };
                },
                switchSession: async (sessionPath, switchOptions) => {
                    return runtimeHost.switchSession(sessionPath, switchOptions);
                },
                reload: async () => {
                    await session.reload();
                },
            },
            onError: (err) => {
                const isCommandError = isCommandExtensionError(err);
                if (isCommandError)
                    exitCode = 1;
                activePromptHadCommandError = activePromptHadCommandError || isCommandError;
                console.error(`Extension error (${err.extensionPath}): ${err.error}`);
            },
        });
        unsubscribe?.();
        unsubscribe = session.subscribe((event) => {
            if (isTerminatingStructuredOutputEvent(event, (name) => session.getToolDefinition(name))) {
                terminatingStructuredOutputCallIds.add(event.toolCallId);
            }
            if (mode === "json") {
                writeRawStdout(`${JSON.stringify(event)}\n`);
            }
        });
    };
    runtimeHost.setRebindSession(async () => {
        await rebindSession();
    });
    try {
        if (mode === "json") {
            const header = session.sessionManager.getHeader();
            if (header) {
                writeRawStdout(`${JSON.stringify(header)}\n`);
            }
        }
        await rebindSession();
        if (initialMessage) {
            await promptWithScopedCommandSuppression(initialMessage, { images: initialImages });
        }
        for (const message of messages) {
            await promptWithScopedCommandSuppression(message);
        }
        const state = session.state;
        const lastMessage = state.messages[state.messages.length - 1];
        if (lastMessage?.role === "assistant") {
            const assistantMsg = lastMessage;
            if (assistantMsg.stopReason === "error" || assistantMsg.stopReason === "aborted") {
                if (mode === "text" && !suppressFinalOutput) {
                    console.error(assistantMsg.errorMessage || `Request ${assistantMsg.stopReason}`);
                }
                exitCode = 1;
            }
            else if (mode === "text" && !suppressFinalOutput) {
                for (const content of assistantMsg.content) {
                    if (content.type === "text") {
                        writeRawStdout(`${content.text}\n`);
                    }
                }
            }
        }
        else if (mode === "text" && !suppressFinalOutput) {
            if (lastMessage?.role === "custom") {
                const text = displayableCustomText(lastMessage);
                if (text !== undefined) {
                    writeRawStdout(`${text}\n`);
                }
            }
            else if (lastMessage?.role === "toolResult") {
                const text = terminatingStructuredOutputText(lastMessage, terminatingStructuredOutputCallIds);
                if (text !== undefined) {
                    writeRawStdout(`${text}\n`);
                }
            }
        }
        return exitCode;
    }
    catch (error) {
        console.error(error instanceof Error ? error.message : String(error));
        return 1;
    }
    finally {
        for (const cleanup of signalCleanupHandlers) {
            cleanup();
        }
        await disposeRuntime();
        await flushRawStdout();
    }
}
//# sourceMappingURL=print-mode.js.map