import { describe, it, expect } from '@jest/globals';
import { chatEventToSseFrame, relayErrorFrame } from './sse.writer';

/**
 * Locks the wire shape the SPA consumes. Each frame is:
 *
 *   event: <name>\n
 *   data: <json>\n
 *   \n
 *
 * The terminal blank line is the SSE message separator.
 */

describe('chatEventToSseFrame', () => {
  it('maps token events with the JSON-encoded inner value', () => {
    const frame = chatEventToSseFrame({ token: { token: 't1', value: 'hello' } });
    expect(frame).toBe(`event: token\ndata: {"token":"t1","value":"hello"}\n\n`);
  });

  it('maps citation events', () => {
    const frame = chatEventToSseFrame({
      citation: {
        chunkId: 'c-1',
        documentId: 'd-1',
        source: 's',
        score: 0.42,
        snippet: 'snip',
      },
    });
    expect(frame?.startsWith('event: citation\n')).toBe(true);
    expect(frame).toContain('"chunkId":"c-1"');
  });

  it('maps agent_step to kebab-case `agent-step`', () => {
    const frame = chatEventToSseFrame({
      agentStep: { agent: 'a', step: 's', stepId: '1' },
    });
    expect(frame?.startsWith('event: agent-step\n')).toBe(true);
  });

  it('maps tool_call to kebab-case `tool-call`', () => {
    const frame = chatEventToSseFrame({
      toolCall: { callId: 'c-1', name: 'echo', args: undefined },
    });
    expect(frame?.startsWith('event: tool-call\n')).toBe(true);
  });

  it('maps error events', () => {
    const frame = chatEventToSseFrame({
      error: { code: 'urn:apf-ai:foo', message: 'bad', retriable: false },
    });
    expect(frame).toBe(
      `event: error\ndata: {"code":"urn:apf-ai:foo","message":"bad","retriable":false}\n\n`,
    );
  });

  it('maps done as the terminal frame', () => {
    const frame = chatEventToSseFrame({
      done: { stats: { tokensIn: 1, tokensOut: 2, chunksRetrieved: 0 } },
    });
    expect(frame?.startsWith('event: done\n')).toBe(true);
    expect(frame?.endsWith('\n\n')).toBe(true);
  });

  it('returns null when no oneof case is populated', () => {
    expect(chatEventToSseFrame({})).toBeNull();
  });
});

describe('relayErrorFrame', () => {
  it('emits an error frame with the supplied code + message + retriable flag', () => {
    expect(relayErrorFrame('urn:apf-ai:relay_error', 'kaboom', true)).toBe(
      `event: error\ndata: {"code":"urn:apf-ai:relay_error","message":"kaboom","retriable":true}\n\n`,
    );
  });

  it('defaults `retriable` to false when omitted', () => {
    const frame = relayErrorFrame('urn:apf-ai:foo', 'msg');
    expect(frame).toContain('"retriable":false');
  });
});
