/**
 * Read-only tool parallel batching for GoogleGenAiAdapter.
 *
 * Mutating FCoP / workspace tools stay strictly serial and in model FC order.
 * Consecutive read-only calls between serial barriers may run via Promise.all.
 */

import type { GeminiToolResult } from "./geminiChatHistory.ts";

/** Explicit allowlist — conservative; unknown tools default to serial. */
export const GEMINI_PARALLEL_READONLY_TOOLS = new Set<string>([
  "read_file",
  "grep_files",
  "list_dir",
  "read_task",
  "inspect_task",
  "read_report",
  "read_issue",
  "read_review",
  "list_history",
  "read_history_task",
  "fcop_report",
  "fcop_check",
  "fcop_audit",
  "fcop_list_alerts",
  "get_team_status",
  "list_tasks",
  "list_reports",
  "list_issues",
  "list_reviews",
]);

/** Always serial regardless of prefix heuristics. */
export const GEMINI_FORCE_SERIAL_TOOLS = new Set<string>([
  "write_file",
  "write_report",
  "write_task",
  "write_issue",
  "write_review",
  "create_task",
  "claim_task",
  "submit_task",
  "finish_task",
  "approve_task",
  "reject_task",
  "archive_task",
  "archive_to_history",
  "migrate_to_v3",
  "deploy_role_templates",
  "redeploy_rules",
  "init_solo",
  "init_project",
  "create_custom_team",
  "mark_human_approved",
  "fcop_create_alert",
  "drop_suggestion",
  "new_workspace",
  "report_failure",
]);

export interface GeminiFunctionCallLike {
  name?: string;
  id?: string;
  args?: unknown;
}

export type PreparedToolSlot =
  | { kind: "immediate"; result: GeminiToolResult }
  | {
      kind: "execute";
      call: GeminiFunctionCallLike;
      readonlyParallel: boolean;
    };

export function isGeminiParallelReadonlyBatchEnabled(
  envValue: string | undefined = process.env.CODEFLOW_GEMINI_PARALLEL_READONLY,
): boolean {
  if (envValue === undefined || envValue === "") return true;
  const normalized = envValue.trim().toLowerCase();
  return normalized !== "0" && normalized !== "false" && normalized !== "off";
}

export function isGeminiForceSerialTool(toolName: string): boolean {
  const name = toolName.trim();
  if (!name) return true;
  if (GEMINI_FORCE_SERIAL_TOOLS.has(name)) return true;
  if (name.startsWith("write_")) return true;
  if (name.startsWith("create_")) return true;
  if (name.startsWith("init_")) return true;
  if (name.startsWith("migrate_")) return true;
  if (name.startsWith("deploy_")) return true;
  if (name.startsWith("archive_")) return true;
  return false;
}

export function isGeminiParallelReadOnlyTool(toolName: string): boolean {
  const name = toolName.trim();
  if (!name || isGeminiForceSerialTool(name)) return false;
  if (GEMINI_PARALLEL_READONLY_TOOLS.has(name)) return true;
  if (
    name.startsWith("read_") ||
    name.startsWith("list_") ||
    name.startsWith("get_")
  ) {
    return true;
  }
  if (name.startsWith("fcop_") && (name.includes("list") || name.includes("report") || name.includes("check") || name.includes("audit"))) {
    return true;
  }
  return false;
}

export function readonlyParallelForCall(
  call: GeminiFunctionCallLike,
  enabled: boolean = isGeminiParallelReadonlyBatchEnabled(),
): boolean {
  if (!enabled) return false;
  return isGeminiParallelReadOnlyTool(String(call.name ?? ""));
}

/**
 * Walk prepared slots in order; batch consecutive readonly executes with Promise.all.
 */
export async function runPreparedToolSlots(
  slots: PreparedToolSlot[],
  execute: (call: GeminiFunctionCallLike) => Promise<GeminiToolResult>,
  options?: {
    parallelReadonly?: boolean;
    onParallelBatch?: (toolNames: string[], size: number) => void;
  },
): Promise<GeminiToolResult[]> {
  const parallelEnabled =
    options?.parallelReadonly ?? isGeminiParallelReadonlyBatchEnabled();
  const results: GeminiToolResult[] = [];
  let index = 0;

  while (index < slots.length) {
    const slot = slots[index]!;
    if (slot.kind === "immediate") {
      results.push(slot.result);
      index++;
      continue;
    }

    const batch: GeminiFunctionCallLike[] = [];
    while (index < slots.length) {
      const current = slots[index]!;
      if (current.kind !== "execute") break;
      if (!parallelEnabled || !current.readonlyParallel) break;
      batch.push(current.call);
      index++;
    }

    if (batch.length > 0) {
      options?.onParallelBatch?.(
        batch.map((c) => String(c.name ?? "unknown_tool")),
        batch.length,
      );
      const batchResults = await Promise.all(batch.map((call) => execute(call)));
      results.push(...batchResults);
      continue;
    }

    const serial = slots[index]!;
    if (serial.kind === "execute") {
      results.push(await execute(serial.call));
      index++;
    }
  }

  return results;
}
