/**
 * @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';
import type { GeminiChat } from '../core/geminiChat.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;
  turn_count?: number;
  duration_ms?: number;
}

/**
 * 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 enum SubagentState {
  RUNNING = 'running',
  COMPLETED = 'completed',
  ERROR = 'error',
  CANCELLED = 'cancelled',
}

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

export interface SubagentProgress {
  isSubagentProgress: true;
  agentName: string;
  recentActivity: SubagentActivityItem[];
  state?: SubagentState;
  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 additional workspace directories scoped to this agent.
   * When provided, the agent receives a workspace context that extends
   * the parent's with these directories. Other agents and the main
   * session are unaffected. If omitted, the parent workspace context
   * is inherited unchanged.
   *
   * Note: Filesystem root paths (e.g. `/` or `C:\`) are rejected at
   * runtime to prevent accidentally granting access to the entire filesystem.
   */
  workspaceDirectories?: string[];

  /**
   * Allows this agent to access the canonical auto-memory inbox patch files
   * under `<projectMemoryDir>/.inbox/{private,global}/extraction.patch`.
   * This is intentionally narrow so the main session cannot bypass review by
   * writing arbitrary inbox patches.
   */
  memoryInboxAccess?: boolean;

  /**
   * Restricts write validation for this agent to extracted skill artifacts and
   * canonical auto-memory inbox patch files. Used by the background
   * auto-memory extractor so active memory files cannot be edited directly.
   */
  autoMemoryExtractionWriteAccess?: boolean;

  /**
   * Controls whether extension memory is injected into this agent's initial
   * session context when JIT context is enabled. Defaults to true.
   */
  includeExtensionContext?: boolean;

  /**
   * 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;

  /**
   * Optional hook invoked before each model call. Receives the active
   * {@link GeminiChat} instance and may modify chat history (e.g., to
   * supersede stale tool outputs and reclaim context-window tokens).
   *
   * Runs immediately after chat compression in the agent loop.
   */
  onBeforeTurn?: (
    chat: GeminiChat,
    signal?: AbortSignal,
  ) => Promise<void> | void;
}

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;
}

/**
 * Summary of an agent reload operation.
 */
export interface AgentReloadSummary {
  totalLoaded: number;
  localCount: number;
  remoteCount: number;
  newAgents: string[];
  updatedAgents: string[];
  deletedAgents: string[];
  errors: string[];
}
