import { describe, it, expect } from 'vitest';
import type { SessionNotification } from '@agentclientprotocol/sdk';
import { mapSessionUpdate } from '../acp-event-map.js';
import type { AcpToolSnapshot } from '../acp-tool-snapshot.js';

/**
 * Pure event-mapping shared by the one-shot ACP dispatch (AcpStreamContext) and
 * the warm ACP backend (Phase 2). Mirrors the original handleSessionUpdate switch
 * verbatim but returns normalized AgentEvents instead of publishing broker frames.
 */
describe('mapSessionUpdate (shared ACP event mapping)', () => {
  function note(update: SessionNotification['update']): SessionNotification {
    return { sessionId: 's1', update };
  }

  it('maps an agent_message_chunk text → a text event', () => {
    const events = mapSessionUpdate(
      note({ sessionUpdate: 'agent_message_chunk', content: { type: 'text', text: 'hello' } }),
    );
    expect(events).toEqual([{ type: 'text', text: 'hello' }]);
  });

  it('maps an agent_thought_chunk text → a reasoning event', () => {
    const events = mapSessionUpdate(
      note({ sessionUpdate: 'agent_thought_chunk', content: { type: 'text', text: 'thinking' } }),
    );
    expect(events).toEqual([{ type: 'reasoning', text: 'thinking' }]);
  });

  it('ignores non-text content on message/thought chunks', () => {
    const img = mapSessionUpdate(
      note({
        sessionUpdate: 'agent_message_chunk',
        content: { type: 'image', data: 'x', mimeType: 'image/png' },
      } as never),
    );
    expect(img).toEqual([]);
  });

  it('maps a tool_call → a tool_call event with a merged snapshot', () => {
    const events = mapSessionUpdate(
      note({
        sessionUpdate: 'tool_call',
        toolCallId: 't1',
        title: 'read_file',
        status: 'pending',
        rawInput: { path: 'a.ts' },
      } as never),
    );
    expect(events).toHaveLength(1);
    expect(events[0]!.type).toBe('tool_call');
    const snap = (events[0] as { type: 'tool_call'; toolCall: AcpToolSnapshot }).toolCall;
    expect(snap.toolCallId).toBe('t1');
    expect(snap.title).toBe('read_file');
    expect(snap.status).toBe('pending');
    expect(snap.rawInput).toEqual({ path: 'a.ts' });
  });

  it('maps a tool_call_update → a tool_update event merged over the prior snapshot', () => {
    const prior = new Map<string, AcpToolSnapshot>([
      ['t1', { toolCallId: 't1', title: 'read_file', status: 'pending', rawInput: { path: 'a.ts' } }],
    ]);
    const events = mapSessionUpdate(
      note({
        sessionUpdate: 'tool_call_update',
        toolCallId: 't1',
        status: 'completed',
        rawOutput: 'file body',
      } as never),
      prior,
    );
    expect(events).toHaveLength(1);
    expect(events[0]!.type).toBe('tool_update');
    const snap = (events[0] as { type: 'tool_update'; toolCall: AcpToolSnapshot }).toolCall;
    expect(snap.toolCallId).toBe('t1');
    // merged: title carried from prior, status updated, output added, input retained
    expect(snap.title).toBe('read_file');
    expect(snap.status).toBe('completed');
    expect(snap.rawOutput).toBe('file body');
    expect(snap.rawInput).toEqual({ path: 'a.ts' });
  });

  it('maps available_commands_update → a commands event', () => {
    const events = mapSessionUpdate(
      note({
        sessionUpdate: 'available_commands_update',
        availableCommands: [
          { name: 'plan', description: 'make a plan' },
          { name: 'review', description: null },
        ],
      } as never),
    );
    expect(events).toEqual([
      {
        type: 'commands',
        commands: [
          { name: 'plan', description: 'make a plan' },
          { name: 'review', description: undefined },
        ],
      },
    ]);
  });

  it('returns [] for unhandled update kinds (plan, mode change)', () => {
    expect(mapSessionUpdate(note({ sessionUpdate: 'plan', entries: [] } as never))).toEqual([]);
    expect(
      mapSessionUpdate(note({ sessionUpdate: 'current_mode_update', currentModeId: 'code' } as never)),
    ).toEqual([]);
  });
});
