import type { ItemId, ToolInputValue, Turn } from "@best-agent/protocol";
import type { AgentProfile } from "../../agent/profile-registry.js";
import type { PreparedToolInvocation } from "../../tool/registry.js";
import type { ToolResumeContext } from "../checkpoint-codec.js";
import type {
  PersistTerminalToolFailure,
  ToolExecutionRuntime,
  ToolShellDebugBase,
} from "./tool-execution-types.js";

export async function runPreToolUseHooks(input: {
  persistTerminalToolFailure: PersistTerminalToolFailure;
  prepared: PreparedToolInvocation;
  preparedToolResumeContext: ToolResumeContext;
  profile: AgentProfile;
  runtime: ToolExecutionRuntime;
  shellDebugBase?: ToolShellDebugBase;
  toolCallId: ItemId;
  toolName: string;
  turn: Turn;
}): Promise<{ blockedOutput?: string; failureOutput?: string }> {
  const {
    persistTerminalToolFailure,
    prepared,
    preparedToolResumeContext,
    profile,
    runtime,
    shellDebugBase,
    toolCallId,
    toolName,
    turn,
  } = input;
  try {
    if (shellDebugBase) {
      runtime.tracing.logShellToolDebug(runtime.cwd, "shell.pre_hooks.start", {
        ...shellDebugBase,
        callId: toolCallId,
      });
    }
    const blockedOutput = await runtime.hooks.applyPreToolUseHooks({
      policyDecision: "allow",
      profileName: profile.name,
      threadId: turn.threadId,
      toolCallId,
      toolInput: prepared.normalizedInput,
      toolName,
    });
    if (shellDebugBase) {
      runtime.tracing.logShellToolDebug(runtime.cwd, "shell.pre_hooks.done", {
        ...shellDebugBase,
        blocked: Boolean(blockedOutput),
        callId: toolCallId,
      });
    }
    return { blockedOutput };
  } catch (error) {
    if (shellDebugBase) {
      runtime.tracing.logShellToolDebug(runtime.cwd, "shell.pre_hooks.error", {
        ...shellDebugBase,
        callId: toolCallId,
        error: runtime.tracing.describeErrorForDebugLog(error),
      });
    }
    const failureOutput = await persistTerminalToolFailure(
      toolCallId,
      prepared.normalizedInput,
      {
        ...preparedToolResumeContext,
        toolCallId,
      },
      error,
    );
    if (failureOutput !== undefined) {
      return { failureOutput };
    }
    throw error;
  }
}

export async function runPostToolUseHooks(input: {
  output: string;
  persistTerminalToolFailure: PersistTerminalToolFailure;
  preparedToolResumeContext: ToolResumeContext;
  profile: AgentProfile;
  runtime: ToolExecutionRuntime;
  shellDebugBase?: ToolShellDebugBase;
  toolCallId: ItemId;
  toolInput: ToolInputValue;
  toolName: string;
  toolStartedAt: number;
  turn: Turn;
}): Promise<{ failureOutput?: string; output: string }> {
  const {
    persistTerminalToolFailure,
    preparedToolResumeContext,
    profile,
    runtime,
    shellDebugBase,
    toolCallId,
    toolInput,
    toolName,
    toolStartedAt,
    turn,
  } = input;
  let output = input.output;
  try {
    if (shellDebugBase) {
      runtime.tracing.logShellToolDebug(runtime.cwd, "shell.post_hooks.start", {
        ...shellDebugBase,
        callId: toolCallId,
      });
    }
    const postHookResult = await runtime.hooks.applyPostToolUseHooks({
      elapsedMs: Math.max(0, Date.now() - toolStartedAt),
      profileName: profile.name,
      result: output,
      threadId: turn.threadId,
      toolCallId,
      toolInput,
      toolName,
    });
    output = postHookResult.output;
    if (shellDebugBase) {
      runtime.tracing.logShellToolDebug(runtime.cwd, "shell.post_hooks.done", {
        ...shellDebugBase,
        callId: toolCallId,
        outputBytes: Buffer.byteLength(output, "utf8"),
      });
    }
    if (await runtime.interruptions.isInterrupted(turn)) {
      await persistTerminalToolFailure(
        toolCallId,
        toolInput,
        {
          ...preparedToolResumeContext,
          toolCallId,
        },
        new Error(`Tool ${toolName} was interrupted before the runtime could persist its result.`),
        `Tool ${toolName} was interrupted before the runtime could persist its result.`,
      );
      throw runtime.interruptions.createTurnTerminatedError({
        turn: await runtime.turnState.requireTurn(turn.id),
      });
    }
    return { output };
  } catch (error) {
    if (shellDebugBase) {
      runtime.tracing.logShellToolDebug(runtime.cwd, "shell.post_hooks.error", {
        ...shellDebugBase,
        callId: toolCallId,
        error: runtime.tracing.describeErrorForDebugLog(error),
      });
    }
    const failureOutput = await persistTerminalToolFailure(
      toolCallId,
      toolInput,
      {
        ...preparedToolResumeContext,
        toolCallId,
      },
      error,
    );
    if (failureOutput !== undefined) {
      return { failureOutput, output };
    }
    throw error;
  }
}
