import { describe, expect, it } from 'vitest';
import { Interrupt, InterruptError, InterruptState, interruptFromAgent } from '../interrupt.js';
import { InterruptResponseContent } from '../types/interrupt.js';
describe('Interrupt', () => {
    it('constructs with all fields and supports response mutation', () => {
        const interrupt = new Interrupt({
            id: 'int-1',
            name: 'confirm_action',
            reason: 'Please confirm',
            response: 'approved',
        });
        expect(interrupt).toEqual({
            id: 'int-1',
            name: 'confirm_action',
            reason: 'Please confirm',
            response: 'approved',
            source: 'hook',
        });
        // response is mutable after construction
        interrupt.response = 'changed';
        expect(interrupt.response).toBe('changed');
    });
    it('round-trips through JSON serialization with complex data', () => {
        const original = new Interrupt({
            id: 'int-1',
            name: 'test',
            reason: { complex: { nested: 'data' } },
            response: ['array', 'response'],
        });
        const serialized = JSON.stringify(original);
        const deserialized = Interrupt.fromJSON(JSON.parse(serialized));
        expect(deserialized).toEqual(original);
    });
    it('omits undefined reason/response from toJSON', () => {
        const interrupt = new Interrupt({ id: 'int-1', name: 'test' });
        const json = interrupt.toJSON();
        expect(json).toStrictEqual({ id: 'int-1', name: 'test', source: 'hook' });
        expect('reason' in json).toBe(false);
        expect('response' in json).toBe(false);
    });
});
describe('InterruptError', () => {
    it('creates catchable error with single interrupt', () => {
        const interrupt = new Interrupt({ id: 'int-1', name: 'confirm_delete' });
        const error = new InterruptError(interrupt);
        expect(error).toBeInstanceOf(Error);
        expect(error).toMatchObject({
            name: 'InterruptError',
            message: 'Interrupt raised: confirm_delete',
            interrupts: [interrupt],
        });
    });
    it('creates error with multiple interrupts', () => {
        const a = new Interrupt({ id: 'int-1', name: 'security_check' });
        const b = new Interrupt({ id: 'int-2', name: 'budget_check' });
        const error = new InterruptError([a, b]);
        expect(error).toBeInstanceOf(Error);
        expect(error).toMatchObject({
            name: 'InterruptError',
            message: '2 interrupts raised: security_check, budget_check',
            interrupts: [a, b],
        });
    });
});
describe('InterruptState', () => {
    describe('getOrCreateInterrupt', () => {
        it('creates new interrupt and stores it', () => {
            const state = new InterruptState();
            const interrupt = state.getOrCreateInterrupt('int-1', 'test', 'reason');
            expect(interrupt).toEqual({ id: 'int-1', name: 'test', reason: 'reason', source: 'hook' });
            expect(state.interrupts['int-1']).toBe(interrupt);
            expect(state.getInterruptsList()).toStrictEqual([interrupt]);
        });
        it('returns existing interrupt by ID without overwriting', () => {
            const state = new InterruptState();
            const first = state.getOrCreateInterrupt('int-1', 'test', 'reason');
            first.response = 'user response';
            const second = state.getOrCreateInterrupt('int-1', 'different', 'different reason');
            expect(second).toBe(first);
            expect(second.response).toBe('user response');
        });
        it('creates separate interrupts for different IDs with same name', () => {
            const state = new InterruptState();
            state.activate();
            const first = state.getOrCreateInterrupt('tool:tool-1:0:confirm', 'confirm', 'reason');
            first.response = { approved: true };
            const second = state.getOrCreateInterrupt('tool:tool-2:0:confirm', 'confirm', 'reason');
            expect(second).not.toBe(first);
            expect(second.id).toBe('tool:tool-2:0:confirm');
            expect(second.response).toBeUndefined();
        });
        it('creates interrupt with preemptive response', () => {
            const state = new InterruptState();
            const interrupt = state.getOrCreateInterrupt('int-1', 'confirm', 'reason', 'pre-approved');
            expect(interrupt).toEqual({
                id: 'int-1',
                name: 'confirm',
                reason: 'reason',
                response: 'pre-approved',
                source: 'hook',
            });
        });
        it('ignores preemptive response when interrupt already exists', () => {
            const state = new InterruptState();
            const first = state.getOrCreateInterrupt('int-1', 'confirm', 'reason');
            first.response = 'user response';
            const second = state.getOrCreateInterrupt('int-1', 'confirm', 'reason', 'preemptive');
            expect(second).toBe(first);
            expect(second).toEqual({
                id: 'int-1',
                name: 'confirm',
                reason: 'reason',
                response: 'user response',
                source: 'hook',
            });
        });
    });
    describe('activate / deactivate', () => {
        it('deactivate clears all state', () => {
            const state = new InterruptState();
            state.getOrCreateInterrupt('int-1', 'test');
            state.activate();
            expect(state.activated).toBe(true);
            state.deactivate();
            expect(state).toMatchObject({
                interrupts: {},
                resumeResponses: undefined,
                activated: false,
            });
        });
    });
    describe('resume', () => {
        it('does nothing when not activated', () => {
            const state = new InterruptState();
            state.getOrCreateInterrupt('int-1', 'test');
            state.resume([new InterruptResponseContent({ interruptId: 'int-1', response: 'yes' })]);
            expect(state.interrupts['int-1'].response).toBeUndefined();
        });
        it('populates interrupt responses and stores resumeResponses when activated', () => {
            const state = new InterruptState();
            state.getOrCreateInterrupt('int-1', 'first');
            state.getOrCreateInterrupt('int-2', 'second');
            state.activate();
            const responses = [
                new InterruptResponseContent({ interruptId: 'int-1', response: 'response1' }),
                new InterruptResponseContent({ interruptId: 'int-2', response: { complex: 'data' } }),
            ];
            state.resume(responses);
            expect(state.interrupts['int-1']).toMatchObject({ response: 'response1' });
            expect(state.interrupts['int-2']).toMatchObject({ response: { complex: 'data' } });
            expect(state.resumeResponses).toBe(responses);
        });
        it('throws error for unknown interrupt ID', () => {
            const state = new InterruptState();
            state.getOrCreateInterrupt('int-1', 'test');
            state.activate();
            expect(() => {
                state.resume([new InterruptResponseContent({ interruptId: 'unknown', response: 'yes' })]);
            }).toThrow('interrupt_id=<unknown> | no interrupt found');
        });
    });
    describe('serialization', () => {
        it('round-trips through JSON with full state', () => {
            const original = new InterruptState();
            original.getOrCreateInterrupt('int-1', 'test', { complex: 'reason' });
            original.interrupts['int-1'].response = ['array', 'response'];
            original.activate();
            const serialized = JSON.stringify(original);
            const deserialized = InterruptState.fromJSON(JSON.parse(serialized));
            expect(deserialized.toJSON()).toStrictEqual(original.toJSON());
        });
        it('round-trips pendingToolExecution through JSON', () => {
            const original = new InterruptState();
            original.getOrCreateInterrupt('int-1', 'test');
            original.activate();
            original.setPendingToolExecution({
                assistantMessageData: {
                    role: 'assistant',
                    content: [{ toolUse: { name: 'tool', toolUseId: 't-1', input: {} } }],
                },
                completedToolResults: {
                    't-0': { toolResult: { toolUseId: 't-0', status: 'success', content: [] } },
                },
            });
            const serialized = JSON.stringify(original);
            const deserialized = InterruptState.fromJSON(JSON.parse(serialized));
            expect(deserialized.toJSON()).toStrictEqual(original.toJSON());
            expect(deserialized.pendingToolExecution).toStrictEqual(original.pendingToolExecution);
        });
        it('deserializes state with resumeResponses', () => {
            const state = InterruptState.fromJSON({
                interrupts: {
                    'int-1': { id: 'int-1', name: 'test', reason: 'reason', response: 'yes' },
                },
                resumeResponses: [{ interruptResponse: { interruptId: 'int-1', response: 'yes' } }],
                activated: true,
            });
            expect(state).toMatchObject({
                activated: true,
                interrupts: {
                    'int-1': { id: 'int-1', name: 'test', reason: 'reason', response: 'yes' },
                },
                resumeResponses: [{ interruptResponse: { interruptId: 'int-1', response: 'yes' } }],
            });
        });
    });
});
describe('interruptFromAgent', () => {
    // Minimal agent-like object with _interruptState
    function createMockAgent(state) {
        return { _interruptState: state };
    }
    it('returns preemptive response immediately without throwing', () => {
        const state = new InterruptState();
        const agent = createMockAgent(state);
        const result = interruptFromAgent(agent, 'test-id', {
            name: 'confirm',
            reason: 'need approval',
            response: 'pre-approved',
        }, 'tool');
        expect(result).toBe('pre-approved');
        expect(state.interrupts['test-id']).toMatchObject({
            id: 'test-id',
            name: 'confirm',
            reason: 'need approval',
            response: 'pre-approved',
            source: 'tool',
        });
    });
    it('returns resume response over preemptive response for existing interrupt', () => {
        const state = new InterruptState();
        state.getOrCreateInterrupt('test-id', 'confirm', 'need approval');
        state.interrupts['test-id'].response = 'user-provided';
        const agent = createMockAgent(state);
        const result = interruptFromAgent(agent, 'test-id', {
            name: 'confirm',
            reason: 'need approval',
            response: 'preemptive',
        }, 'tool');
        expect(result).toBe('user-provided');
        expect(state.interrupts['test-id']).toMatchObject({
            id: 'test-id',
            name: 'confirm',
            reason: 'need approval',
            response: 'user-provided',
        });
    });
    it('does not interrupt when preemptive response is null', () => {
        const state = new InterruptState();
        const agent = createMockAgent(state);
        const result = interruptFromAgent(agent, 'test-id', {
            name: 'confirm',
            response: null,
        }, 'tool');
        expect(result).toBeNull();
        expect(state.interrupts['test-id']).toMatchObject({
            id: 'test-id',
            name: 'confirm',
            response: null,
            source: 'tool',
        });
    });
});
//# sourceMappingURL=interrupt.test.js.map