import { jsx as _jsx } from "react/jsx-runtime";
/** @vitest-environment jsdom */
import React from 'react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { act, cleanup, render } from '@testing-library/react';
import { StorageAdapterProvider } from '../../../contexts/StorageAdapterContext';
import { useChatAgent } from '../index';
import { hasProvider, registerProvider } from '../../../lib/providers';
import { buildToolApprovalInterruption } from '../interruptions';
const runMock = vi.hoisted(() => vi.fn());
const MockRunState = vi.hoisted(() => {
    return class MockRunState {
        constructor(serialized) {
            Object.defineProperty(this, "serialized", {
                enumerable: true,
                configurable: true,
                writable: true,
                value: void 0
            });
            Object.defineProperty(this, "interruptions", {
                enumerable: true,
                configurable: true,
                writable: true,
                value: void 0
            });
            this.serialized = serialized;
            this.interruptions = [];
        }
        static async fromString(_, serialized) {
            return new MockRunState(serialized);
        }
        toString() {
            return this.serialized;
        }
        getInterruptions() {
            return this.interruptions;
        }
        approve() { }
        reject() { }
        setInterruptions(interruptions) {
            this.interruptions = interruptions;
        }
    };
});
const streamHandlersMock = vi.hoisted(() => vi.fn(async (_resp, handlers) => {
    const typedHandlers = handlers;
    if (typedHandlers.onToolCall) {
        await typedHandlers.onToolCall('demo_tool', '{}', {
            callId: 'call-1',
            status: 'completed',
            timestamp: Date.now()
        });
    }
    if (typedHandlers.onToolOutput) {
        await typedHandlers.onToolOutput('demo_tool', 'result', {
            callId: 'call-1',
            status: 'completed',
            timestamp: Date.now()
        });
    }
    if (typedHandlers.onMessageOutputCreated) {
        if (typedHandlers.onFinalOutputAccessed) {
            await typedHandlers.onFinalOutputAccessed('Final response text');
        }
        await typedHandlers.onMessageOutputCreated('Final response text');
    }
}));
vi.mock('@openai/agents', () => ({
    RunState: MockRunState,
    Agent: class {
    },
    AgentOutputType: class {
    },
    Runner: class {
        constructor(config) {
            Object.defineProperty(this, "config", {
                enumerable: true,
                configurable: true,
                writable: true,
                value: config
            });
            Object.defineProperty(this, "run", {
                enumerable: true,
                configurable: true,
                writable: true,
                value: runMock
            });
        }
    },
    OpenAIProvider: class {
        constructor(options) {
            Object.defineProperty(this, "options", {
                enumerable: true,
                configurable: true,
                writable: true,
                value: options
            });
        }
    }
}));
vi.mock('../../../../plugins/openai/streamResponse', () => ({
    streamOpenAIResponse: streamHandlersMock
}));
vi.mock('../../../../plugins/openai/runner', () => ({
    runOpenAIChat: vi.fn(async ({ handlers }) => {
        const response = await runMock();
        await streamHandlersMock(response, handlers);
        return { response };
    }),
    resumeOpenAIChat: vi.fn(async ({ handlers }) => {
        const response = await runMock();
        await streamHandlersMock(response, handlers);
        return { response };
    })
}));
vi.mock('../../../lib/chatAgent', () => ({
    createChatAgent: () => ({ id: 'mock-agent' })
}));
const ensureMockProvider = vi.hoisted(() => () => {
    if (hasProvider('openai')) {
        return;
    }
    const createHandle = (input, callbacks) => {
        const streamCallbacks = {
            onToolCall: callbacks.onToolCall,
            onToolOutput: callbacks.onToolOutput,
            onMessageOutputCreated: callbacks.onMessageOutputCreated,
            onFinalOutputAccessed: callbacks.onFinalOutputAccessed
        };
        const completed = (async () => {
            const response = (await runMock());
            await streamHandlersMock(response, streamCallbacks);
            return response;
        })();
        return {
            cancel: async () => {
                if (input.abortController && !input.abortController.signal.aborted) {
                    input.abortController.abort();
                }
                try {
                    await completed;
                }
                catch (error) {
                    if (!(error instanceof Error && error.name === 'AbortError')) {
                        throw error;
                    }
                }
            },
            completed,
            signal: input.signal
        };
    };
    const provider = {
        metadata: {
            kind: 'openai',
            name: 'Mock OpenAI',
            supportsTools: true,
            supportsApprovals: true,
            supportsStreaming: true,
            isImplemented: true
        },
        createAgent: () => ({ id: 'mock-agent' }),
        startRun: async (_agent, input, callbacks) => createHandle(input, callbacks),
        resumeRun: async (_agent, input, callbacks) => createHandle(input, callbacks),
        deserializeRunState: async (_agent, serialized) => MockRunState.fromString({}, serialized)
    };
    registerProvider(provider);
});
const createMemoryAdapter = () => {
    const store = new Map();
    const subscribers = new Map();
    const notify = (key, value) => {
        const listeners = subscribers.get(key);
        if (!listeners)
            return;
        listeners.forEach((listener) => listener(value));
    };
    return {
        getItem: (key) => store.get(key) ?? null,
        setItem: (key, value) => {
            store.set(key, value);
            notify(key, value);
        },
        removeItem: (key) => {
            store.delete(key);
            notify(key, null);
        },
        subscribe: (key, callback) => {
            const set = subscribers.get(key) ?? new Set();
            set.add(callback);
            subscribers.set(key, set);
            return () => {
                const listeners = subscribers.get(key);
                if (!listeners)
                    return;
                listeners.delete(callback);
                if (listeners.size === 0) {
                    subscribers.delete(key);
                }
            };
        }
    };
};
afterEach(() => {
    cleanup();
    runMock.mockReset();
    streamHandlersMock.mockReset();
});
const createMockInterruption = (status, index) => ({
    toJSON: () => ({
        rawItem: {
            status,
            name: 'demo_tool',
            arguments: { value: index },
            callId: `call-${index}`,
            id: `interruption-${index}`,
            message: status === 'pending' ? 'Needs approval' : 'Resolved'
        },
        agent: { name: 'Mock agent' }
    }),
    rawItem: {
        status,
        name: 'demo_tool',
        arguments: { value: index },
        callId: `call-${index}`,
        id: `interruption-${index}`,
        message: status === 'pending' ? 'Needs approval' : 'Resolved'
    }
});
const createRunResult = (statuses) => {
    const interruptions = statuses.map((status, index) => createMockInterruption(status, index));
    const state = new MockRunState('serialized-state');
    state.setInterruptions(interruptions);
    return {
        interruptions,
        state,
        finalOutput: 'Final response text'
    };
};
const Harness = React.forwardRef(({ sessionId, options }, ref) => {
    const api = useChatAgent(sessionId, options);
    React.useImperativeHandle(ref, () => api, [api]);
    return null;
});
const defaultOptions = {
    apiKey: 'test-key',
    providerId: 'openai',
    modelName: 'gpt-test',
    mcpServers: []
};
describe('pending run cache handling', () => {
    let adapter;
    const storageKey = 'chat-session-test-session-pending-run';
    beforeEach(() => {
        ensureMockProvider();
        adapter = createMemoryAdapter();
    });
    it('does not persist pending run when interruptions are already resolved', async () => {
        runMock.mockResolvedValueOnce(createRunResult(['approved']));
        const ref = React.createRef();
        render(_jsx(StorageAdapterProvider, { adapter: adapter, children: _jsx(Harness, { sessionId: 'test-session', options: defaultOptions, ref: ref }) }));
        const getHandle = () => {
            const handle = ref.current;
            if (!handle) {
                throw new Error('Hook handle not initialized');
            }
            return handle;
        };
        await act(async () => {
            await getHandle().invokeChatAgent({ content: 'Hello world' });
        });
        expect(adapter.getItem(storageKey)).toBeNull();
        expect(getHandle().pendingRunId).toBeNull();
    });
    it('continues to persist pending interruptions that require approval', async () => {
        runMock.mockResolvedValueOnce(createRunResult(['pending']));
        const ref = React.createRef();
        render(_jsx(StorageAdapterProvider, { adapter: adapter, children: _jsx(Harness, { sessionId: 'test-session', options: defaultOptions, ref: ref }) }));
        const getHandle = () => {
            const handle = ref.current;
            if (!handle) {
                throw new Error('Hook handle not initialized');
            }
            return handle;
        };
        await act(async () => {
            await getHandle().invokeChatAgent({ content: 'Hello world' });
        });
        const persisted = adapter.getItem(storageKey);
        expect(persisted).not.toBeNull();
        const parsed = persisted ? JSON.parse(persisted) : null;
        expect(parsed?.interruptions).toHaveLength(1);
        expect(parsed?.interruptions?.[0]?.status).toBe('pending');
    });
});
describe('buildToolApprovalInterruption status mapping', () => {
    const runId = 'run-test';
    const timestamp = Date.now();
    const createRawInterruption = (status) => ({
        toJSON: () => ({ rawItem: { status, name: 'demo_tool' } }),
        rawItem: { status, name: 'demo_tool' }
    });
    it('maps completed-like statuses to approved', () => {
        const interruption = buildToolApprovalInterruption(runId, createRawInterruption('approved'), timestamp, 0);
        expect(interruption.status).toBe('approved');
        expect(interruption.resolvedAt).toBe(timestamp);
    });
    it('maps failure statuses to rejected', () => {
        const interruption = buildToolApprovalInterruption(runId, createRawInterruption('failed'), timestamp, 0);
        expect(interruption.status).toBe('rejected');
    });
    it('defaults unknown statuses to pending', () => {
        const interruption = buildToolApprovalInterruption(runId, createRawInterruption('requires_action'), timestamp, 0);
        expect(interruption.status).toBe('pending');
        expect(interruption.resolvedAt).toBeUndefined();
    });
});
