import { ContextCache } from "./contextCache.js";
import { ContextFetcher, MissingRequiredVariableError } from "./ContextFetcher.js";
import { getLogger, getTracer, setSpanWithError } from "@inkeep/agents-core";
import { SpanStatusCode } from "@opentelemetry/api";
import crypto from "node:crypto";

//#region src/domains/run/context/ContextResolver.ts
const logger = getLogger("context-resolver");
const tracer = getTracer("context-resolver");
var ContextResolver = class {
	fetcher;
	cache;
	executionContext;
	constructor(executionContext, credentialStoreRegistry) {
		this.executionContext = executionContext;
		this.fetcher = new ContextFetcher(executionContext, credentialStoreRegistry);
		this.cache = new ContextCache(executionContext);
		logger.info({
			tenantId: this.executionContext.tenantId,
			hasCredentialSupport: !!credentialStoreRegistry
		}, "ContextResolver initialized");
	}
	/**
	* Resolve all contexts for a given configuration and trigger event
	*/
	async resolve(contextConfig, options) {
		const startTime = Date.now();
		logger.info({
			contextConfigId: contextConfig.id,
			triggerEvent: options.triggerEvent,
			conversationId: options.conversationId
		}, "Starting context resolution");
		return tracer.startActiveSpan("context.resolve", { attributes: {
			"context.config_id": contextConfig.id,
			"context.trigger_event": options.triggerEvent
		} }, async (parentSpan) => {
			try {
				const result = {
					resolvedContext: {},
					headers: options.headers || {},
					fetchedDefinitions: [],
					cacheHits: [],
					cacheMisses: [],
					errors: [],
					skipped: [],
					totalDurationMs: 0
				};
				result.resolvedContext.headers = result.headers;
				const currentHeaders = await this.cache.get({
					conversationId: options.conversationId,
					contextConfigId: contextConfig.id,
					contextVariableKey: "headers"
				});
				if (options.headers && Object.keys(options.headers).length > 0) {
					await this.cache.invalidateHeaders(this.executionContext.tenantId, this.executionContext.projectId, options.conversationId, contextConfig.id);
					logger.info({
						conversationId: options.conversationId,
						contextConfigId: contextConfig.id
					}, "Invalidated headers in cache");
					await this.cache.set({
						contextConfigId: contextConfig.id,
						contextVariableKey: "headers",
						conversationId: options.conversationId,
						value: options.headers,
						tenantId: this.executionContext.tenantId
					});
					logger.info({
						conversationId: options.conversationId,
						contextConfigId: contextConfig.id
					}, "Headers set in cache");
				} else if (currentHeaders) result.headers = currentHeaders.value;
				else result.headers = {};
				result.resolvedContext.headers = result.headers;
				const contextVariables = contextConfig.contextVariables || {};
				const contextVariableEntries = Object.entries(contextVariables);
				if (contextVariableEntries.length === 0) {
					logger.info({ contextConfigId: contextConfig.id }, "No context variables in context config");
					result.totalDurationMs = Date.now() - startTime;
					parentSpan.setStatus({ code: SpanStatusCode.OK });
					return result;
				}
				contextVariableEntries.filter(([, def]) => def.trigger === "initialization");
				const invocationDefs = contextVariableEntries.filter(([, def]) => def.trigger === "invocation");
				if (options.triggerEvent === "invocation" && invocationDefs.length > 0) await this.cache.invalidateInvocationDefinitions(this.executionContext.tenantId, this.executionContext.projectId, options.conversationId, contextConfig.id, invocationDefs.map(([, def]) => def.id));
				const requestHash = this.createRequestHash(result.headers);
				const fetchPromises = contextVariableEntries.map(([templateKey, definition]) => this.resolveSingleFetchDefinition(contextConfig, definition, templateKey, options, requestHash, result).catch((error) => {
					const errorMessage = error instanceof Error ? error.message : "Unknown error";
					if (error instanceof MissingRequiredVariableError) {
						logger.info({
							contextConfigId: contextConfig.id,
							definitionId: definition.id,
							templateKey,
							reason: errorMessage
						}, "Context fetch skipped due to missing required variable");
						result.skipped.push({
							definitionId: definition.id,
							reason: errorMessage
						});
						if (definition.defaultValue !== void 0) {
							result.resolvedContext[templateKey] = definition.defaultValue;
							logger.info({
								contextConfigId: contextConfig.id,
								definitionId: definition.id,
								templateKey
							}, "Using default value for skipped context variable");
						}
						return;
					}
					logger.error({
						contextConfigId: contextConfig.id,
						definitionId: definition.id,
						templateKey,
						error: errorMessage
					}, "Failed to resolve context variable");
					result.errors.push({
						definitionId: definition.id,
						error: errorMessage
					});
					if (definition.defaultValue !== void 0) {
						result.resolvedContext[templateKey] = definition.defaultValue;
						logger.info({
							contextConfigId: contextConfig.id,
							definitionId: definition.id,
							templateKey
						}, "Using default value for failed context variable");
					}
				}));
				await Promise.all(fetchPromises);
				result.totalDurationMs = Date.now() - startTime;
				parentSpan.addEvent("context.resolution.completed", {
					resolved_keys: Object.keys(result.resolvedContext),
					fetched_definitions: result.fetchedDefinitions,
					skipped_definitions: result.skipped.map((s) => s.definitionId)
				});
				if (result.errors.length > 0) parentSpan.setStatus({
					code: SpanStatusCode.ERROR,
					message: `Context resolution completed with errors`
				});
				else parentSpan.setStatus({ code: SpanStatusCode.OK });
				logger.info({
					contextConfigId: contextConfig.id,
					resolvedKeys: Object.keys(result.resolvedContext),
					fetchedDefinitions: result.fetchedDefinitions.length,
					cacheHits: result.cacheHits.length,
					cacheMisses: result.cacheMisses.length,
					errors: result.errors.length,
					skipped: result.skipped.length,
					totalDurationMs: result.totalDurationMs
				}, "Context resolution completed");
				return result;
			} catch (error) {
				const durationMs = Date.now() - startTime;
				setSpanWithError(parentSpan, error instanceof Error ? error : new Error(String(error)));
				logger.error({
					contextConfigId: contextConfig.id,
					error: error instanceof Error ? error.message : String(error),
					durationMs
				}, "Context resolution failed");
				throw error;
			} finally {
				parentSpan.end();
			}
		});
	}
	/**
	* Resolve a single context variable
	*/
	async resolveSingleFetchDefinition(contextConfig, definition, templateKey, options, requestHash, result) {
		const cachedEntry = await this.cache.get({
			conversationId: options.conversationId,
			contextConfigId: contextConfig.id,
			contextVariableKey: templateKey,
			requestHash
		});
		if (cachedEntry) {
			result.resolvedContext[templateKey] = cachedEntry.value;
			result.cacheHits.push(definition.id);
			logger.debug({
				definitionId: definition.id,
				templateKey,
				conversationId: options.conversationId
			}, "Cache hit for context variable");
			return;
		}
		result.cacheMisses.push(definition.id);
		logger.debug({
			definitionId: definition.id,
			templateKey,
			conversationId: options.conversationId
		}, "Cache miss for context variable, fetching data");
		const definitionWithConversationId = {
			...definition,
			fetchConfig: {
				...definition.fetchConfig,
				conversationId: options.conversationId
			}
		};
		const fetchedData = await tracer.startActiveSpan("context-resolver.resolve_single_fetch_definition", { attributes: {
			"context.definition_id": definition.id,
			"context.template_key": templateKey,
			"context.url_template": definition.fetchConfig.url,
			"context.method": definition.fetchConfig.method,
			"context.trigger": definition.trigger
		} }, async (parentSpan) => {
			try {
				const { data, resolvedUrl } = await this.fetcher.fetch(definitionWithConversationId, result.resolvedContext);
				parentSpan.setStatus({ code: SpanStatusCode.OK });
				parentSpan.addEvent("context.fetch_success", {
					definition_id: definition.id,
					template_key: templateKey,
					source: resolvedUrl
				});
				return data;
			} catch (error) {
				if (error instanceof MissingRequiredVariableError) {
					parentSpan.setStatus({ code: SpanStatusCode.OK });
					parentSpan.addEvent("context.fetch_skipped", {
						definition_id: definition.id,
						template_key: templateKey,
						reason: error.message
					});
				} else setSpanWithError(parentSpan, error instanceof Error ? error : new Error(String(error)));
				throw error;
			} finally {
				parentSpan.end();
			}
		});
		result.resolvedContext[templateKey] = fetchedData;
		result.fetchedDefinitions.push(definition.id);
		await this.cache.set({
			contextConfigId: contextConfig.id,
			contextVariableKey: templateKey,
			conversationId: options.conversationId,
			value: fetchedData,
			requestHash,
			tenantId: this.executionContext.tenantId
		});
		logger.debug({
			definitionId: definition.id,
			templateKey,
			conversationId: options.conversationId
		}, "Context variable resolved and cached");
	}
	/**
	* Resolve the headers for a given conversation
	*/
	async resolveHeaders(conversationId, contextConfigId) {
		const cachedEntry = await this.cache.get({
			conversationId,
			contextConfigId,
			contextVariableKey: "headers"
		});
		if (cachedEntry) return cachedEntry.value;
		return {};
	}
	/**
	* Create a hash of the headers for cache invalidation
	*/
	createRequestHash(headers$1) {
		const contextString = JSON.stringify(headers$1, Object.keys(headers$1).sort());
		return crypto.createHash("sha256").update(contextString).digest("hex").substring(0, 16);
	}
	/**
	* Clear cache
	*/
	async clearCache(tenantId, projectId, conversationId) {
		await this.cache.clearConversation(tenantId, projectId, conversationId);
		logger.info({ conversationId }, "Context cache cleared for conversation");
	}
};

//#endregion
export { ContextResolver };