/**
 * @license
 * Copyright 2026 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import { describe, it, expect, beforeEach, vi } from 'vitest';
import { UpdateTopicTool } from './topicTool.js';
import { TopicState } from '../config/topicState.js';
import { MessageBus } from '../confirmation-bus/message-bus.js';
import type { PolicyEngine } from '../policy/policy-engine.js';
import {
  UPDATE_TOPIC_TOOL_NAME,
  TOPIC_PARAM_TITLE,
  TOPIC_PARAM_SUMMARY,
  TOPIC_PARAM_STRATEGIC_INTENT,
} from './definitions/base-declarations.js';
import type { Config } from '../config/config.js';

describe('TopicState', () => {
  let state: TopicState;

  beforeEach(() => {
    state = new TopicState();
  });

  it('should store and retrieve topic title and intent', () => {
    expect(state.getTopic()).toBeUndefined();
    expect(state.getIntent()).toBeUndefined();
    const success = state.setTopic('Test Topic', 'Test Intent');
    expect(success).toBe(true);
    expect(state.getTopic()).toBe('Test Topic');
    expect(state.getIntent()).toBe('Test Intent');
  });

  it('should sanitize newlines and carriage returns', () => {
    state.setTopic('Topic\nWith\r\nLines', 'Intent\nWith\r\nLines');
    expect(state.getTopic()).toBe('Topic With Lines');
    expect(state.getIntent()).toBe('Intent With Lines');
  });

  it('should trim whitespace', () => {
    state.setTopic('  Spaced Topic   ', '  Spaced Intent   ');
    expect(state.getTopic()).toBe('Spaced Topic');
    expect(state.getIntent()).toBe('Spaced Intent');
  });

  it('should reject empty or whitespace-only inputs', () => {
    expect(state.setTopic('', '')).toBe(false);
  });

  it('should reset topic and intent', () => {
    state.setTopic('Test Topic', 'Test Intent');
    state.reset();
    expect(state.getTopic()).toBeUndefined();
    expect(state.getIntent()).toBeUndefined();
  });
});

describe('UpdateTopicTool', () => {
  let tool: UpdateTopicTool;
  let mockMessageBus: MessageBus;
  let mockConfig: Config;

  beforeEach(() => {
    mockMessageBus = new MessageBus(vi.mocked({} as PolicyEngine));
    // Mock enough of Config to satisfy the tool
    mockConfig = {
      topicState: new TopicState(),
    } as unknown as Config;
    tool = new UpdateTopicTool(mockConfig, mockMessageBus);
  });

  it('should have correct name and display name', () => {
    expect(tool.name).toBe(UPDATE_TOPIC_TOOL_NAME);
    expect(tool.displayName).toBe('Update Topic Context');
  });

  it('should update TopicState and include strategic intent on execute', async () => {
    const invocation = tool.build({
      [TOPIC_PARAM_TITLE]: 'New Chapter',
      [TOPIC_PARAM_SUMMARY]: 'The goal is to implement X. Previously we did Y.',
      [TOPIC_PARAM_STRATEGIC_INTENT]: 'Initial Move',
    });
    const result = await invocation.execute({
      abortSignal: new AbortController().signal,
    });

    expect(result.llmContent).toContain('Current topic: "New Chapter"');
    expect(result.llmContent).toContain(
      'Topic summary: The goal is to implement X. Previously we did Y.',
    );
    expect(result.llmContent).toContain('Strategic Intent: Initial Move');
    expect(mockConfig.topicState.getTopic()).toBe('New Chapter');
    expect(mockConfig.topicState.getIntent()).toBe('Initial Move');
    expect(result.returnDisplay).toContain('## 📂 Topic: **New Chapter**');
    expect(result.returnDisplay).toContain('**Summary:**');
    expect(result.returnDisplay).toContain(
      '> [!STRATEGY]\n> **Intent:** Initial Move',
    );
  });

  it('should render only intent for tactical updates (same topic)', async () => {
    mockConfig.topicState.setTopic('New Chapter');

    const invocation = tool.build({
      [TOPIC_PARAM_TITLE]: 'New Chapter',
      [TOPIC_PARAM_STRATEGIC_INTENT]: 'Subsequent Move',
    });
    const result = await invocation.execute({
      abortSignal: new AbortController().signal,
    });

    expect(result.returnDisplay).not.toContain('## 📂 Topic:');
    expect(result.returnDisplay).toBe(
      '> [!STRATEGY]\n> **Intent:** Subsequent Move',
    );
    expect(result.llmContent).toBe('Strategic Intent: Subsequent Move');
  });

  it('should return error if strategic_intent is missing', async () => {
    try {
      tool.build({
        [TOPIC_PARAM_TITLE]: 'Title',
      });
      expect.fail('Should have thrown validation error');
    } catch (e: unknown) {
      if (e instanceof Error) {
        expect(e.message).toContain(
          "must have required property 'strategic_intent'",
        );
      } else {
        expect.fail('Expected Error instance');
      }
    }
    expect(mockConfig.topicState.getTopic()).toBeUndefined();
  });
});
