import { describe, it, expect } from 'vitest';
import { AgentResult } from '../agent.js';
import { AgentMetrics } from '../../telemetry/meter.js';
import { AgentTrace } from '../../telemetry/tracer.js';
import { Message } from '../messages.js';
import { TextBlock, ReasoningBlock, ToolUseBlock, ToolResultBlock, CachePointBlock } from '../messages.js';
import { CitationsBlock } from '../citations.js';
import { Interrupt } from '../../interrupt.js';
describe('AgentResult', () => {
    describe('toString', () => {
        describe('when content is empty', () => {
            it('returns empty string', () => {
                const message = new Message({
                    role: 'assistant',
                    content: [],
                });
                const result = new AgentResult({
                    stopReason: 'endTurn',
                    lastMessage: message,
                    metrics: new AgentMetrics(),
                    invocationState: {},
                });
                expect(result.toString()).toBe('');
            });
        });
        describe('when content has single TextBlock', () => {
            it('returns the text content', () => {
                const message = new Message({
                    role: 'assistant',
                    content: [new TextBlock('Hello, world!')],
                });
                const result = new AgentResult({
                    stopReason: 'endTurn',
                    lastMessage: message,
                    metrics: new AgentMetrics(),
                    invocationState: {},
                });
                expect(result.toString()).toBe('Hello, world!');
            });
        });
        describe('when content has multiple TextBlocks', () => {
            it('returns all text joined with newlines', () => {
                const message = new Message({
                    role: 'assistant',
                    content: [new TextBlock('First line'), new TextBlock('Second line'), new TextBlock('Third line')],
                });
                const result = new AgentResult({
                    stopReason: 'endTurn',
                    lastMessage: message,
                    metrics: new AgentMetrics(),
                    invocationState: {},
                });
                expect(result.toString()).toBe('First line\nSecond line\nThird line');
            });
        });
        describe('when content has ReasoningBlock with text', () => {
            it('returns the reasoning text with prefix', () => {
                const message = new Message({
                    role: 'assistant',
                    content: [new ReasoningBlock({ text: 'Let me think about this...' })],
                });
                const result = new AgentResult({
                    stopReason: 'endTurn',
                    lastMessage: message,
                    metrics: new AgentMetrics(),
                    invocationState: {},
                });
                expect(result.toString()).toBe('💭 Reasoning:\n   Let me think about this...');
            });
        });
        describe('when content has ReasoningBlock without text', () => {
            it('returns empty string (reasoning block is skipped)', () => {
                const message = new Message({
                    role: 'assistant',
                    content: [new ReasoningBlock({ signature: 'abc123' })],
                });
                const result = new AgentResult({
                    stopReason: 'endTurn',
                    lastMessage: message,
                    metrics: new AgentMetrics(),
                    invocationState: {},
                });
                expect(result.toString()).toBe('');
            });
        });
        describe('when content has mixed TextBlock and ReasoningBlock', () => {
            it('returns all text joined with newlines', () => {
                const message = new Message({
                    role: 'assistant',
                    content: [
                        new TextBlock('Here is my response.'),
                        new ReasoningBlock({ text: 'I reasoned carefully.' }),
                        new TextBlock('Additional context.'),
                    ],
                });
                const result = new AgentResult({
                    stopReason: 'endTurn',
                    lastMessage: message,
                    metrics: new AgentMetrics(),
                    invocationState: {},
                });
                expect(result.toString()).toBe('Here is my response.\n💭 Reasoning:\n   I reasoned carefully.\nAdditional context.');
            });
        });
        describe('when content has only non-text blocks', () => {
            it('returns empty string', () => {
                const message = new Message({
                    role: 'assistant',
                    content: [
                        new ToolUseBlock({ name: 'calc', toolUseId: 'id-1', input: { a: 1, b: 2 } }),
                        new ToolResultBlock({
                            toolUseId: 'id-1',
                            status: 'success',
                            content: [new TextBlock('3')],
                        }),
                        new CachePointBlock({ cacheType: 'default' }),
                    ],
                });
                const result = new AgentResult({
                    stopReason: 'toolUse',
                    lastMessage: message,
                    metrics: new AgentMetrics(),
                    invocationState: {},
                });
                expect(result.toString()).toBe('');
            });
        });
        describe('when content has mixed text and non-text blocks', () => {
            it('returns only text from TextBlock and ReasoningBlock', () => {
                const message = new Message({
                    role: 'assistant',
                    content: [
                        new TextBlock('Before tool'),
                        new ToolUseBlock({ name: 'calc', toolUseId: 'id-1', input: { a: 1, b: 2 } }),
                        new ReasoningBlock({ text: 'Thinking...' }),
                        new CachePointBlock({ cacheType: 'default' }),
                        new TextBlock('After tool'),
                    ],
                });
                const result = new AgentResult({
                    stopReason: 'toolUse',
                    lastMessage: message,
                    metrics: new AgentMetrics(),
                    invocationState: {},
                });
                expect(result.toString()).toBe('Before tool\n💭 Reasoning:\n   Thinking...\nAfter tool');
            });
        });
        describe('when interrupts are present', () => {
            it('returns JSON-stringified interrupts, taking priority over text content', () => {
                const message = new Message({
                    role: 'assistant',
                    content: [new TextBlock('ignored')],
                });
                const interrupt = new Interrupt({ id: 'i-1', name: 'confirm', reason: 'ok?' });
                const result = new AgentResult({
                    stopReason: 'interrupt',
                    lastMessage: message,
                    metrics: new AgentMetrics(),
                    invocationState: {},
                    interrupts: [interrupt],
                });
                expect(result.toString()).toBe(JSON.stringify([interrupt]));
            });
            it('falls through when interrupts array is empty', () => {
                const message = new Message({
                    role: 'assistant',
                    content: [new TextBlock('Hello')],
                });
                const result = new AgentResult({
                    stopReason: 'endTurn',
                    lastMessage: message,
                    metrics: new AgentMetrics(),
                    invocationState: {},
                    interrupts: [],
                });
                expect(result.toString()).toBe('Hello');
            });
        });
        describe('when structuredOutput is present', () => {
            it('returns JSON-stringified structured output, taking priority over text content', () => {
                const message = new Message({
                    role: 'assistant',
                    content: [new TextBlock('ignored')],
                });
                const structuredOutput = { answer: 42, note: 'hello' };
                const result = new AgentResult({
                    stopReason: 'endTurn',
                    lastMessage: message,
                    metrics: new AgentMetrics(),
                    invocationState: {},
                    structuredOutput,
                });
                expect(result.toString()).toBe(JSON.stringify(structuredOutput));
            });
        });
        describe('when interrupts and structuredOutput are both present', () => {
            it('returns interrupts, taking priority over structuredOutput', () => {
                const message = new Message({
                    role: 'assistant',
                    content: [new TextBlock('ignored')],
                });
                const interrupt = new Interrupt({ id: 'i-1', name: 'confirm' });
                const structuredOutput = { answer: 42 };
                const result = new AgentResult({
                    stopReason: 'interrupt',
                    lastMessage: message,
                    metrics: new AgentMetrics(),
                    invocationState: {},
                    interrupts: [interrupt],
                    structuredOutput,
                });
                expect(result.toString()).toBe(JSON.stringify([interrupt]));
            });
        });
        describe('when content has CitationsBlock', () => {
            it('concatenates generated content text from citations', () => {
                const message = new Message({
                    role: 'assistant',
                    content: [
                        new TextBlock('Here is a citation:'),
                        new CitationsBlock({
                            citations: [
                                {
                                    location: { type: 'documentChar', documentIndex: 0, start: 0, end: 5 },
                                    source: 'doc',
                                    sourceContent: [{ text: 'source text' }],
                                    title: 'Doc',
                                },
                            ],
                            content: [{ text: 'cited fragment' }],
                        }),
                    ],
                });
                const result = new AgentResult({
                    stopReason: 'endTurn',
                    lastMessage: message,
                    metrics: new AgentMetrics(),
                    invocationState: {},
                });
                expect(result.toString()).toBe('Here is a citation:\ncited fragment');
            });
        });
        describe('when called implicitly', () => {
            it('works with String() conversion', () => {
                const message = new Message({
                    role: 'assistant',
                    content: [new TextBlock('Hello')],
                });
                const result = new AgentResult({
                    stopReason: 'endTurn',
                    lastMessage: message,
                    metrics: new AgentMetrics(),
                    invocationState: {},
                });
                expect(String(result)).toBe('Hello');
            });
            it('works with template literals', () => {
                const message = new Message({
                    role: 'assistant',
                    content: [new TextBlock('World')],
                });
                const result = new AgentResult({
                    stopReason: 'endTurn',
                    lastMessage: message,
                    metrics: new AgentMetrics(),
                    invocationState: {},
                });
                expect(`Response: ${result}`).toBe('Response: World');
            });
        });
    });
    describe('contextSize', () => {
        it('returns latestContextSize from metrics', () => {
            const message = new Message({
                role: 'assistant',
                content: [new TextBlock('Hello')],
            });
            const metrics = new AgentMetrics({ latestContextSize: 500 });
            const result = new AgentResult({
                stopReason: 'endTurn',
                lastMessage: message,
                metrics,
                invocationState: {},
            });
            expect(result.contextSize).toBe(500);
        });
        it('returns undefined when metrics has no latestContextSize', () => {
            const message = new Message({
                role: 'assistant',
                content: [new TextBlock('Hello')],
            });
            const result = new AgentResult({
                stopReason: 'endTurn',
                lastMessage: message,
                metrics: new AgentMetrics(),
                invocationState: {},
            });
            expect(result.contextSize).toBeUndefined();
        });
        it('returns undefined when no metrics are available', () => {
            const message = new Message({
                role: 'assistant',
                content: [new TextBlock('Hello')],
            });
            const result = new AgentResult({
                stopReason: 'endTurn',
                lastMessage: message,
                invocationState: {},
            });
            expect(result.contextSize).toBeUndefined();
        });
    });
    describe('projectedContextSize', () => {
        it('returns projectedContextSize from metrics', () => {
            const message = new Message({
                role: 'assistant',
                content: [new TextBlock('Hello')],
            });
            const metrics = new AgentMetrics({ projectedContextSize: 750 });
            const result = new AgentResult({
                stopReason: 'endTurn',
                lastMessage: message,
                metrics,
                invocationState: {},
            });
            expect(result.projectedContextSize).toBe(750);
        });
        it('returns undefined when metrics has no projectedContextSize', () => {
            const message = new Message({
                role: 'assistant',
                content: [new TextBlock('Hello')],
            });
            const result = new AgentResult({
                stopReason: 'endTurn',
                lastMessage: message,
                metrics: new AgentMetrics(),
                invocationState: {},
            });
            expect(result.projectedContextSize).toBeUndefined();
        });
    });
    describe('toJSON', () => {
        it('excludes traces and metrics from serialization', () => {
            const message = new Message({
                role: 'assistant',
                content: [new TextBlock('Hello')],
            });
            const traces = [new AgentTrace('Cycle 1')];
            const metrics = new AgentMetrics();
            const result = new AgentResult({
                stopReason: 'endTurn',
                lastMessage: message,
                traces,
                metrics,
                invocationState: {},
            });
            const json = result.toJSON();
            expect(json).toEqual({
                type: 'agentResult',
                stopReason: 'endTurn',
                lastMessage: message,
            });
        });
        it('includes structuredOutput when present', () => {
            const message = new Message({
                role: 'assistant',
                content: [new TextBlock('Response')],
            });
            const structuredOutput = { field: 'value' };
            const result = new AgentResult({
                stopReason: 'endTurn',
                lastMessage: message,
                structuredOutput,
                invocationState: {},
            });
            const json = result.toJSON();
            expect(json).toHaveProperty('structuredOutput', structuredOutput);
        });
        it('excludes structuredOutput when not present', () => {
            const message = new Message({
                role: 'assistant',
                content: [new TextBlock('Response')],
            });
            const result = new AgentResult({
                stopReason: 'endTurn',
                lastMessage: message,
                invocationState: {},
            });
            const json = result.toJSON();
            expect(json).not.toHaveProperty('structuredOutput');
        });
        it('is automatically used by JSON.stringify()', () => {
            const message = new Message({
                role: 'assistant',
                content: [new TextBlock('Hello')],
            });
            const traces = [new AgentTrace('Cycle 1')];
            const metrics = new AgentMetrics();
            const result = new AgentResult({
                stopReason: 'endTurn',
                lastMessage: message,
                traces,
                metrics,
                invocationState: {},
            });
            const jsonString = JSON.stringify(result);
            const parsed = JSON.parse(jsonString);
            expect(parsed).toEqual({
                type: 'agentResult',
                stopReason: 'endTurn',
                lastMessage: expect.objectContaining({
                    role: 'assistant',
                    content: expect.any(Array),
                }),
            });
        });
        it('preserves traces and metrics as accessible properties', () => {
            const message = new Message({
                role: 'assistant',
                content: [new TextBlock('Hello')],
            });
            const traces = [new AgentTrace('Cycle 1')];
            const metrics = new AgentMetrics();
            const result = new AgentResult({
                stopReason: 'endTurn',
                lastMessage: message,
                traces,
                metrics,
                invocationState: {},
            });
            // Properties are still accessible
            expect({ traces: result.traces, metrics: result.metrics }).toEqual({
                traces,
                metrics,
            });
            // But not in JSON
            const json = result.toJSON();
            expect(json).toEqual({
                type: 'agentResult',
                stopReason: 'endTurn',
                lastMessage: message,
            });
        });
        it('prevents bloated API responses when forwarding result directly', () => {
            const message = new Message({
                role: 'assistant',
                content: [new TextBlock('Response text')],
            });
            // Simulate large traces and metrics from real agent execution
            const traces = [new AgentTrace('Cycle 1'), new AgentTrace('Cycle 2'), new AgentTrace('Cycle 3')];
            const metrics = new AgentMetrics();
            const result = new AgentResult({
                stopReason: 'endTurn',
                lastMessage: message,
                traces,
                metrics,
                invocationState: {},
            });
            // Simulate what happens in Express/Next.js: res.json(result)
            const apiResponse = JSON.parse(JSON.stringify(result));
            // Verify API response is lean - no traces/metrics bloat
            expect(apiResponse).toEqual({
                type: 'agentResult',
                stopReason: 'endTurn',
                lastMessage: expect.objectContaining({
                    role: 'assistant',
                    content: expect.any(Array),
                }),
            });
            expect(apiResponse).not.toHaveProperty('traces');
            expect(apiResponse).not.toHaveProperty('metrics');
        });
    });
});
//# sourceMappingURL=agent.test.js.map