import { afterEach, describe, expect, it, vi } from 'vitest';
import { OpencodeSdkAdapter } from './OpencodeSdkAdapter.js';
const POLL_PROMPT = '系统提醒：当前工作已完成或运行时已进入空闲状态。请立即调用 poll_message 工具阻塞等待新消息和流程线任务提醒；收到流程任务提醒后调用 my_task 查看详情，按需调用 start_task 开始处理；如果 start_task 失败且提示任务已被其他实例领取，请不要停下，继续调用 poll_message 等待新消息；完成任务后调用 submit_task_result 提交结果，然后继续 poll_message。';
afterEach(() => {
    vi.useRealTimers();
    vi.restoreAllMocks();
});
describe('OpencodeSdkAdapter', () => {
    it('should expose opencode provider identity', () => {
        const adapter = new OpencodeSdkAdapter();
        expect(adapter.providerId).toBe('opencode');
        expect(adapter.displayName).toBe('OpenCode');
    });
    it('should start with no active sessions', () => {
        const adapter = new OpencodeSdkAdapter();
        expect(adapter.hasSession('missing-session')).toBe(false);
        expect(adapter.getSessionStatus('missing-session')).toBeUndefined();
    });
    it('normalizes poll_message-style idle commands into a blocking poll prompt', () => {
        const adapter = new OpencodeSdkAdapter();
        const prompt = Reflect.get(adapter, 'toIdlePrompt').call(adapter, 'system: 请保持工作循环：持续调用 poll_message 阻塞等待新消息和流程线任务提醒；收到流程任务提醒后调用 my_task 查看详情，按需调用 start_task 开始处理，完成后调用 submit_task_result 提交结果，然后继续 poll_message。');
        expect(prompt).toBe(POLL_PROMPT);
    });
    it('starts idle detection when commands are set after OpenCode is already waiting for input', async () => {
        vi.useFakeTimers();
        const adapter = new OpencodeSdkAdapter();
        const sentMessages = [];
        Reflect.set(adapter, 'sessions', new Map([
            [
                'session-1',
                {
                    adapterSession: {
                        sessionId: 'session-1',
                        status: 'waiting_input',
                        messages: [],
                        createdAt: new Date().toISOString(),
                        totalUsage: { inputTokens: 0, outputTokens: 0 },
                    },
                    config: {
                        command: 'opencode',
                        workingDirectory: process.cwd(),
                        autoAccept: true,
                    },
                    sseActive: false,
                    pendingPermissions: new Map(),
                    emittedToolStarts: new Set(),
                    workingDirectory: process.cwd(),
                    userMessageIds: new Set(),
                    currentAssistantText: '',
                    seenTextPartIds: new Set(),
                },
            ],
        ]));
        vi.spyOn(adapter, 'sendMessage').mockImplementation(async (_sessionId, message) => {
            sentMessages.push(message);
        });
        adapter.setIdleCommands('session-1', {
            idleInputCommand: 'system: 使用poll_message阻塞获取消息',
            nonInputCommand: '',
        });
        await vi.advanceTimersByTimeAsync(1499);
        expect(sentMessages).toEqual([]);
        await vi.advanceTimersByTimeAsync(1);
        expect(sentMessages).toEqual([POLL_PROMPT]);
        expect(adapter.getSessionStatus('session-1')).toBe('waiting_input');
    });
    it('re-arms polling idle detection after OpenCode returns to waiting for input', async () => {
        vi.useFakeTimers();
        const adapter = new OpencodeSdkAdapter();
        const sentMessages = [];
        const session = {
            adapterSession: {
                sessionId: 'session-1',
                status: 'waiting_input',
                messages: [],
                createdAt: new Date().toISOString(),
                totalUsage: { inputTokens: 0, outputTokens: 0 },
            },
            config: {
                command: 'opencode',
                workingDirectory: process.cwd(),
                autoAccept: true,
            },
            sseActive: false,
            pendingPermissions: new Map(),
            emittedToolStarts: new Set(),
            workingDirectory: process.cwd(),
            userMessageIds: new Set(),
            currentAssistantText: '',
            seenTextPartIds: new Set(),
        };
        Reflect.set(adapter, 'sessions', new Map([['session-1', session]]));
        vi.spyOn(adapter, 'sendMessage').mockImplementation(async (_sessionId, message) => {
            sentMessages.push(message);
            session.adapterSession.status = 'thinking';
        });
        adapter.setIdleCommands('session-1', {
            idleInputCommand: 'system: 使用poll_message阻塞获取消息',
            nonInputCommand: '',
        });
        await vi.advanceTimersByTimeAsync(1500);
        expect(sentMessages).toHaveLength(1);
        session.adapterSession.status = 'waiting_input';
        Reflect.get(adapter, 'startIdleDetection').call(adapter, 'session-1');
        await vi.advanceTimersByTimeAsync(1500);
        expect(sentMessages).toHaveLength(2);
    });
    it('does not send polling prompts while OpenCode is busy', () => {
        const adapter = new OpencodeSdkAdapter();
        const sentMessages = [];
        Reflect.set(adapter, 'sessions', new Map([
            [
                'session-1',
                {
                    adapterSession: {
                        sessionId: 'session-1',
                        status: 'thinking',
                        messages: [],
                        createdAt: new Date().toISOString(),
                        totalUsage: { inputTokens: 0, outputTokens: 0 },
                    },
                    config: {
                        command: 'opencode',
                        workingDirectory: process.cwd(),
                        autoAccept: true,
                    },
                    sseActive: false,
                    pendingPermissions: new Map(),
                    emittedToolStarts: new Set(),
                    workingDirectory: process.cwd(),
                    userMessageIds: new Set(),
                    currentAssistantText: '',
                    seenTextPartIds: new Set(),
                },
            ],
        ]));
        vi.spyOn(adapter, 'sendMessage').mockImplementation(async (_sessionId, message) => {
            sentMessages.push(message);
        });
        adapter.setIdleCommands('session-1', {
            idleInputCommand: 'system: 使用poll_message阻塞获取消息',
            nonInputCommand: '',
        });
        expect(sentMessages).toEqual([]);
    });
    it('includes serve startup output when the OpenCode process exits early', () => {
        const adapter = new OpencodeSdkAdapter();
        const message = Reflect.get(adapter, 'formatStartupExitMessage').call(adapter, { code: 1, signal: null }, ['missing auth configuration']);
        expect(message).toContain('OpenCode server exited during startup (code 1)');
        expect(message).toContain('missing auth configuration');
    });
    it('dispatches the initial prompt asynchronously so launch can return after session creation', () => {
        const adapter = new OpencodeSdkAdapter();
        const sendMessage = vi.spyOn(adapter, 'sendMessage').mockResolvedValue(undefined);
        Reflect.get(adapter, 'dispatchInitialPrompt').call(adapter, 'session-1', 'hello');
        expect(sendMessage).toHaveBeenCalledWith('session-1', 'hello');
    });
});
