import { type Token, type Type } from '@frontmcp/di';

import { type ParsedPackageSpecifier } from '../../esm-loader/package-specifier';
import { type ProviderType } from '../interfaces';
import { type AgentMetadata, type EsmOptions, type RemoteAuthConfig, type RemoteTransportOptions } from '../metadata';

/**
 * Discriminator enum for agent record types.
 */
export enum AgentKind {
  /** Agent defined as a class decorated with @Agent */
  CLASS_TOKEN = 'CLASS_TOKEN',
  /** Agent defined using agent() function decorator */
  FUNCTION = 'FUNCTION',
  /** Agent provided as a direct value (e.g., for testing) */
  VALUE = 'VALUE',
  /** Agent created via factory function */
  FACTORY = 'FACTORY',
  /** Agent loaded from an npm package via esm.sh */
  ESM = 'ESM',
  /** Agent proxied from a remote MCP server */
  REMOTE = 'REMOTE',
}

/**
 * Record for class-based agents decorated with @Agent.
 *
 * @example
 * ```typescript
 * @Agent({
 *   name: 'research-agent',
 *   llm: { adapter: 'openai', model: 'gpt-4-turbo', apiKey: { env: 'OPENAI_API_KEY' } },
 * })
 * export default class ResearchAgent extends AgentContext { ... }
 * ```
 */
export interface AgentClassTokenRecord {
  kind: AgentKind.CLASS_TOKEN;
  provide: Type;
  metadata: AgentMetadata;
  providers?: ProviderType[];
}

/**
 * Record for function-based agents created with agent().
 *
 * @example
 * ```typescript
 * const researchAgent = agent({
 *   name: 'research-agent',
 *   inputSchema: { topic: z.string() },
 *   llm: { adapter: 'openai', model: 'gpt-4-turbo', apiKey: { env: 'OPENAI_API_KEY' } },
 * })((input, ctx) => {
 *   return { result: 'done' };
 * });
 * ```
 */
export interface AgentFunctionTokenRecord {
  kind: AgentKind.FUNCTION;
  // NOTE: `any` is intentional - function providers must be loosely typed
  // to support various input/output schema combinations at runtime
  provide: (...args: any[]) => any | Promise<any>;
  metadata: AgentMetadata;
  providers?: ProviderType[];
}

/**
 * Record for agents provided as direct values (useful for testing).
 */
export interface AgentValueRecord {
  kind: AgentKind.VALUE;
  provide: Token;
  useValue: unknown;
  metadata: AgentMetadata;
  providers?: ProviderType[];
}

/**
 * Record for agents created via factory functions with DI injection.
 *
 * @example
 * ```typescript
 * {
 *   useFactory: (configService: ConfigService) => new CustomAgent(configService),
 *   inject: [ConfigService],
 *   metadata: { name: 'custom-agent', llm: { ... } },
 * }
 * ```
 */
export interface AgentFactoryRecord {
  kind: AgentKind.FACTORY;
  provide: Token;
  useFactory: (...args: unknown[]) => unknown | Promise<unknown>;
  inject?: Token[];
  metadata: AgentMetadata;
  providers?: ProviderType[];
}

/**
 * Record for ESM-loaded agents from npm packages.
 */
export interface AgentEsmRecord {
  kind: AgentKind.ESM;
  provide: string;
  specifier: ParsedPackageSpecifier;
  metadata: AgentMetadata;
  providers?: ProviderType[];
}

/** Single named agent loaded from an npm package at runtime */
export interface AgentEsmTargetRecord {
  kind: AgentKind.ESM;
  provide: symbol;
  specifier: ParsedPackageSpecifier;
  /** Which agent to load from the package */
  targetName: string;
  options?: EsmOptions;
  metadata: AgentMetadata;
  providers?: ProviderType[];
}

/** Single named agent proxied from a remote MCP server */
export interface AgentRemoteRecord {
  kind: AgentKind.REMOTE;
  provide: symbol;
  /** Remote MCP server URL */
  url: string;
  /** Which agent to proxy */
  targetName: string;
  transportOptions?: RemoteTransportOptions;
  remoteAuth?: RemoteAuthConfig;
  metadata: AgentMetadata;
  providers?: ProviderType[];
}

/**
 * Union type of all possible agent record types.
 */
export type AgentRecord =
  | AgentClassTokenRecord
  | AgentFunctionTokenRecord
  | AgentValueRecord
  | AgentFactoryRecord
  | AgentEsmRecord
  | AgentEsmTargetRecord
  | AgentRemoteRecord;
