import { BestAgentError } from "@best-agent/foundation";
import type { ToolInputJsonSchema } from "./contracts.js";
import type { ZodToolInputSchema } from "./schema-types.js";
import { z } from "zod";

const TOOL_INPUT_SCHEMA_KIND = "best-agent.tool-input-schema";
export type { ZodToolInputSchema } from "./schema-types.js";

export function toolInputSchema<const S extends z.ZodType>(
  schema: S,
  options: { jsonSchema?: Record<string, unknown> } = {},
): ZodToolInputSchema<z.output<S>> {
  const jsonSchema = options.jsonSchema ?? (z.toJSONSchema(schema) as Record<string, unknown>);
  delete jsonSchema.$schema;
  return {
    jsonSchema,
    kind: TOOL_INPUT_SCHEMA_KIND,
    zodSchema: schema as z.ZodType<z.output<S>>,
  };
}

/** Create a typed tool input schema whose output type binds directly to TInput.
 *  Use this when the Zod schema output IS the same type as TInput on the ToolDefinition. */
export function typedToolInputSchema<TInput, const S extends z.ZodType<TInput> = z.ZodType<TInput>>(
  schema: S,
  options: { jsonSchema?: Record<string, unknown> } = {},
): ZodToolInputSchema<TInput> {
  const jsonSchema = options.jsonSchema ?? (z.toJSONSchema(schema) as Record<string, unknown>);
  delete jsonSchema.$schema;
  return {
    jsonSchema,
    kind: TOOL_INPUT_SCHEMA_KIND,
    zodSchema: schema as z.ZodType<TInput>,
  };
}

export function isZodToolInputSchema(inputSchema: unknown): inputSchema is ZodToolInputSchema {
  return (
    Boolean(inputSchema) &&
    typeof inputSchema === "object" &&
    (inputSchema as { kind?: unknown }).kind === TOOL_INPUT_SCHEMA_KIND
  );
}

export function getToolInputJsonSchema(inputSchema: unknown): unknown {
  return isZodToolInputSchema(inputSchema) ? inputSchema.jsonSchema : inputSchema;
}

export function parseZodToolInput<TInput>(
  toolName: string,
  inputSchema: ZodToolInputSchema<TInput>,
  input: unknown,
): TInput {
  const parsed = inputSchema.zodSchema.safeParse(input);
  if (parsed.success) {
    return parsed.data;
  }
  const issue = parsed.error.issues[0];
  const path = issue?.path.length ? ` input.${issue.path.join(".")}` : " input";
  throw new BestAgentError(
    "TOOL_INPUT_INVALID",
    `${toolName}${path} does not match the expected schema.`,
  );
}

/** Build a plain JSON Schema from a Zod schema, for use as a wire-format
 *  description on legacy tools where the schema shape differs from TInput.
 *  The legacyAdapter is responsible for full validation and normalization;
 *  the returned schema is only used to generate the LLM-facing JSON Schema. */
export function wireInputSchema<const S extends z.ZodType>(
  schema: S,
  options: { jsonSchema?: Record<string, unknown> } = {},
): ToolInputJsonSchema {
  const jsonSchema = options.jsonSchema ?? (z.toJSONSchema(schema) as Record<string, unknown>);
  delete jsonSchema.$schema;
  return jsonSchema as ToolInputJsonSchema;
}
