import assert from "node:assert/strict";
import { describe, it } from "node:test";
import {
  GEMINI_FORCE_SERIAL_TOOLS,
  GEMINI_PARALLEL_READONLY_TOOLS,
  isGeminiForceSerialTool,
  isGeminiParallelReadOnlyTool,
  isGeminiParallelReadonlyBatchEnabled,
  readonlyParallelForCall,
  runPreparedToolSlots,
  type PreparedToolSlot,
} from "../geminiToolParallel.ts";

describe("geminiToolParallel", () => {
  it("classifies patrol and workspace reads as parallel-safe", () => {
    for (const name of [
      "read_file",
      "grep_files",
      "list_dir",
      "fcop_report",
      "list_tasks",
      "read_task",
    ]) {
      assert.equal(isGeminiParallelReadOnlyTool(name), true);
      assert.equal(isGeminiForceSerialTool(name), false);
    }
  });

  it("forces mutating FCoP tools serial", () => {
    for (const name of [
      "write_report",
      "write_task",
      "claim_task",
      "submit_task",
      "write_file",
    ]) {
      assert.equal(isGeminiForceSerialTool(name), true);
      assert.equal(isGeminiParallelReadOnlyTool(name), false);
      assert.equal(GEMINI_FORCE_SERIAL_TOOLS.has(name), true);
    }
  });

  it("defaults unknown tools to serial (not parallel)", () => {
    assert.equal(isGeminiParallelReadOnlyTool("custom_mutator"), false);
    assert.equal(isGeminiParallelReadOnlyTool(""), false);
  });

  it("respects CODEFLOW_GEMINI_PARALLEL_READONLY=0", () => {
    assert.equal(isGeminiParallelReadonlyBatchEnabled("0"), false);
    assert.equal(isGeminiParallelReadonlyBatchEnabled("false"), false);
    assert.equal(isGeminiParallelReadonlyBatchEnabled("1"), true);
    assert.equal(isGeminiParallelReadonlyBatchEnabled(undefined), true);
  });

  it("readonlyParallelForCall honors enable flag", () => {
    const call = { name: "read_file", id: "1", args: { path: "a" } };
    assert.equal(readonlyParallelForCall(call, true), true);
    assert.equal(readonlyParallelForCall(call, false), false);
  });

  it("runPreparedToolSlots batches consecutive readonly executes", async () => {
    const order: string[] = [];
    const slots: PreparedToolSlot[] = [
      { kind: "execute", call: { name: "read_file", id: "a" }, readonlyParallel: true },
      { kind: "execute", call: { name: "grep_files", id: "b" }, readonlyParallel: true },
      { kind: "execute", call: { name: "write_report", id: "c" }, readonlyParallel: false },
      { kind: "execute", call: { name: "list_tasks", id: "d" }, readonlyParallel: true },
    ];

    const results = await runPreparedToolSlots(
      slots,
      async (call) => {
        order.push(String(call.name));
        await new Promise((r) => setTimeout(r, 5));
        return {
          name: String(call.name),
          id: call.id,
          response: { output: "ok" },
        };
      },
      { parallelReadonly: true },
    );

    assert.equal(results.length, 4);
    assert.ok(order.indexOf("read_file") < order.indexOf("write_report"));
    assert.ok(order.indexOf("grep_files") < order.indexOf("write_report"));
    assert.ok(order.indexOf("write_report") < order.indexOf("list_tasks"));
    assert.equal(GEMINI_PARALLEL_READONLY_TOOLS.has("read_file"), true);
  });

  it("preserves slot order in results including immediate slots", async () => {
    const slots: PreparedToolSlot[] = [
      {
        kind: "immediate",
        result: { name: "read_file", id: "r1", response: { output: "cached" } },
      },
      { kind: "execute", call: { name: "list_tasks", id: "e1" }, readonlyParallel: true },
    ];
    const results = await runPreparedToolSlots(slots, async (call) => ({
      name: String(call.name),
      id: call.id,
      response: { output: "live" },
    }));
    assert.deepEqual(
      results.map((r) => r.name),
      ["read_file", "list_tasks"],
    );
    assert.deepEqual(results[0]?.response, { output: "cached" });
  });
});
