import { MCPTransportType } from "../types/utility.js";
import { getLogger } from "./logger.js";
import { MCP_TOOL_CONNECTION_TIMEOUT_MS, MCP_TOOL_INITIAL_RECONNECTION_DELAY_MS, MCP_TOOL_MAX_RECONNECTION_DELAY_MS, MCP_TOOL_MAX_RETRIES, MCP_TOOL_RECONNECTION_DELAY_GROWTH_FACTOR } from "../constants/execution-limits-shared/index.js";
import { z } from "@hono/zod-openapi";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import { CallToolResultSchema } from "@modelcontextprotocol/sdk/types.js";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
import { DEFAULT_REQUEST_TIMEOUT_MSEC } from "@modelcontextprotocol/sdk/shared/protocol.js";
import { jsonSchema, tool } from "ai";
import { asyncExitHook } from "exit-hook";
import { match } from "ts-pattern";

//#region src/utils/mcp-client.ts
const logger = getLogger("mcp-client");
const activeMcpClients = /* @__PURE__ */ new Set();
let exitHookRegistered = false;
function ensureExitHook() {
	if (exitHookRegistered) return;
	exitHookRegistered = true;
	asyncExitHook(async () => {
		const clients = Array.from(activeMcpClients);
		const results = await Promise.allSettled(clients.map((c) => c.disconnect()));
		for (let i = 0; i < results.length; i++) {
			const result = results[i];
			if (result.status === "rejected") console.error(`[MCP] Failed to disconnect client "${clients[i]?.name}" during exit:`, result.reason);
		}
	}, { wait: 5e3 });
}
var McpClient = class {
	name;
	client;
	timeout;
	transport;
	serverConfig;
	connected = false;
	constructor(opts) {
		this.name = opts.name;
		this.timeout = opts.timeout || DEFAULT_REQUEST_TIMEOUT_MSEC;
		this.serverConfig = opts.server;
		this.client = new Client({
			name: opts.name,
			version: opts.version || "1.0.0"
		}, { capabilities: opts.capabilities || {} });
	}
	isConnected() {
		return this.connected;
	}
	getInstructions() {
		return this.client.getInstructions();
	}
	async connect() {
		if (this.connected) return;
		await match(this.serverConfig).with({ type: MCPTransportType.streamableHttp }, (config) => this.connectHttp(config)).with({ type: MCPTransportType.sse }, (config) => this.connectSSE(config)).exhaustive();
		this.connected = true;
		const close = this.client.onclose;
		this.client.onclose = () => {
			this.connected = false;
			if (typeof close === "function") close();
		};
		ensureExitHook();
		activeMcpClients.add(this);
	}
	async connectSSE(config) {
		const url = typeof config.url === "string" ? config.url : config.url.toString();
		const headersToSend = config.headers || {};
		this.transport = new SSEClientTransport(new URL(url), {
			eventSourceInit: config.eventSourceInit,
			requestInit: { headers: headersToSend }
		});
		await this.client.connect(this.transport, { timeout: config.timeout ?? this.timeout });
	}
	async connectHttp(config) {
		const { url, requestInit } = config;
		const normalizeHeaders = (headers) => {
			if (!headers) return {};
			if (headers instanceof Headers) {
				const obj = {};
				headers.forEach((value, key) => {
					obj[key] = value;
				});
				return obj;
			}
			if (Array.isArray(headers)) return Object.fromEntries(headers);
			return headers;
		};
		const mergedHeaders = {
			...normalizeHeaders(requestInit?.headers),
			...config.headers || {}
		};
		const mergedRequestInit = {
			...requestInit,
			headers: mergedHeaders
		};
		const urlString = typeof url === "string" ? url : url.toString();
		this.transport = new StreamableHTTPClientTransport(new URL(urlString), {
			requestInit: mergedRequestInit,
			reconnectionOptions: {
				maxRetries: MCP_TOOL_MAX_RETRIES,
				maxReconnectionDelay: MCP_TOOL_MAX_RECONNECTION_DELAY_MS,
				initialReconnectionDelay: MCP_TOOL_INITIAL_RECONNECTION_DELAY_MS,
				reconnectionDelayGrowFactor: MCP_TOOL_RECONNECTION_DELAY_GROWTH_FACTOR,
				...config.reconnectionOptions
			},
			sessionId: config.sessionId
		});
		await this.client.connect(this.transport, { timeout: MCP_TOOL_CONNECTION_TIMEOUT_MS });
	}
	async disconnect() {
		activeMcpClients.delete(this);
		if (!this.transport) return;
		try {
			await this.transport.close();
		} catch (e) {
			console.error(`[MCP] Error disconnecting client "${this.name}":`, e);
			throw e;
		} finally {
			this.transport = void 0;
			this.connected = false;
		}
	}
	validateSelectedTools(tools, activeTools) {
		if (!activeTools) return;
		for (const item of activeTools) {
			if (tools.includes(item)) continue;
			console.warn(`[Tools] Tool ${item} not found in tools`);
		}
	}
	async selectTools() {
		const { tools } = await this.client.listTools();
		const { selectedTools, activeTools } = this.serverConfig;
		let availableTools;
		if (activeTools === void 0) availableTools = tools;
		else if (activeTools.length === 0) return [];
		else availableTools = tools.filter((tool$1) => activeTools.includes(tool$1.name));
		if (selectedTools === void 0) return availableTools;
		if (selectedTools.length === 0) return [];
		const toolNames = availableTools.map((tool$1) => tool$1.name);
		this.validateSelectedTools(toolNames, selectedTools);
		return availableTools.filter((tool$1) => selectedTools.includes(tool$1.name));
	}
	async tools() {
		const tools = await this.selectTools();
		const results = {};
		for (const def of tools) try {
			let schema;
			try {
				schema = z.fromJSONSchema(def.inputSchema);
			} catch (conversionError) {
				logger.warn({
					server: this.name,
					tool: def.name,
					error: conversionError instanceof Error ? conversionError.message : String(conversionError)
				}, "z.fromJSONSchema failed; passing raw JSON Schema through");
				schema = jsonSchema(def.inputSchema ?? {});
			}
			const createdTool = tool({
				id: `${this.name}.${def.name}`,
				description: def.description || "",
				inputSchema: schema,
				execute: async (context) => {
					return await this.client.callTool({
						name: def.name,
						arguments: context
					}, CallToolResultSchema, { timeout: this.timeout });
				}
			});
			if (def.name) results[def.name] = createdTool;
		} catch (e) {
			console.error(`Error creating tool ${def.name}:`, e);
		}
		return results;
	}
};

//#endregion
export { McpClient, activeMcpClients };