import { spawn } from "node:child_process";
import { Readable, Writable } from "node:stream";
import * as acp from "@agentclientprotocol/sdk";
import { PeerRuntime } from "../peer/runtime/peer-runtime.js";
import { AcpClientRuntime } from "./acp-client-runtime.js";
import { AcpGuestProjection } from "./acp-guest-projection.js";
export class GuestAcpRuntime {
    peerRuntime;
    acpRuntime;
    projection;
    acpProcess;
    promptChain = Promise.resolve();
    cancelRequested = false;
    hello;
    appState;
    uiState;
    constructor(peerRuntime, acpRuntime, projection, acpProcess) {
        this.peerRuntime = peerRuntime;
        this.acpRuntime = acpRuntime;
        this.projection = projection;
        this.acpProcess = acpProcess;
        this.hello = peerRuntime.hello;
        this.appState = peerRuntime.appState;
        this.uiState = peerRuntime.uiState;
    }
    static fromCommand(options) {
        const cwd = options.cwd ?? process.cwd();
        const acpProcess = spawn(options.acpCommand, options.acpArgs, {
            cwd,
            env: createAcpProcessEnv(process.env),
            stdio: ["pipe", "pipe", "inherit"],
        });
        const stream = acp.ndJsonStream(Writable.toWeb(acpProcess.stdin), Readable.toWeb(acpProcess.stdout));
        let runtime;
        const peerRuntime = new PeerRuntime({
            hubUrl: options.hubUrl,
            agentId: options.agentId,
            token: options.token,
            displayName: options.displayName,
            version: options.version,
            clientKind: "guest",
            executorEnabled: false,
            cwd,
            onHandshakeLog: (message) => {
                console.error(`[d-pi guest] ${message}`);
            },
            onGuestAgentMessage: async (payload) => {
                if (!runtime) {
                    throw new Error("Guest ACP runtime is not initialized.");
                }
                await runtime.enqueueInboundAgentMessage(payload);
            },
        });
        const projection = new AcpGuestProjection({
            appState: peerRuntime.appState,
            agentId: options.agentId,
            cwd,
        });
        const acpRuntime = new AcpClientRuntime({
            stream,
            cwd,
            onSessionUpdate: (notification) => {
                projection.applySessionUpdate(notification);
            },
        });
        runtime = new GuestAcpRuntime(peerRuntime, acpRuntime, projection, acpProcess);
        return runtime;
    }
    static fromRuntimes(peerRuntime, acpOptions) {
        const projection = new AcpGuestProjection({
            appState: peerRuntime.appState,
            agentId: peerRuntime.hello.agentId ?? "guest",
            cwd: peerRuntime.hello.cwd ?? process.cwd(),
        });
        const originalOnSessionUpdate = acpOptions.onSessionUpdate;
        return new GuestAcpRuntime(peerRuntime, new AcpClientRuntime({
            ...acpOptions,
            onSessionUpdate: async (notification) => {
                projection.applySessionUpdate(notification);
                await originalOnSessionUpdate?.(notification);
            },
        }), projection);
    }
    async start() {
        await this.peerRuntime.start();
        try {
            await this.acpRuntime.start();
        }
        catch (error) {
            await this.peerRuntime.stop().catch(() => { });
            throw error;
        }
    }
    async stop() {
        await this.acpRuntime.stop();
        this.acpProcess?.kill();
        await this.peerRuntime.stop();
    }
    async queueWrite(text) {
        this.promptChain = this.promptChain
            .catch(() => { })
            .then(async () => {
            this.cancelRequested = false;
            this.projection.appendUserPrompt(text);
            await promptAcpWithProjection(this.acpRuntime, this.projection, text, () => this.cancelRequested);
        });
        await this.promptChain;
    }
    async enqueueInboundAgentMessage(payload) {
        this.promptChain = this.promptChain
            .catch(() => { })
            .then(async () => {
            this.cancelRequested = false;
            const prompt = this.projection.appendInboundAgentMessage(payload);
            await promptAcpWithProjection(this.acpRuntime, this.projection, prompt, () => this.cancelRequested);
        });
        await this.promptChain;
    }
    async queueFlush() { }
    async submitPrompt(text) {
        await this.queueWrite(text);
    }
    async abort() {
        this.cancelRequested = true;
        await this.acpRuntime.cancel();
        this.projection.completeTurn("cancelled");
        return true;
    }
    async switchAgent() {
        throw new Error("Agent switching is not available in guest ACP mode.");
    }
    async setModel() {
        throw new Error("Model selection is not available in guest ACP mode.");
    }
    async setThinkingLevel(_level) {
        throw new Error("Thinking level selection is not available in guest ACP mode.");
    }
    async invokeCommand(commandName) {
        if (commandName === "group") {
            return;
        }
        throw new Error(`/${commandName} is not available in guest ACP mode.`);
    }
    getSessionSources() {
        return Promise.resolve([]);
    }
    pauseSource() {
        return Promise.resolve([]);
    }
    restartSource() {
        return Promise.resolve([]);
    }
    removeSource() {
        return Promise.resolve([]);
    }
    getMcpServers() {
        return Promise.resolve({ servers: [] });
    }
    pauseMcpServer() {
        return Promise.resolve([]);
    }
    restartMcpServer() {
        return Promise.resolve([]);
    }
    removeMcpServer() {
        return Promise.resolve([]);
    }
}
export function createAcpProcessEnv(env) {
    const next = { ...env };
    delete next.D_PI_TOKEN;
    return next;
}
export function formatGuestAgentMessagePrompt(payload) {
    return [
        "[D-Pi agent message]",
        `from: ${payload.fromAgentId}`,
        `to: ${payload.toAgentId}`,
        `sent_at: ${payload.sentAt}`,
        "",
        payload.message,
    ].join("\n");
}
async function promptAcpWithProjection(acpRuntime, projection, prompt, isCancelled) {
    try {
        const result = await acpRuntime.prompt(prompt);
        projection.completeTurn(isCancelled() ? "cancelled" : result.stopReason);
    }
    catch (error) {
        if (isCancelled()) {
            projection.completeTurn("cancelled");
            return;
        }
        projection.failTurn(error);
        throw error;
    }
}
//# sourceMappingURL=acp-guest-runtime.js.map