import { describe, it, expect } from 'vitest';
import type { Message } from '../../llm/openai-compat.js';
import type { AgentLoopCallbacks } from './types.js';
import type { ToolCallResult } from './tool-execution.js';
import type { DispatchedImage } from './tool-dispatcher.js';
import { recordToolResult } from './tool-result-recorder.js';

const baseResult = (overrides: Partial<ToolCallResult> = {}): ToolCallResult => ({
  toolCallId: 'tc-1',
  result: 'ok',
  countedAsRegularToolUse: true,
  ...overrides,
});

describe('recordToolResult', () => {
  it('fires side effects in order: onToolResult → increment+onMemoryCheckpoint → messages.push → images', () => {
    const order: string[] = [];
    const messages: Message[] = [];
    const pendingImages: DispatchedImage[] = [];
    const callbacks: AgentLoopCallbacks = {
      onToolResult: () => { order.push('onToolResult'); },
      onMemoryCheckpoint: () => { order.push('onMemoryCheckpoint'); },
    };
    const result = baseResult({ images: [{ dataUrl: 'data:image/png;base64,AAAA' }] });

    const updated = recordToolResult('Read', result, messages, callbacks, pendingImages, 3);

    // messages.push happens after callbacks; assert relative order via lengths captured in callbacks
    expect(order).toEqual(['onToolResult', 'onMemoryCheckpoint']);
    expect(updated).toBe(4);
    expect(messages).toHaveLength(1);
    expect(pendingImages).toHaveLength(1);
  });

  it('increments the count and checkpoints when countedAsRegularToolUse is true', () => {
    let checkpointed = -1;
    const callbacks: AgentLoopCallbacks = {
      onMemoryCheckpoint: (n: number) => { checkpointed = n; },
    };
    const updated = recordToolResult('Read', baseResult(), [], callbacks, [], 5);
    expect(updated).toBe(6);
    expect(checkpointed).toBe(6);
  });

  it('does NOT increment or checkpoint when countedAsRegularToolUse is false', () => {
    let called = false;
    const callbacks: AgentLoopCallbacks = {
      onMemoryCheckpoint: () => { called = true; },
    };
    const messages: Message[] = [];
    const updated = recordToolResult('Read', baseResult({ countedAsRegularToolUse: false }), messages, callbacks, [], 7);
    expect(updated).toBe(7);
    expect(called).toBe(false);
    // the tool-result message is still pushed
    expect(messages).toHaveLength(1);
  });

  it('reports isError=true to onToolResult when the result starts with "Error: "', () => {
    let observed: boolean | undefined;
    const callbacks: AgentLoopCallbacks = {
      onToolResult: (_name, meta) => { observed = meta.isError; },
    };
    recordToolResult('Read', baseResult({ result: 'Error: nope' }), [], callbacks, [], 0);
    expect(observed).toBe(true);
  });

  it('does not push to pendingImages when the result has no images', () => {
    const pendingImages: DispatchedImage[] = [];
    recordToolResult('Read', baseResult(), [], undefined, pendingImages, 0);
    expect(pendingImages).toHaveLength(0);
  });
});
