/**
 * Tests for the TASK DISCIPLINE signal — `currentFocus` returned on
 * every taskmanager response — and the OPERATING POSTURE guideline
 * that pairs with it.
 *
 * Production observation (scripts/memory-bench/live-v2-*,
 * agent-agent-v2-v2-s2-multi-topic-1779570320941): Kimi-K2.6 and gpt-5.4
 * lost task focus after cascading tool errors and silently reverted
 * to a prior task instead of completing the current one. The fix is
 * two-pronged:
 *   1. Surface `currentFocus` on every taskmanager response so the
 *      anchor stays visible across error cascades.
 *   2. OPERATING POSTURE explicitly tells the agent: only one task
 *      in_progress at a time, transition before working, finish or
 *      release before switching.
 */
import { describe, test, expect } from '@jest/globals';
import TaskManagerTool from '../taskManagerTool.js';
import { BaseTool, ToolsRegistry } from '../baseTool.js';

const SILENT_LOGGER = { info() {}, warn() {}, error() {}, debug() {} };

function makeTool() {
  const tool = new TaskManagerTool({ description: 'test' });
  tool.logger = SILENT_LOGGER;
  return tool;
}

describe('_buildCurrentFocus — task-discipline signal', () => {
  test('ok=true when exactly one task is in_progress', () => {
    const focus = makeTool()._buildCurrentFocus([
      { id: 'task-1', title: 'Polish README', description: 'Rewrite intro', status: 'in_progress' },
      { id: 'task-2', title: 'Other thing',   description: '...',           status: 'pending' },
    ]);
    expect(focus.ok).toBe(true);
    expect(focus.inProgressCount).toBe(1);
    expect(focus.current).toMatchObject({
      taskId: 'task-1', title: 'Polish README', description: 'Rewrite intro',
    });
    expect(focus.warning).toBeUndefined();
  });

  test('ok=false with warning when 0 tasks are in_progress', () => {
    const focus = makeTool()._buildCurrentFocus([
      { id: 'task-1', title: 'X', description: '...', status: 'pending' },
      { id: 'task-2', title: 'Y', description: '...', status: 'completed' },
    ]);
    expect(focus.ok).toBe(false);
    expect(focus.inProgressCount).toBe(0);
    expect(focus.current).toBeNull();
    expect(focus.warning).toMatch(/in_progress/);
    expect(focus.warning).toMatch(/BEFORE/);
  });

  test('ok=false with warning when 2+ tasks are in_progress', () => {
    const focus = makeTool()._buildCurrentFocus([
      { id: 'task-1', title: 'A', description: '...', status: 'in_progress' },
      { id: 'task-2', title: 'B', description: '...', status: 'in_progress' },
      { id: 'task-3', title: 'C', description: '...', status: 'pending' },
    ]);
    expect(focus.ok).toBe(false);
    expect(focus.inProgressCount).toBe(2);
    expect(Array.isArray(focus.current)).toBe(true);
    expect(focus.current).toHaveLength(2);
    expect(focus.warning).toMatch(/Multiple tasks/);
  });

  test('empty task list reports 0 in_progress with warning', () => {
    const focus = makeTool()._buildCurrentFocus([]);
    expect(focus.ok).toBe(false);
    expect(focus.inProgressCount).toBe(0);
    expect(focus.current).toBeNull();
  });

  test('null/undefined input handled gracefully (no crash)', () => {
    expect(() => makeTool()._buildCurrentFocus(null)).not.toThrow();
    expect(() => makeTool()._buildCurrentFocus(undefined)).not.toThrow();
    const f = makeTool()._buildCurrentFocus(null);
    expect(f.inProgressCount).toBe(0);
  });

  test('long descriptions are truncated to 140 chars', () => {
    const long = 'x'.repeat(500);
    const focus = makeTool()._buildCurrentFocus([
      { id: 'task-1', title: 'T', description: long, status: 'in_progress' },
    ]);
    expect(focus.current.description.length).toBeLessThanOrEqual(141);
    expect(focus.current.description.endsWith('…')).toBe(true);
  });

  test('non-string description is coerced to empty (no crash)', () => {
    const focus = makeTool()._buildCurrentFocus([
      { id: 'task-1', title: 'T', description: null, status: 'in_progress' },
    ]);
    expect(focus.current.description).toBe('');
  });
});

describe('OPERATING POSTURE — TASK DISCIPLINE section', () => {
  // The OPERATING POSTURE block lives in baseTool.js and is built per
  // agent based on its capabilities. We construct a BaseTool-derived
  // tool registry stub and verify the block content directly.
  function buildOperatingPosture(capabilities) {
    // Use the real ToolsRegistry — it's the class that owns
    // generateToolDescriptionsForPrompt. Register fake tools matching
    // the capability list so the block emits its sections.
    const reg = new ToolsRegistry(SILENT_LOGGER);
    for (const id of capabilities) {
      reg.tools.set(id, { id, getDescription: () => `# ${id}` });
    }
    return reg.generateToolDescriptionsForPrompt(capabilities);
  }

  test('section appears when taskmanager capability is present', () => {
    const block = buildOperatingPosture(['memory', 'taskmanager']);
    expect(block).toMatch(/TASK DISCIPLINE/);
    expect(block).toMatch(/ONE.*task.*in_progress/);
    expect(block).toMatch(/Transition first, then work/);
    expect(block).toMatch(/Finish or release/);
    expect(block).toMatch(/Anchor on tool failures/);
    expect(block).toMatch(/currentFocus/);
  });

  test('section does NOT appear without taskmanager capability', () => {
    const block = buildOperatingPosture(['memory']);
    expect(block).not.toMatch(/TASK DISCIPLINE/);
  });

  test('section guidance is descriptive of cognitive effort, not bureaucratic checklist', () => {
    // The aim is "task list reflects actual work", not "fill in fields".
    const block = buildOperatingPosture(['memory', 'taskmanager']);
    expect(block).toMatch(/cognitive effort/);
    // Should describe what to do on tool failures — that's the main
    // failure mode we observed.
    expect(block).toMatch(/tool.*fail/i);
  });
});
