import { describe, expect, it } from 'vitest';

import {
  TERMINAL_OUTPUT_REPLAY_MAX_BYTES,
  appendTerminalOutputReplayChunk,
  createTerminalOutputReplayBuffer,
  getLatestTerminalOutputReplayChunkId,
  listTerminalOutputReplayChunksSince,
} from './output-replay-buffer.js';

describe('terminal output replay buffer', () => {
  it('starts empty', () => {
    const bufferState = createTerminalOutputReplayBuffer();
    expect(bufferState).toEqual({ chunks: [], totalBytes: 0, nextId: 1 });
    expect(getLatestTerminalOutputReplayChunkId(bufferState)).toBe(0);
  });

  it('appends chunks with incrementing ids', () => {
    const bufferState = createTerminalOutputReplayBuffer();
    const first = appendTerminalOutputReplayChunk(bufferState, 'prompt> ');
    const second = appendTerminalOutputReplayChunk(bufferState, 'ls\r\n');

    expect(first).toEqual({ id: 1, data: 'prompt> ', bytes: 8 });
    expect(second).toEqual({ id: 2, data: 'ls\r\n', bytes: 4 });
    expect(getLatestTerminalOutputReplayChunkId(bufferState)).toBe(2);
  });

  it('lists chunks after a replay cursor', () => {
    const bufferState = createTerminalOutputReplayBuffer();
    appendTerminalOutputReplayChunk(bufferState, 'prompt> ');
    appendTerminalOutputReplayChunk(bufferState, 'ls\r\n');
    appendTerminalOutputReplayChunk(bufferState, 'file.txt\r\n');

    expect(listTerminalOutputReplayChunksSince(bufferState, 1).map((chunk) => chunk.data)).toEqual([
      'ls\r\n',
      'file.txt\r\n',
    ]);
  });

  it('trims old chunks beyond max bytes', () => {
    const bufferState = createTerminalOutputReplayBuffer();
    appendTerminalOutputReplayChunk(bufferState, '1234', 8);
    appendTerminalOutputReplayChunk(bufferState, '5678', 8);
    appendTerminalOutputReplayChunk(bufferState, '90', 8);

    expect(bufferState.chunks.map((chunk) => chunk.data)).toEqual(['5678', '90']);
    expect(bufferState.totalBytes).toBe(6);
  });

  it('trims oversized single chunks to the configured max bytes', () => {
    const bufferState = createTerminalOutputReplayBuffer();
    const chunk = appendTerminalOutputReplayChunk(bufferState, 'abcdefghij', 4);

    expect(chunk?.data).toBe('ghij');
    expect(chunk?.bytes).toBe(4);
    expect(bufferState.totalBytes).toBe(4);
  });

  it('does not split multibyte characters when trimming oversized chunks', () => {
    const bufferState = createTerminalOutputReplayBuffer();
    const chunk = appendTerminalOutputReplayChunk(bufferState, '🙂x', 2);

    expect(chunk?.data).toBe('x');
    expect(chunk?.bytes).toBe(1);
    expect(bufferState.totalBytes).toBe(1);
  });

  it('uses the default max bytes when not provided', () => {
    const bufferState = createTerminalOutputReplayBuffer();
    const chunk = appendTerminalOutputReplayChunk(bufferState, 'ok');

    expect(chunk?.bytes).toBe(2);
    expect(TERMINAL_OUTPUT_REPLAY_MAX_BYTES).toBe(64 * 1024);
  });
});
