/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @fileoverview Defines the core configuration interfaces and types for the agent architecture.
 */

import type { Content, FunctionDeclaration } from '@google/genai';
import type { AnyDeclarativeTool } from '../tools/tools.js';
import { type z } from 'zod';
import type { ModelConfig } from '../services/modelConfigService.js';
import type { AnySchema } from 'ajv';
import type { AgentCard } from '@a2a-js/sdk';
import type { A2AAuthConfig } from './auth-provider/types.js';
import type { MCPServerConfig } from '../config/config.js';

/**
 * Describes the possible termination modes for an agent.
 */
export enum AgentTerminateMode {
  ERROR = 'ERROR',
  TIMEOUT = 'TIMEOUT',
  GOAL = 'GOAL',
  MAX_TURNS = 'MAX_TURNS',
  ABORTED = 'ABORTED',
  ERROR_NO_COMPLETE_TASK_CALL = 'ERROR_NO_COMPLETE_TASK_CALL',
}

/**
 * Represents the output structure of an agent's execution.
 */
export interface OutputObject {
  result: string;
  terminate_reason: AgentTerminateMode;
}

/**
 * The default query string provided to an agent as input.
 */
export const DEFAULT_QUERY_STRING = 'Get Started!';

/**
 * The default maximum number of conversational turns for an agent.
 */
export const DEFAULT_MAX_TURNS = 30;

/**
 * The default maximum execution time for an agent in minutes.
 */
export const DEFAULT_MAX_TIME_MINUTES = 10;

/**
 * Represents the validated input parameters passed to an agent upon invocation.
 * Used primarily for templating the system prompt. (Replaces ContextState)
 */
export type AgentInputs = Record<string, unknown>;

/**
 * Simplified input structure for Remote Agents, which consumes a single string query.
 */
export type RemoteAgentInputs = { query: string };

/**
 * Structured events emitted during subagent execution for user observability.
 */
export enum SubagentActivityErrorType {
  REJECTED = 'REJECTED',
  CANCELLED = 'CANCELLED',
  GENERIC = 'GENERIC',
}

/**
 * Standard error messages for subagent activities.
 */
export const SUBAGENT_REJECTED_ERROR_PREFIX = 'User rejected this operation.';
export const SUBAGENT_CANCELLED_ERROR_MESSAGE = 'Request cancelled.';

export interface SubagentActivityEvent {
  isSubagentActivityEvent: true;
  agentName: string;
  type: 'TOOL_CALL_START' | 'TOOL_CALL_END' | 'THOUGHT_CHUNK' | 'ERROR';
  data: Record<string, unknown>;
}

export interface SubagentActivityItem {
  id: string;
  type: 'thought' | 'tool_call';
  content: string;
  displayName?: string;
  description?: string;
  args?: string;
  status: 'running' | 'completed' | 'error' | 'cancelled';
}

export interface SubagentProgress {
  isSubagentProgress: true;
  agentName: string;
  recentActivity: SubagentActivityItem[];
  state?: 'running' | 'completed' | 'error' | 'cancelled';
  result?: string;
  terminateReason?: AgentTerminateMode;
}

export function isSubagentProgress(obj: unknown): obj is SubagentProgress {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    'isSubagentProgress' in obj &&
    obj.isSubagentProgress === true
  );
}

/**
 * Checks if the tool call data indicates an error.
 */
export function isToolActivityError(data: unknown): boolean {
  return (
    data !== null &&
    typeof data === 'object' &&
    'isError' in data &&
    data.isError === true
  );
}

/**
 * The base definition for an agent.
 * @template TOutput The specific Zod schema for the agent's final output object.
 */
export type AgentCardLoadOptions =
  | { type: 'url'; url: string }
  | { type: 'json'; json: string };

/** Minimal shape needed by helper functions, avoids generic TOutput constraints. */
interface RemoteAgentRef {
  name: string;
  agentCardUrl?: string;
  agentCardJson?: string;
}

/**
 * Derives the AgentCardLoadOptions from a RemoteAgentDefinition.
 * Throws if neither agentCardUrl nor agentCardJson is present.
 */
export function getAgentCardLoadOptions(
  def: RemoteAgentRef,
): AgentCardLoadOptions {
  if (def.agentCardJson) {
    return { type: 'json', json: def.agentCardJson };
  }
  if (def.agentCardUrl) {
    return { type: 'url', url: def.agentCardUrl };
  }
  throw new Error(
    `Remote agent '${def.name}' has neither agentCardUrl nor agentCardJson`,
  );
}

/**
 * Extracts a target URL for auth providers from a RemoteAgentDefinition.
 * For URL-based agents, returns the agentCardUrl.
 * For JSON-based agents, attempts to parse the URL from the inline card JSON.
 * Returns undefined if no URL can be determined.
 */
