import { describe, expect, it } from "vitest";
import {
  classifyTerminalOutputInvalid,
  containsTerminalControlPayload,
  containsTerminalControlPayloadPrefix,
} from "./terminal-output-classifier.js";

describe("terminal output classifier", () => {
  it("returns the leaked textual tool name instead of guessing from visible tools", () => {
    expect(
      classifyTerminalOutputInvalid('<tool_call>shell {"command":"npm test"}', [
        "read-file",
        "shell",
      ]),
    ).toEqual({
      invalidKind: "textual-tool-call",
      reasonCode: "textual-tool-call",
      toolKnown: true,
      toolName: "shell",
    });
    expect(
      classifyTerminalOutputInvalid('<tool_call>unknown_tool {"command":"npm test"}', [
        "read-file",
        "shell",
      ]),
    ).toEqual({
      invalidKind: "textual-tool-call",
      reasonCode: "textual-tool-call",
      toolKnown: false,
      toolName: "unknown_tool",
    });
  });

  it("ignores control-like JSON inside fenced code blocks", () => {
    expect(
      containsTerminalControlPayload(
        'Example:\n```json\n{"kind":"tool","toolName":"shell"}\n```\nThis is documentation.',
        ["shell"],
      ),
    ).toBe(false);
  });

  it("recognizes standalone runtime decision JSON", () => {
    expect(
      containsTerminalControlPayload('{"kind":"tool","toolName":"shell","input":{}}', ["shell"]),
    ).toBe(true);
    expect(
      classifyTerminalOutputInvalid('{"kind":"tool","toolName":"shell","input":{}}', ["shell"]),
    ).toEqual({
      invalidKind: "textual-tool-call",
      reasonCode: "runtime-decision-json",
      toolKnown: true,
      toolName: "shell",
    });
  });

  it("recognizes standalone runtime decision JSON arrays", () => {
    expect(
      containsTerminalControlPayload('[{"kind":"tool","toolName":"shell","input":{}}]', ["shell"]),
    ).toBe(true);
    expect(
      classifyTerminalOutputInvalid('[{"kind":"tool","toolName":"shell","input":{}}]', ["shell"]),
    ).toEqual({
      invalidKind: "textual-tool-call",
      reasonCode: "runtime-decision-json",
      toolKnown: true,
      toolName: "shell",
    });
    expect(classifyTerminalOutputInvalid('[{"kind":"plan_update","todos":[]}]', ["shell"])).toEqual(
      {
        invalidKind: "phase-or-plan-payload",
        reasonCode: "runtime-decision-json",
      },
    );
  });

  it("recognizes provider-native tool call JSON", () => {
    expect(
      containsTerminalControlPayload(
        '{"tool_calls":[{"name":"shell","arguments":{"command":"echo hidden"}}]}',
        ["shell"],
      ),
    ).toBe(true);
    expect(
      containsTerminalControlPayload(
        '{"toolCalls":[{"toolName":"shell","input":{"command":"echo hidden"}}]}',
        ["shell"],
      ),
    ).toBe(true);
    expect(
      containsTerminalControlPayload(
        '{"function_call":{"name":"shell","arguments":{"command":"echo hidden"}}}',
        ["shell"],
      ),
    ).toBe(true);
    expect(
      containsTerminalControlPayload(
        '{"functionCall":{"name":"shell","arguments":{"command":"echo hidden"}}}',
        ["shell"],
      ),
    ).toBe(true);
    expect(
      containsTerminalControlPayload(
        '{"functionCall":{"name":"shell","input":{"command":"echo hidden"}}}',
        ["shell"],
      ),
    ).toBe(true);
    expect(
      classifyTerminalOutputInvalid(
        '{"tool_calls":[{"name":"shell","arguments":{"command":"echo hidden"}}]}',
        ["shell"],
      ),
    ).toEqual({
      invalidKind: "textual-tool-call",
      reasonCode: "provider-native-tool-call-json",
    });
    expect(
      classifyTerminalOutputInvalid(
        '{"function_call":{"name":"shell","arguments":{"command":"echo hidden"}}}',
        ["shell"],
      ),
    ).toEqual({
      invalidKind: "textual-tool-call",
      reasonCode: "provider-native-tool-call-json",
    });
  });

  it("does not treat loose provider-native-shaped business JSON as control payload", () => {
    expect(containsTerminalControlPayload('{"tool_calls":[]}', ["shell"])).toBe(false);
    expect(containsTerminalControlPayload('{"toolCalls":[{"label":"Hammer"}]}', ["shell"])).toBe(
      false,
    );
    expect(containsTerminalControlPayload('{"functionCall":false}', ["shell"])).toBe(false);
    expect(containsTerminalControlPayload('{"function_call":"not a tool call"}', ["shell"])).toBe(
      false,
    );
    expect(
      containsTerminalControlPayload('{"tool_calls":[{"name":"not a tool"}]}', ["shell"]),
    ).toBe(false);
  });

  it("keeps business JSON arrays with kind fields valid", () => {
    expect(containsTerminalControlPayload('[{"kind":"tool","label":"Hammer"}]', ["shell"])).toBe(
      false,
    );
  });

  it("classifies every runtime decision JSON kind with the runtime-decision-json reason", () => {
    expect(classifyTerminalOutputInvalid('{"kind":"respond","text":"ok"}', ["shell"])).toEqual({
      invalidKind: "generic-control-payload",
      reasonCode: "runtime-decision-json",
    });
    expect(classifyTerminalOutputInvalid('{"kind":"plan_update","todos":[]}', ["shell"])).toEqual({
      invalidKind: "phase-or-plan-payload",
      reasonCode: "runtime-decision-json",
    });
    expect(
      classifyTerminalOutputInvalid(
        '{"kind":"phase_result","phaseName":"change","result":"done"}',
        ["shell"],
      ),
    ).toEqual({
      invalidKind: "phase-or-plan-payload",
      reasonCode: "runtime-decision-json",
    });
    expect(
      classifyTerminalOutputInvalid('Prose first.\n{"kind":"respond","text":"ok"}', ["shell"]),
    ).toEqual({
      invalidKind: "generic-control-payload",
      reasonCode: "runtime-decision-json",
    });
  });

  it("recognizes runtime decision JSON on a standalone line after prose", () => {
    expect(
      containsTerminalControlPayload(
        'I will call:\n{"kind":"tool","toolName":"shell","input":{}}',
        ["shell"],
      ),
    ).toBe(true);
    expect(
      classifyTerminalOutputInvalid('I will call:\n{"kind":"tool","toolName":"shell","input":{}}', [
        "shell",
      ]),
    ).toEqual({
      invalidKind: "textual-tool-call",
      reasonCode: "runtime-decision-json",
      toolKnown: true,
      toolName: "shell",
    });
  });

  it("recognizes very large runtime decision JSON on a standalone line after prose", () => {
    const text = `I will call:\n{"kind":"tool","toolName":"shell","input":{"veryLarge":"${"x".repeat(9_000)}"}}`;

    expect(containsTerminalControlPayload(text, ["shell"])).toBe(true);
    expect(classifyTerminalOutputInvalid(text, ["shell"])).toEqual({
      invalidKind: "textual-tool-call",
      reasonCode: "runtime-decision-json",
      toolKnown: true,
      toolName: "shell",
    });
  });

  it("recognizes very large provider-native JSON on a standalone line after prose", () => {
    const text = `I will call:\n{"tool_calls":[{"name":"shell","arguments":{"command":"echo hidden","large":"${"x".repeat(9_000)}"}}]}`;

    expect(containsTerminalControlPayload(text, ["shell"])).toBe(true);
    expect(classifyTerminalOutputInvalid(text, ["shell"])).toEqual({
      invalidKind: "textual-tool-call",
      reasonCode: "provider-native-tool-call-json",
    });
  });

  it("does not parse very large standalone business JSON after prose as control payload", () => {
    const text = `Business object:\n{"label":"Hammer","description":"${"x".repeat(9_000)}"}`;

    expect(containsTerminalControlPayload(text, ["shell"])).toBe(false);
  });

  it("keeps fenced very large runtime-like JSON valid for assistant terminal output", () => {
    const text = `Example:\n\`\`\`json\n{"kind":"tool","toolName":"shell","input":{"veryLarge":"${"x".repeat(9_000)}"}}\n\`\`\``;

    expect(containsTerminalControlPayload(text, ["shell"])).toBe(false);
  });

  it("does not treat incomplete runtime decision JSON prefixes as final control payloads", () => {
    const prefix = '{"kind":"tool","toolName":"shell","input":';

    expect(containsTerminalControlPayload(prefix, ["shell"])).toBe(false);
    expect(containsTerminalControlPayloadPrefix(prefix, ["shell"])).toBe(true);
  });

  it("does not treat ordinary business JSON with a kind field as control payload", () => {
    expect(containsTerminalControlPayload('{"kind":"tool","label":"Hammer","price":10}', [])).toBe(
      false,
    );
    expect(containsTerminalControlPayloadPrefix('{"kind":"tool","label":', [])).toBe(false);
    expect(
      classifyTerminalOutputInvalid('{"kind":"tool","label":"Hammer","price":10}', []),
    ).toEqual({
      invalidKind: "generic-control-payload",
      reasonCode: "structured-control-payload",
    });
  });

  it("keeps fenced runtime JSON valid for assistant terminal output", () => {
    expect(
      containsTerminalControlPayload(
        '```json\n[{"kind":"tool","toolName":"shell","input":{}}]\n```',
        ["shell"],
      ),
    ).toBe(false);
  });

  it("does not treat standalone business JSON with a kind field as control payload", () => {
    expect(containsTerminalControlPayload('{"kind":"tool","label":"Hammer","price":10}', [])).toBe(
      false,
    );
  });

  it("does not treat inline prose JSON examples as control payload", () => {
    expect(
      containsTerminalControlPayload(
        'An example object is {"kind":"tool","toolName":"shell","input":{}}.',
        ["shell"],
      ),
    ).toBe(false);
    expect(
      containsTerminalControlPayload('An example object is {"kind":"tool","label":"Hammer"}.', [
        "shell",
      ]),
    ).toBe(false);
  });

  it("classifies provider internal thinking markup as internal payload", () => {
    expect(containsTerminalControlPayload("<think>hidden reasoning</think>", ["shell"])).toBe(true);
    expect(classifyTerminalOutputInvalid("<think>hidden reasoning</think>", ["shell"])).toEqual({
      invalidKind: "provider-internal-payload",
      reasonCode: "provider-internal-payload",
    });
  });

  it("recognizes streaming tool markup prefixes", () => {
    expect(containsTerminalControlPayload("<tool", ["shell"])).toBe(false);
    expect(containsTerminalControlPayloadPrefix("<tool", ["shell"])).toBe(true);
    expect(containsTerminalControlPayloadPrefix("tool_calls_section_begin", ["shell"])).toBe(true);
  });
});
