import type { JSONSchema7 } from "json-schema";
import type { DeepPartial, TypeAtPath, TypePath } from "./type-path-utils";
import type { AsyncIterableStream } from "../../utils";
import type { StandardSchemaV1 } from "@standard-schema/spec";
import type { ToolResponse } from "./ToolResponse";

/**
 * Interface for reading tool call arguments from a stream, which are
 * generated by a language learning model (LLM). Provides methods to
 * retrieve specific values, partial streams, or complete items from
 * an array, based on a specified path.
 *
 * @template TArgs The type of arguments being read.
 */
export interface ToolCallArgsReader<TArgs extends Record<string, unknown>> {
  /**
   * Returns a promise that will resolve to the value at the given path,
   * as soon as that path is generated by the LLM.
   *
   * @param fieldPath An array of object keys or array indices.
   */
  get<PathT extends TypePath<TArgs>>(
    ...fieldPath: PathT
  ): Promise<TypeAtPath<TArgs, PathT>>;

  /**
   * Returns a stream that will emit partial values at the given path,
   * as they are generated by the LLM.
   *
   * @param fieldPath An array of object keys or array indices.
   */
  streamValues<PathT extends TypePath<TArgs>>(
    ...fieldPath: PathT
  ): AsyncIterableStream<DeepPartial<TypeAtPath<TArgs, PathT>>>;

  /**
   * Returns a stream that will emit partial text at the given path,
   * as they are generated by the LLM.
   *
   * @param fieldPath An array of object keys or array indices.
   */
  streamText<PathT extends TypePath<TArgs>>(
    ...fieldPath: PathT
  ): TypeAtPath<TArgs, PathT> extends string & infer U
    ? AsyncIterableStream<U>
    : never;

  /**
   * Returns a stream that will emit complete items in the array
   * at the given path, as they are generated by the LLM.
   *
   * @param fieldPath An array of object keys or array indices.
   */
  forEach<PathT extends TypePath<TArgs>>(
    ...fieldPath: PathT
  ): TypeAtPath<TArgs, PathT> extends Array<infer U>
    ? AsyncIterableStream<U>
    : never;
}

export interface ToolCallResponseReader<TResult> {
  get: () => Promise<ToolResponse<TResult>>;
}

export interface ToolCallReader<
  TArgs extends Record<string, unknown> = Record<string, unknown>,
  TResult = unknown,
> {
  args: ToolCallArgsReader<TArgs>;
  response: ToolCallResponseReader<TResult>;

  /**
   * @deprecated Deprecated. Use `response.get().result` instead.
   */
  result: {
    get: () => Promise<TResult>;
  };
}

export type ToolExecutionContext = {
  toolCallId: string;
  abortSignal: AbortSignal;
  human: (payload: unknown) => Promise<unknown>;
};

export type ToolExecuteFunction<TArgs, TResult> = (
  args: TArgs,
  context: ToolExecutionContext,
) => TResult | Promise<TResult>;

export type ToolStreamCallFunction<
  TArgs extends Record<string, unknown> = Record<string, unknown>,
  TResult = unknown,
> = (
  reader: ToolCallReader<TArgs, TResult>,
  context: ToolExecutionContext,
) => void;

type OnSchemaValidationErrorFunction<TResult> = ToolExecuteFunction<
  unknown,
  TResult
>;

type ToolBase<
  TArgs extends Record<string, unknown> = Record<string, unknown>,
  TResult = unknown,
> = {
  /**
   * @deprecated Experimental, API may change.
   */
  streamCall?: ToolStreamCallFunction<TArgs, TResult>;
};

type BackendTool<
  TArgs extends Record<string, unknown> = Record<string, unknown>,
  TResult = unknown,
> = ToolBase<TArgs, TResult> & {
  type: "backend";

  description?: undefined;
  parameters?: undefined;
  disabled?: undefined;
  execute?: undefined;
  experimental_onSchemaValidationError?: undefined;
};

type FrontendTool<
  TArgs extends Record<string, unknown> = Record<string, unknown>,
  TResult = unknown,
> = ToolBase<TArgs, TResult> & {
  type: "frontend";

  description?: string | undefined;
  parameters: StandardSchemaV1<TArgs> | JSONSchema7;
  disabled?: boolean;
  execute: ToolExecuteFunction<TArgs, TResult>;
  experimental_onSchemaValidationError?: OnSchemaValidationErrorFunction<TResult>;
};

type HumanTool<
  TArgs extends Record<string, unknown> = Record<string, unknown>,
  TResult = unknown,
> = ToolBase<TArgs, TResult> & {
  type: "human";

  description?: string | undefined;
  parameters: StandardSchemaV1<TArgs> | JSONSchema7;
  disabled?: boolean;
  execute?: undefined;
  experimental_onSchemaValidationError?: undefined;
};

export type Tool<
  TArgs extends Record<string, unknown> = Record<string, unknown>,
  TResult = unknown,
> =
  | FrontendTool<TArgs, TResult>
  | BackendTool<TArgs, TResult>
  | HumanTool<TArgs, TResult>
  | ToolWithoutType<TArgs, TResult>;

/**
 * @deprecated Use `Tool` with an explicit `type` field instead.
 */
export type ToolWithoutType<
  TArgs extends Record<string, unknown> = Record<string, unknown>,
  TResult = unknown,
> = (
  | Omit<FrontendTool<TArgs, TResult>, "type">
  | Omit<BackendTool<TArgs, TResult>, "type">
  | Omit<HumanTool<TArgs, TResult>, "type">
) & { type?: undefined };
