import { validationHelper } from "./validation.js";
import { CredentialStuffer, JsonTransformer, TemplateEngine, getLogger } from "@inkeep/agents-core";

//#region src/domains/run/context/ContextFetcher.ts
const logger = getLogger("context-fetcher");
var MissingRequiredVariableError = class extends Error {
	constructor(variable) {
		super(`Missing required variable: ${variable}`);
		this.name = "MissingRequiredVariableError";
	}
};
/**
* GraphQL error checker - validates response for GraphQL errors and throws if found
*/
const checkGraphQLErrors = (data) => {
	if (data && typeof data === "object" && "errors" in data) {
		const errorObj = data;
		if (Array.isArray(errorObj.errors) && errorObj.errors.length > 0) {
			const agentqlErrors = errorObj.errors;
			const errorMessage = `GraphQL request failed with ${agentqlErrors.length} errors: ${agentqlErrors.map((e) => e.message || "Unknown error").join(", ")}`;
			throw new Error(errorMessage);
		}
	}
};
const responseErrorCheckers = [checkGraphQLErrors];
var ContextFetcher = class {
	executionContext;
	defaultTimeout;
	credentialStuffer;
	constructor(executionContext, credentialStoreRegistry, defaultTimeout = 1e4) {
		this.executionContext = executionContext;
		this.defaultTimeout = defaultTimeout;
		if (credentialStoreRegistry) this.credentialStuffer = new CredentialStuffer(credentialStoreRegistry);
		logger.info({
			tenantId: this.executionContext.tenantId,
			defaultTimeout: this.defaultTimeout,
			hasCredentialSupport: !!this.credentialStuffer
		}, "ContextFetcher initialized");
	}
	/**
	* Fetch data according to a fetch definition
	*/
	async fetch(definition, context) {
		const startTime = Date.now();
		logger.info({
			definitionId: definition.id,
			url: definition.fetchConfig.url
		}, "Starting context fetch");
		try {
			const resolvedConfig = await this.resolveTemplateVariables(definition.fetchConfig, context, definition.credentialReferenceId);
			const response = await this.performRequest(resolvedConfig);
			let transformedData = response.data;
			if (definition.fetchConfig.transform) transformedData = await this.transformResponse(response.data, definition.fetchConfig.transform);
			if (definition.responseSchema) this.validateResponseWithJsonSchema(transformedData, definition.responseSchema, definition.id);
			const durationMs = Date.now() - startTime;
			logger.info({
				definitionId: definition.id,
				source: response.source,
				durationMs
			}, "Context fetch completed successfully");
			return {
				data: transformedData,
				resolvedUrl: resolvedConfig.url
			};
		} catch (error) {
			const durationMs = Date.now() - startTime;
			const errorMessage = error instanceof Error ? error.message : "Unknown error";
			if (error instanceof MissingRequiredVariableError) logger.error({
				definitionId: definition.id,
				error: errorMessage,
				durationMs
			}, "Context fetch skipped due to missing required variable");
			logger.error({
				definitionId: definition.id,
				error: errorMessage,
				durationMs
			}, "Context fetch failed");
			throw error;
		}
	}
	async getCredential(credentialReferenceId) {
		const { project, tenantId, projectId } = this.executionContext;
		try {
			const credentialReference = project.credentialReferences?.[credentialReferenceId];
			logger.info({ credentialReference }, "Credential reference");
			if (!credentialReference || !this.credentialStuffer) throw new Error(`Credential store not found for reference ID: ${credentialReferenceId}`);
			const credentialContext = {
				tenantId,
				projectId
			};
			const storeReference = {
				credentialStoreId: credentialReference.credentialStoreId,
				retrievalParams: credentialReference.retrievalParams || {}
			};
			return await this.credentialStuffer.getCredentials(credentialContext, storeReference);
		} catch (error) {
			logger.error({
				credentialReferenceId,
				error: error instanceof Error ? error.message : "Unknown error"
			}, "Failed to resolve credentials for fetch request");
			throw error;
		}
	}
	/**
	* Resolve template variables in fetch configuration and inject credential headers
	*/
	async resolveTemplateVariables(fetchConfig, context, credentialReferenceId) {
		const resolved = { ...fetchConfig };
		const filteredRequiredToFetch = fetchConfig.requiredToFetch?.filter((variable) => variable.startsWith("{{") && variable.endsWith("}}"));
		if (filteredRequiredToFetch) for (const variable of filteredRequiredToFetch) {
			let resolvedVariable;
			try {
				resolvedVariable = this.interpolateTemplate(variable, context);
			} catch {
				throw new MissingRequiredVariableError(variable);
			}
			if (resolvedVariable === "" || resolvedVariable === variable) throw new MissingRequiredVariableError(variable);
		}
		resolved.url = this.interpolateTemplate(fetchConfig.url, context);
		logger.info({ resolvedUrl: resolved.url }, "Resolved URL");
		if (fetchConfig.headers) {
			resolved.headers = {};
			for (const [key, value] of Object.entries(fetchConfig.headers)) resolved.headers[key] = this.interpolateTemplate(value, context);
		}
		if (fetchConfig.body) resolved.body = this.interpolateObjectTemplates(fetchConfig.body, context);
		if (credentialReferenceId && this.credentialStuffer) try {
			const credentialHeaders = (await this.getCredential(credentialReferenceId))?.headers;
			if (credentialHeaders) {
				resolved.headers = {
					...resolved.headers,
					...credentialHeaders
				};
				logger.info({ credentialReferenceId }, "Added credential headers to fetch request");
			}
		} catch (error) {
			logger.error({
				credentialReferenceId,
				error: error instanceof Error ? error.message : "Unknown error"
			}, "Failed to resolve credentials for fetch request");
			throw error;
		}
		return resolved;
	}
	/**
	* Interpolate template variables in a string using TemplateEngine
	*/
	interpolateTemplate(template, context) {
		try {
			return TemplateEngine.render(template, context, {
				strict: false,
				preserveUnresolved: true
			});
		} catch (error) {
			logger.error({
				template,
				error: error instanceof Error ? error.message : "Unknown error"
			}, "Failed to interpolate template variable");
			return template;
		}
	}
	/**
	* Interpolate template variables in an object recursively using TemplateEngine
	*/
	interpolateObjectTemplates(obj, context) {
		const result = {};
		for (const [key, value] of Object.entries(obj)) if (typeof value === "string") result[key] = this.interpolateTemplate(value, context);
		else if (value && typeof value === "object" && !Array.isArray(value)) result[key] = this.interpolateObjectTemplates(value, context);
		else result[key] = value;
		return result;
	}
	/**
	* Perform HTTP request
	*/
	async performRequest(config) {
		const startTime = Date.now();
		try {
			logger.debug({
				url: config.url,
				method: config.method
			}, "Performing HTTP request");
			const response = await fetch(config.url, {
				method: config.method,
				headers: config.headers,
				body: config.body ? JSON.stringify(config.body) : void 0,
				signal: AbortSignal.timeout(config.timeout || this.defaultTimeout)
			});
			if (!response.ok) {
				const errorText = await response.text();
				throw /* @__PURE__ */ new Error(`HTTP ${response.status}: ${response.statusText} - ${errorText}`);
			}
			const contentType = response.headers.get("content-type") || "";
			let data;
			if (contentType.includes("application/json")) data = await response.json();
			else data = await response.text();
			const durationMs = Date.now() - startTime;
			for (const checker of responseErrorCheckers) checker(data);
			return {
				data,
				source: config.url,
				durationMs
			};
		} catch (error) {
			const durationMs = Date.now() - startTime;
			const requestError = error instanceof Error ? error : /* @__PURE__ */ new Error("Unknown error");
			logger.warn({
				url: config.url,
				error: requestError.message,
				durationMs
			}, "HTTP request failed");
			throw requestError;
		}
	}
	/**
	* Transform response data using JsonTransformer (JMESPath)
	*/
	async transformResponse(data, transform) {
		try {
			return await JsonTransformer.transform(data, transform);
		} catch (error) {
			logger.error({
				transform,
				error: error instanceof Error ? error.message : "Unknown error"
			}, "Failed to transform response data");
			return data;
		}
	}
	/**
	* Validate response against JSON Schema
	*/
	validateResponseWithJsonSchema(data, jsonSchema, definitionId) {
		const validate = validationHelper(jsonSchema);
		if (!validate(data)) {
			const errors = validate.errors || [];
			const formattedErrors = errors.map((e) => `${e.instancePath || "/"}: ${e.message}`).join("; ");
			logger.error({
				definitionId,
				jsonSchema,
				errors,
				formattedErrors
			}, "JSON Schema response validation failed");
			throw new Error(`Response validation failed: ${formattedErrors}`);
		}
	}
	/**
	* Test a fetch definition without caching
	*/
	async test(definition, context) {
		const startTime = Date.now();
		try {
			const result = await this.fetch(definition, context);
			return {
				success: true,
				data: result.data,
				resolvedUrl: result.resolvedUrl,
				durationMs: Date.now() - startTime
			};
		} catch (error) {
			return {
				success: false,
				error: error instanceof Error ? error.message : "Unknown error",
				durationMs: Date.now() - startTime
			};
		}
	}
	/**
	* Get fetcher statistics
	*/
	getStats() {
		return {
			tenantId: this.executionContext.tenantId,
			defaultTimeout: this.defaultTimeout
		};
	}
};

//#endregion
export { ContextFetcher, MissingRequiredVariableError };