// ACP client integration test for the OpenCode ACP server.
import { afterAll, beforeAll, expect, test } from 'vitest';
import { spawn } from 'node:child_process';
import { Readable, Writable } from 'node:stream';
import { ClientSideConnection, PROTOCOL_VERSION, ndJsonStream, } from '@agentclientprotocol/sdk';
const UPDATE_TIMEOUT_MS = 5000;
class TestClient {
    updates = [];
    listeners = [];
    async requestPermission(params) {
        const firstOption = params.options[0];
        if (!firstOption) {
            return { outcome: { outcome: 'cancelled' } };
        }
        return {
            outcome: {
                outcome: 'selected',
                optionId: firstOption.optionId,
            },
        };
    }
    async sessionUpdate(params) {
        this.updates.push(params);
        const matching = this.listeners.filter((listener) => {
            return listener.predicate(params);
        });
        this.listeners = this.listeners.filter((listener) => {
            return !matching.includes(listener);
        });
        matching.forEach((listener) => {
            clearTimeout(listener.timeout);
            listener.resolve(params);
        });
    }
    resetUpdates() {
        this.updates = [];
    }
    waitForUpdate({ predicate, timeoutMs = UPDATE_TIMEOUT_MS, }) {
        const existing = this.updates.find((update) => {
            return predicate(update);
        });
        if (existing) {
            return Promise.resolve(existing);
        }
        return new Promise((resolve, reject) => {
            const timeout = setTimeout(() => {
                this.listeners = this.listeners.filter((listener) => {
                    return listener.resolve !== resolve;
                });
                reject(new Error('Timed out waiting for session update'));
            }, timeoutMs);
            const listener = {
                predicate,
                resolve,
                reject,
                timeout,
            };
            this.listeners = [...this.listeners, listener];
        });
    }
}
let serverProcess = null;
let connection = null;
let client = null;
let initResult = null;
let sessionId = null;
beforeAll(async () => {
    serverProcess = spawn('opencode', ['acp', '--port', '0', '--cwd', process.cwd()], {
        stdio: ['pipe', 'pipe', 'pipe'],
    });
    if (!serverProcess.stdin || !serverProcess.stdout) {
        throw new Error('Failed to open ACP stdio streams');
    }
    const input = Writable.toWeb(serverProcess.stdin);
    const output = Readable.toWeb(serverProcess.stdout);
    const localClient = new TestClient();
    client = localClient;
    const stream = ndJsonStream(input, output);
    connection = new ClientSideConnection(() => localClient, stream);
    initResult = await connection.initialize({
        protocolVersion: PROTOCOL_VERSION,
        clientCapabilities: {
            fs: {
                readTextFile: false,
                writeTextFile: false,
            },
            terminal: false,
        },
    });
}, 30000);
afterAll(async () => {
    if (serverProcess) {
        serverProcess.kill('SIGTERM');
        await new Promise((resolve) => {
            setTimeout(resolve, 500);
        });
        if (!serverProcess.killed) {
            serverProcess.kill('SIGKILL');
        }
    }
});
test('creates a session and receives prompt events', async () => {
    if (!connection || !client) {
        throw new Error('ACP connection not initialized');
    }
    const sessionResponse = await connection.newSession({
        cwd: process.cwd(),
        mcpServers: [],
    });
    sessionId = sessionResponse.sessionId;
    const promptResponse = await connection.prompt({
        sessionId,
        prompt: [{ type: 'text', text: 'Hello from ACP test.' }],
    });
    const update = await client.waitForUpdate({
        predicate: (notification) => {
            return (notification.sessionId === sessionId &&
                notification.update.sessionUpdate === 'agent_message_chunk');
        },
    });
    expect(promptResponse.stopReason).toBeTruthy();
    expect(update.update.sessionUpdate).toBe('agent_message_chunk');
}, 30000);
test('loads an existing session and streams history', async () => {
    if (!connection || !client) {
        throw new Error('ACP connection not initialized');
    }
    if (!initResult?.agentCapabilities?.loadSession) {
        expect(true).toBe(true);
        return;
    }
    if (!sessionId) {
        throw new Error('Missing session ID from previous test');
    }
    client.resetUpdates();
    const loadPromise = connection.loadSession({
        sessionId,
        cwd: process.cwd(),
        mcpServers: [],
    });
    const update = await client.waitForUpdate({
        predicate: (notification) => {
            return (notification.sessionId === sessionId &&
                notification.update.sessionUpdate === 'user_message_chunk');
        },
    });
    await loadPromise;
    expect(update.update.sessionUpdate).toBe('user_message_chunk');
}, 30000);
