import type { ProviderOptions } from '@ai-sdk/provider-utils';
import type { LanguageModel, OnStepFinishEvent, OnStepStartEvent, smoothStream } from 'ai';
import type { JsonSchema7Type } from 'zod-to-json-schema';

import type { AgentMessage, ContentMetadata } from './message';
import type { ProviderId, ProviderCredentials } from '../../runtime/model/provider-credentials';
import type {
	AgentEvent,
	AgentEventHandler,
	SubAgentCompletedPayload,
	SubAgentStartedPayload,
} from '../runtime/event';
import type { SerializedMessageList } from '../runtime/message-list';
import type { BuiltTelemetry } from '../telemetry';
import type { JSONValue } from '../utils/json';

export type SmoothStreamOptions = NonNullable<Parameters<typeof smoothStream>[0]>;

export type FinishReason =
	| 'stop'
	| 'max-iterations'
	| 'length'
	| 'content-filter'
	| 'tool-calls'
	| 'error'
	| 'other';

export type TokenUsage<T extends Record<string, unknown> = Record<string, unknown>> = {
	promptTokens: number;
	completionTokens: number;
	totalTokens: number;
	/** Estimated cost in USD, computed from models.dev pricing. */
	cost?: number;
	inputTokenDetails?: {
		/** Uncached input tokens (billed at the full input rate). */
		noCache?: number;
		cacheRead?: number;
		cacheWrite?: number;
	};
	outputTokenDetails?: {
		reasoning?: number;
	};
	additionalMetadata?: T;
};

/**
 * Typed model config for known providers — gives IDE autocompletion for
 * provider-specific credential fields based on the model id prefix.
 */
export type TypedModelConfig = {
	[P in ProviderId]: { id: `${P}/${string}` } & ProviderCredentials<P>;
}[ProviderId];

export type ModelConfig =
	| string
	| TypedModelConfig
	| { id: string; [k: string]: unknown }
	// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents -- LanguageModel is semantically distinct from string
	| LanguageModel;

export interface AgentResult {
	id?: string;
	finishReason?: FinishReason;
	usage?: TokenUsage;
	/**
	 * The generated message
	 */
	messages: AgentMessage[];
	/**
	 * Metadata about the response from the provider
	 */
	providerMetadata?: Record<string, unknown>;
	rawResponse?: unknown;
	/** Parsed structured output when structuredOutput schema is set. */
	output?: unknown;
}

export type StreamChunk = ContentMetadata &
	(
		| { type: 'start-step' }
		| { type: 'finish-step' }
		| { type: 'text-start'; id: string }
		| { type: 'text-delta'; id: string; delta: string }
		| { type: 'text-end'; id: string }
		| { type: 'reasoning-start'; id: string }
		| { type: 'reasoning-delta'; id: string; delta: string }
		| { type: 'reasoning-end'; id: string }
		| { type: 'tool-input-start'; toolCallId: string; toolName: string }
		| { type: 'tool-input-delta'; toolCallId: string; delta: string }
		| { type: 'tool-call'; toolCallId: string; toolName: string; input: unknown }
		| {
				/**
				 * Emitted just before a tool handler starts executing. Bridged from
				 * the runtime event bus (not part of the AI SDK fullStream). Pairs
				 * with the subsequent `tool-result` to let consumers show a
				 * mid-flight indicator between "LLM picked a tool" and "result arrived".
				 */
				type: 'tool-execution-start';
				toolCallId: string;
				toolName: string;
				/** Epoch ms when the handler started, measured on the runtime. */
				startTime: number;
		  }
		| {
				/**
				 * Emitted as soon as an individual tool handler settles, bridged from
				 * the runtime event bus. Lets consumers flip a concurrent tool call to
				 * its terminal state immediately, instead of waiting for the batched
				 * `tool-result` chunks emitted only after the whole batch settles.
				 */
				type: 'tool-execution-end';
				toolCallId: string;
				toolName: string;
				isError: boolean;
				/** Epoch ms when the handler settled, measured on the runtime. */
				endTime: number;
		  }
		| {
				type: 'tool-result';
				toolCallId: string;
				toolName: string;
				output: unknown;
				isError?: boolean;
				canceled?: boolean;
		  }
		| {
				type: 'tool-call-suspended';
				runId: string;
				toolCallId: string;
				toolName: string;
				input?: unknown;
				suspendPayload?: unknown;
				/** JSON Schema describing the shape of data to send when resuming. */
				resumeSchema?: JsonSchema7Type;
		  }
		// `message` is reserved for sub-agent / app-defined `CustomAgentMessage`
		| { type: 'message'; message: AgentMessage }
		| ({ type: 'subagent-started' } & SubAgentStartedPayload)
		| ({ type: 'subagent-completed' } & SubAgentCompletedPayload)
		| {
				type: 'finish';
				finishReason: FinishReason;
				usage?: TokenUsage;
				model?: string;
				structuredOutput?: unknown;
		  }
		| { type: 'error'; error: unknown }
	);

export interface RunOptions {
	persistence?: AgentPersistenceOptions;
}

export interface AgentExecutionCounter {
	incrementMessageCount(): void;
	incrementToolCallCount(): void;
	incrementTokenCount(tokenCount: number): void;
}