export function getRemoteAgentTargetUrl(
  def: RemoteAgentRef,
): string | undefined {
  if (def.agentCardUrl) {
    return def.agentCardUrl;
  }
  if (def.agentCardJson) {
    try {
      const parsed: unknown = JSON.parse(def.agentCardJson);
      // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
      const card = parsed as AgentCard;
      if (card.url) {
        return card.url;
      }
    } catch {
      // JSON parse will fail properly later in loadAgent
    }
  }
  return undefined;
}

export interface BaseAgentDefinition<
  TOutput extends z.ZodTypeAny = z.ZodUnknown,
> {
  /** Unique identifier for the agent. */
  name: string;
  displayName?: string;
  description: string;
  experimental?: boolean;
  inputConfig: InputConfig;
  outputConfig?: OutputConfig<TOutput>;
  metadata?: {
    hash?: string;
    filePath?: string;
  };
}

export interface LocalAgentDefinition<
  TOutput extends z.ZodTypeAny = z.ZodUnknown,
> extends BaseAgentDefinition<TOutput> {
  kind: 'local';

  // Local agent required configs
  promptConfig: PromptConfig;
  modelConfig: ModelConfig;
  runConfig: RunConfig;

  // Optional configs
  toolConfig?: ToolConfig;

  /**
   * Optional inline MCP servers for this agent.
   */
  mcpServers?: Record<string, MCPServerConfig>;

  /**
   * An optional function to process the raw output from the agent's final tool
   * call into a string format.
   *
   * @param output The raw output value from the `complete_task` tool, now strongly typed with TOutput.
   * @returns A string representation of the final output.
   */
  processOutput?: (output: z.infer<TOutput>) => string;
}

export interface BaseRemoteAgentDefinition<
  TOutput extends z.ZodTypeAny = z.ZodUnknown,
> extends BaseAgentDefinition<TOutput> {
  kind: 'remote';
  /** The user-provided description, before any remote card merging. */
  originalDescription?: string;
  /**
   * Optional authentication configuration for the remote agent.
   * If not specified, the agent will try to use defaults based on the AgentCard's
   * security requirements.
   */
  auth?: A2AAuthConfig;
}

export interface RemoteAgentDefinition<
  TOutput extends z.ZodTypeAny = z.ZodUnknown,
> extends BaseRemoteAgentDefinition<TOutput> {
  agentCardUrl?: string;
  agentCardJson?: string;
}

export type AgentDefinition<TOutput extends z.ZodTypeAny = z.ZodUnknown> =
  | LocalAgentDefinition<TOutput>
  | RemoteAgentDefinition<TOutput>;

/**
 * Configures the initial prompt for the agent.
 */
export interface PromptConfig {
  /**
   * A single system prompt string. Supports templating using `${input_name}` syntax.
   */
  systemPrompt?: string;
  /**
   * An array of user/model content pairs for few-shot prompting.
   */
  initialMessages?: Content[];

  /**
   * The specific task or question to trigger the agent's execution loop.
   * This is sent as the first user message, distinct from the systemPrompt (identity/rules)
   * and initialMessages (history/few-shots). Supports templating.
   * If not provided, a generic "Get Started!" message is used.
   */
  query?: string;
}

/**
 * Configures the tools available to the agent during its execution.
 */
export interface ToolConfig {
  tools: Array<string | FunctionDeclaration | AnyDeclarativeTool>;
}

/**
 * Configures the expected inputs (parameters) for the agent.
 */
export interface InputConfig {
  inputSchema: AnySchema;
}

/**
 * Configures the expected outputs for the agent.
 */
export interface OutputConfig<T extends z.ZodTypeAny> {
  /**
   * The name of the final result parameter. This will be the name of the
   * argument in the `submit_final_output` tool (e.g., "report", "answer").
   */
  outputName: string;
  /**
   * A description of the expected output. This will be used as the description
   * for the tool argument.
   */
  description: string;
  /**
   * Optional JSON schema for the output. If provided, it will be used as the
   * schema for the tool's argument, allowing for structured output enforcement.
   * Defaults to { type: 'string' }.
   */
  schema: T;
}

/**
 * Configures the execution environment and constraints for the agent.
 */
export interface RunConfig {
  /**
   * The maximum execution time for the agent in minutes.
   * If not specified, defaults to DEFAULT_MAX_TIME_MINUTES (10).
   */
  maxTimeMinutes?: number;
  /**
   * The maximum number of conversational turns.
   * If not specified, defaults to DEFAULT_MAX_TURNS (30).
   */
  maxTurns?: number;
}
