import { BestAgentError } from "@best-agent/foundation";
import type { CapabilityRequest, ToolInputValue } from "@best-agent/protocol";
import { z } from "zod";
import { isPlainObject, readStringField } from "./builtins/shared.js";
import { type ToolDefinition, type ToolExecutionContext } from "./registry.js";
import { toolInputSchema } from "./schema.js";

export interface CommandToolConfig {
  args?: string[];
  command: string;
  description: string;
  extensionName: string;
  toolName: string;
}

type CommandToolInput = {
  input?: string;
  timeout_ms?: number;
};
const COMMAND_TOOL_INPUT_SCHEMA = toolInputSchema(
  z.strictObject({
    input: z.string().optional(),
    timeout_ms: z.int().positive().optional(),
  }),
);

export function createCommandToolDefinition(
  config: CommandToolConfig,
): ToolDefinition<CommandToolInput> {
  const toolName = qualifyExtensionToolName(config.extensionName, config.toolName);
  return {
    description: config.description,
    effect: "execute",
    executionClass: "external-side-effect",
    idempotent: false,
    inputSchema: COMMAND_TOOL_INPUT_SCHEMA,
    legacyAdapter: {
      adapt(input, _context) {
        return normalizeCommandToolInput(toolName, input);
      },
    },
    name: toolName,
    historyInput(input) {
      return sanitizeCommandToolInputForHistory(input, "tool history");
    },
    isContentBearingInput(input) {
      return typeof input.input === "string" && input.input.length > 0;
    },
    providerHistoryInput(input) {
      return sanitizeCommandToolInputForHistory(input, "provider follow-up");
    },
    source: "extension",
    sourceExtension: config.extensionName,
    capabilities(input: CommandToolInput, context: ToolExecutionContext): CapabilityRequest[] {
      return [
        {
          group: "shell",
          argv: buildArgv(config, input),
          cwd: context.cwd,
        },
      ];
    },
    async execute(input: CommandToolInput, context: ToolExecutionContext): Promise<string> {
      return context.sandbox.runCommand(
        buildArgv(config, input),
        context.cwd,
        input.timeout_ms === undefined ? {} : { timeoutMs: input.timeout_ms },
      );
    },
  };
}

export function qualifyExtensionToolName(extensionName: string, toolName: string): string {
  return `ext:${extensionName}:${toolName}`;
}

function buildArgv(config: CommandToolConfig, input: CommandToolInput): string[] {
  return [
    config.command,
    ...(config.args ?? []),
    ...(input.input && input.input.length > 0 ? [input.input] : []),
  ];
}

function normalizeCommandToolInput(toolName: string, input: ToolInputValue): CommandToolInput {
  if (typeof input === "string") {
    return { input };
  }
  if (!isPlainObject(input)) {
    throw new BestAgentError(
      "TOOL_INPUT_INVALID",
      `${toolName} requires a string input or an object with input and optional timeout_ms.`,
    );
  }

  const normalizedInput =
    readStringField(input, ["input", "text", "content", "query", "command"]) ?? "";
  const timeoutMs = normalizeCommandToolTimeoutMs(toolName, input);
  return timeoutMs === undefined
    ? { input: normalizedInput }
    : { input: normalizedInput, timeout_ms: timeoutMs };
}

function sanitizeCommandToolInputForHistory(
  input: CommandToolInput,
  target: "provider follow-up" | "tool history",
): ToolInputValue {
  return {
    ...(typeof input.input === "string"
      ? { input: `[content omitted from ${target}: ${input.input.length} chars]` }
      : {}),
    ...(typeof input.timeout_ms === "number" ? { timeout_ms: input.timeout_ms } : {}),
  };
}

function normalizeCommandToolTimeoutMs(
  toolName: string,
  input: Record<string, ToolInputValue>,
): number | undefined {
  const value = input.timeout_ms;
  if (value === undefined) {
    return undefined;
  }
  if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
    throw new BestAgentError(
      "TOOL_INPUT_INVALID",
      `${toolName} timeout_ms must be a positive integer.`,
    );
  }
  return value;
}