export interface ExecutionOptions {
	maxIterations?: number;
	abortSignal?: AbortSignal;
	providerOptions?: ProviderOptions;
	/** AI SDK `smoothStream` transform. Enabled by default; pass `false` to disable. */
	smoothStream?: SmoothStreamOptions | false;
	/**
	 * Request the provider's raw stream events so a run aborted mid-turn can still
	 * be billed for the tokens it consumed before the stop. Off by default — only
	 * hosts that bill stopped runs (e.g. Instance AI) need it; leaving it off avoids
	 * streaming raw provider events that nothing consumes.
	 */
	recoverUsageOnAbort?: boolean;
	/** Inherited telemetry from a host runtime. */
	telemetry?: BuiltTelemetry;
	/** Inherited execution counter from the host runtime. Used for aggregate heartbeat telemetry. */
	executionCounter?: AgentExecutionCounter;
	onStepStart?: (event: OnStepStartEvent) => void | Promise<void>;
	onStepFinish?: (event: OnStepFinishEvent) => void | Promise<void>;
}

export interface PersistedExecutionOptions {
	maxIterations?: number;
}

export interface ToolResultEntry {
	tool: string;
	input: unknown;
	output: unknown;
	transformed?: boolean;
	canceled?: boolean;
}

export interface GenerateResult {
	/** Unique identifier for this run. Useful for HITL resume and correlation/logging. */
	runId: string;
	messages: AgentMessage[];
	structuredOutput?: unknown;
	usage?: TokenUsage;
	/** The model ID used for this generation (e.g. 'anthropic/claude-haiku-4-5'). */
	model?: string;
	finishReason?: FinishReason;
	providerMetadata?: Record<string, unknown>;
	/** Tool calls made during the run (with merged results when available). */
	toolCalls?: ToolResultEntry[];
	/**
	 * Present when the run suspended awaiting tool resume (HITL).
	 * Call `agent.resume('generate', data, { runId, toolCallId })` to resume.
	 */
	pendingSuspend?: Array<{
		runId: string;
		toolCallId: string;
		toolName: string;
		input: unknown;
		suspendPayload: unknown;
		/** JSON Schema describing the shape of data to send when resuming. */
		resumeSchema?: JsonSchema7Type;
	}>;
	/**
	 * Present when the run ended due to an error (finishReason === 'error').
	 * generate() never throws — errors are surfaced here instead so
	 * callers can handle them without try/catch.
	 */
	error?: unknown;
	/** Return a snapshot of the agent state for this run. */
	getState(): SerializableAgentState;
}

export interface StreamResult {
	/** Unique identifier for this run. Available immediately before any chunks are emitted. */
	runId: string;
	/** The readable stream of chunks. */
	stream: ReadableStream<StreamChunk>;
	/**
	 * Return the current agent state for this run.
	 * May be called while streaming or after the stream closes.
	 */
	getState(): SerializableAgentState;
}

export interface ResumeOptions {
	runId: string;
	toolCallId: string;
}

export interface BuiltAgent {
	readonly name: string;

	generate(
		input: AgentMessage[] | string,
		options?: RunOptions & ExecutionOptions,
	): Promise<GenerateResult>;
	stream(
		input: AgentMessage[] | string,
		options?: RunOptions & ExecutionOptions,
	): Promise<StreamResult>;

	on(event: AgentEvent, handler: AgentEventHandler): void;

	/** Cancel the currently running agent. Synchronous — sets an abort flag that the agentic loop checks asynchronously. */
	abort(): void;

	/**
	 * Close the agent and release all held resources.
	 *
	 * - Waits for any in-flight background tasks (title generation, observer
	 *   cycles) to settle via the runtime's `dispose()`.
	 * - Disconnects every MCP client that was attached via `.mcp()`.
	 *
	 * Safe to call multiple times. Should be called when the agent is no
	 * longer needed so MCP transports are not left open indefinitely.
	 */
	close(): Promise<void>;

	/** Resume a tool with custom resume data */
	resume(
		method: 'generate',
		data: unknown,
		options: ResumeOptions & ExecutionOptions,
	): Promise<GenerateResult>;
	resume(
		method: 'stream',
		data: unknown,
		options: ResumeOptions & ExecutionOptions,
	): Promise<StreamResult>;

	/** Approve a tool that uses requireApproval or needsApprovalFn */
	approve(method: 'generate', options: ResumeOptions & ExecutionOptions): Promise<GenerateResult>;
	approve(method: 'stream', options: ResumeOptions & ExecutionOptions): Promise<StreamResult>;

	/** Deny a tool that uses requireApproval or needsApprovalFn */
	deny(method: 'generate', options: ResumeOptions & ExecutionOptions): Promise<GenerateResult>;
	deny(method: 'stream', options: ResumeOptions & ExecutionOptions): Promise<StreamResult>;
}

// --- Checkpoint Storage ---

export type AgentRunState =
	| 'idle' // agent has not been started yet
	| 'running' // agent is running
	| 'success' // agent stopped successfully
	| 'failed' // agent stopped with an error
	| 'suspended' // agent is suspended by HITL, guardrail, or other control, can be resumed
	| 'waiting' // agent is waiting for a tool call to complete
	| 'cancelled'; // agent was cancelled by the user or engine

export interface AgentResumeData {
	resumeToolCall?: {
		toolCallId: string;
	};
	resumeData: unknown;
}

export type PendingToolCall = {
	toolCallId: string;
	toolName: string;
	input: JSONValue;
} & (
	| {
			suspended: true;
			suspendPayload: unknown;
			resumeSchema: JsonSchema7Type;
			runId: string;
	  }
	| {
			suspended: false;
	  }
);

export interface SerializableAgentState {
	persistence?: AgentPersistenceOptions;
	status: AgentRunState;
	messageList: SerializedMessageList;
	resumeData?: AgentResumeData;
	pendingToolCalls: Record<string, PendingToolCall>;
	finishReason?: FinishReason;
	usage?: TokenUsage;
	executionOptions?: PersistedExecutionOptions;
	/** Number of completed LLM iterations at suspension time. */
	iterationCount?: number;
}

export type AgentPersistenceOptions = {
	threadId: string;
	resourceId: string;
};
