import { BestAgentError } from "@best-agent/foundation";
import type { ItemId, SandboxProfile, Turn } from "@best-agent/protocol";
import type { AgentProfile } from "../../agent/profile-registry.js";
import type { PreparedToolInvocation, RuntimeToolDefinition } from "../../tool/registry.js";
import type { ExecuteToolInvocationOptions } from "../agent-runtime-tool-execution-types.js";
import type { ToolResumeContext } from "../checkpoint-codec.js";
import { authorizePreparedToolExecution } from "./tool-capability-evaluation.js";
import { runPreToolUseHooks } from "./tool-hook-runner.js";
import { persistToolOutput } from "./tool-result-persistence.js";
import type {
  PersistTerminalToolFailure,
  ToolExecutionRuntime,
  ToolShellDebugBase,
} from "./tool-execution-types.js";

export async function authorizeToolExecutionFlow(input: {
  options: ExecuteToolInvocationOptions;
  persistTerminalToolFailure: PersistTerminalToolFailure;
  prepared: PreparedToolInvocation;
  preparedToolResumeContext: ToolResumeContext;
  previewCallId?: ItemId;
  profile: AgentProfile;
  runtime: ToolExecutionRuntime;
  shellDebugBase?: ToolShellDebugBase;
  threadMode: string;
  toolName: string;
  toolResumeContext: ToolResumeContext;
  turn: Turn;
  authorizationTurn: Turn;
}): Promise<
  | {
      kind: "authorized";
      executionSandboxProfile?: SandboxProfile;
      toolCallId: ItemId;
      toolDefinition: RuntimeToolDefinition;
      webSearchBudget?: { currentCount: number; limit: number; scope: "workflow" };
    }
  | { kind: "blocked"; output: string }
  | { kind: "terminal-failure"; output: string }
> {
  const {
    options,
    persistTerminalToolFailure,
    prepared,
    preparedToolResumeContext,
    previewCallId,
    profile,
    runtime,
    shellDebugBase,
    toolName,
    toolResumeContext,
    turn,
  } = input;
  if (input.threadMode === "plan") {
    await runtime.capability.assertPlanModeToolAllowed(toolName, prepared, turn.threadId);
    if (!profile.allowedTools.includes(toolName)) {
      throw new BestAgentError(
        "TOOL_NOT_ALLOWED",
        `Tool ${toolName} is not allowed by the ${profile.name} profile.`,
      );
    }
  }

  const toolCallItem = runtime.toolCalls.createToolCallItem(
    turn,
    toolName,
    prepared.displayInput,
    options.skipPrelude ? toolResumeContext.toolCallId : previewCallId,
    prepared.normalizedInput,
    toolResumeContext.providerToolName,
  );
  if (!options.skipPrelude) {
    await runtime.items.pushItems([toolCallItem]);
  }
  if (shellDebugBase) {
    runtime.tracing.logShellToolDebug(runtime.cwd, "shell.tool_call.ready", {
      ...shellDebugBase,
      callId: toolCallItem.callId,
      skipPrelude: options.skipPrelude,
    });
  }
  const webSearchBudget = runtime.webSearchBudget.reserveWebSearchBudget(turn, toolName);
  const toolDefinition = runtime.preparedTools.tools.getDefinition(toolName);
  const capabilityEvaluation = await authorizePreparedToolExecution({
    authorizationTurn: input.authorizationTurn,
    options,
    persistTerminalToolFailure,
    prepared,
    preparedToolResumeContext,
    profile,
    runtime,
    shellDebugBase,
    toolCallId: toolCallItem.callId,
    toolDefinition,
    toolName,
    turn,
  });
  if (capabilityEvaluation.failureOutput !== undefined) {
    return { kind: "terminal-failure", output: capabilityEvaluation.failureOutput };
  }

  const preHook = await runPreToolUseHooks({
    persistTerminalToolFailure,
    prepared,
    preparedToolResumeContext,
    profile,
    runtime,
    shellDebugBase,
    toolCallId: toolCallItem.callId,
    toolName,
    turn,
  });
  if (preHook.failureOutput !== undefined) {
    return { kind: "terminal-failure", output: preHook.failureOutput };
  }
  if (preHook.blockedOutput) {
    await persistToolOutput({
      output: preHook.blockedOutput,
      resumeContext: {
        ...preparedToolResumeContext,
        toolCallId: toolCallItem.callId,
      },
      runtime,
      toolCallId: toolCallItem.callId,
      toolInput: prepared.normalizedInput,
      toolName,
      turn,
    });
    return { kind: "blocked", output: preHook.blockedOutput };
  }

  return {
    kind: "authorized",
    executionSandboxProfile: capabilityEvaluation.executionSandboxProfile,
    toolCallId: toolCallItem.callId,
    toolDefinition,
    webSearchBudget,
  };
}
