import { describe, test, expect } from 'bun:test';
import { spliceRunningTasks } from './running-tasks';
import type { ChatMessage } from './types';

// The two phrasings the harness actually produces, copied from a live transcript. Both carry the task id,
// which is the only part this relies on.
const bashOutput = (id: string) => `Command running in background with ID: ${id}. Output is being written to: /tmp/x`;
const monitorOutput = (id: string) => `Monitor started (task ${id}, timeout 900000ms). You will be notified.`;

const tool = (toolName: string, output: string): ChatMessage => ({
  role: 'tool',
  toolName,
  toolInput: {},
  toolCallId: `call-${output.slice(-8)}`,
  output,
});

const user = (text: string): ChatMessage => ({ role: 'user', text });
const assistant = (text: string): ChatMessage => ({ role: 'assistant', text });

const roles = (messages: ChatMessage[]) => messages.map((m) => (m.role === 'task' ? `task:${m.taskId}` : m.role));

describe('spliceRunningTasks', () => {
  test('a recovered row lands right after the tool call that started it', () => {
    const messages = [
      user('start a job'),
      tool('Bash', bashOutput('bg1')),
      assistant('started it'),
      user('anything else?'),
    ];
    const out = spliceRunningTasks(messages, [{ taskId: 'bg1', description: 'long job' }]);
    expect(roles(out)).toEqual(['user', 'tool', 'task:bg1', 'assistant', 'user']);
  });

  test('several tasks each land at their own anchor', () => {
    const messages = [
      tool('Bash', bashOutput('bg1')),
      assistant('one'),
      tool('Monitor', monitorOutput('bg2')),
      assistant('two'),
    ];
    const out = spliceRunningTasks(messages, [
      { taskId: 'bg2', description: 'watcher' },
      { taskId: 'bg1', description: 'ticker' },
    ]);
    expect(roles(out)).toEqual(['tool', 'task:bg1', 'assistant', 'tool', 'task:bg2', 'assistant']);
  });

  test('a task whose tool call is not in the loaded window still appends', () => {
    // Reverse pagination means the anchor can genuinely be off-screen. Appending is worse than in place
    // and much better than dropping the row, which would hide running work.
    const messages = [assistant('scrolled past the start')];
    const out = spliceRunningTasks(messages, [{ taskId: 'gone', description: 'orphan' }]);
    expect(roles(out)).toEqual(['assistant', 'task:gone']);
  });

  test('a task already on screen is never added twice', () => {
    const messages: ChatMessage[] = [
      tool('Bash', bashOutput('bg1')),
      { role: 'task', taskId: 'bg1', description: 'long job' },
    ];
    expect(spliceRunningTasks(messages, [{ taskId: 'bg1', description: 'long job' }])).toBe(messages);
  });

  test('nothing to recover returns the same array, not a copy', () => {
    const messages = [user('hi')];
    expect(spliceRunningTasks(messages, [])).toBe(messages);
  });

  test('the id is matched in tool output only, never in prose that mentions it', () => {
    // The id is quoted in the conversation — this very session does it — and a message about a task is
    // not the tool call that started one.
    const messages = [user('what happened to bg1?'), assistant('bg1 is still running')];
    const out = spliceRunningTasks(messages, [{ taskId: 'bg1', description: 'ticker' }]);
    expect(roles(out)).toEqual(['user', 'assistant', 'task:bg1']);
  });

  test('the spawning call wins over a later command that merely prints the id', () => {
    // Caught on real data: a diagnostic script that grepped the transcript printed both live task ids,
    // and matching the most recent mention parked the rows next to that script instead of next to the
    // calls that started them. The id cannot exist before the call that mints it, so first wins.
    const messages = [
      tool('Bash', bashOutput('bg1')),
      assistant('started'),
      tool('Bash', `checking… found beqzu: bg1 still alive`),
      assistant('yes, still going'),
    ];
    const out = spliceRunningTasks(messages, [{ taskId: 'bg1', description: 'ticker' }]);
    expect(roles(out)).toEqual(['tool', 'task:bg1', 'assistant', 'tool', 'assistant']);
  });

  test('the description and type survive the round trip', () => {
    const messages = [tool('Bash', bashOutput('bg1'))];
    const out = spliceRunningTasks(messages, [{ taskId: 'bg1', description: 'ticker', taskType: 'local_bash' }]);
    expect(out[1]).toEqual({ role: 'task', taskId: 'bg1', description: 'ticker', taskType: 'local_bash' });
  });
});
