import { getLogger } from "../../../logger.js";
import { SENTINEL_KEY } from "../constants/artifact-syntax.js";
import { buildSchemaShape, extractFullFields, extractPreviewFields } from "../utils/schema-validation.js";
import { ArtifactService } from "./ArtifactService.js";

//#region src/domains/run/artifacts/ArtifactParser.ts
const logger = getLogger("ArtifactParser");
var ToolChainResolutionError = class extends Error {
	constructor(toolCallId, message) {
		super(message);
		this.toolCallId = toolCallId;
		this.name = "ToolChainResolutionError";
	}
};
/**
* Artifact parser focused on parsing and text processing responsibilities
* Delegates business logic operations to ArtifactService
* Handles artifact tag detection, parsing, and text boundary detection
*/
var ArtifactParser = class ArtifactParser {
	static ARTIFACT_CHECK_REGEX = /<artifact:ref\s+(?=.*id=['"][^'"]+['"])(?=.*tool=['"][^'"]+['"])[^>]*\/>/;
	static ATTR_REGEX = /(\w+)="([^"]*)"|(\w+)='([^']*)'|(\w+)=({[^}]+})/g;
	static ARTIFACT_PATTERNS = [
		"<a",
		"<ar",
		"<art",
		"<arti",
		"<artif",
		"<artifa",
		"<artifac",
		"<artifact",
		"<artifact:",
		"<artifact:r",
		"<artifact:re",
		"<artifact:ref",
		"<artifact:c",
		"<artifact:cr",
		"<artifact:cre",
		"<artifact:crea",
		"<artifact:creat",
		"<artifact:create"
	];
	static INCOMPLETE_CREATE_REGEX = /<artifact:create(?!(?:"[^"]*"|'[^']*'|[^>])*(?:\/>|<\/artifact:create>))/;
	artifactService;
	contextWindowSize;
	artifactSchemasByType = {};
	constructor(executionContext, options) {
		this.contextWindowSize = options?.contextWindowSize;
		if (options?.artifactService) this.artifactService = options.artifactService;
		else this.artifactService = new ArtifactService({
			executionContext,
			...options
		});
		if (options?.artifactComponents) {
			for (const ac of options.artifactComponents) if (ac.name && ac.props?.properties) {
				const previewSchema = extractPreviewFields(ac.props);
				const fullSchema = extractFullFields(ac.props);
				this.artifactSchemasByType[ac.name] = {
					preview: previewSchema.properties ? buildSchemaShape(previewSchema.properties) : {},
					full: fullSchema.properties ? buildSchemaShape(fullSchema.properties) : {}
				};
			}
		}
	}
	/**
	* Set the context window size for oversized artifact detection
	*/
	setContextWindowSize(size) {
		this.contextWindowSize = size;
	}
	buildArtifactDataPart(artifactData) {
		const schemas = this.artifactSchemasByType[artifactData.type ?? ""];
		return {
			kind: "data",
			data: {
				artifactId: artifactData.artifactId,
				toolCallId: artifactData.toolCallId,
				name: artifactData.name,
				description: artifactData.description,
				type: artifactData.type,
				artifactSummary: artifactData.data,
				...schemas && { typeSchema: schemas }
			}
		};
	}
	/**
	* Check if text contains complete artifact markers (ref or create)
	*/
	hasArtifactMarkers(text) {
		const refMatch = ArtifactParser.ARTIFACT_CHECK_REGEX.test(text);
		const createMatch = /<artifact:create\s+((?:"[^"]*"|'[^']*'|[^>])+?)(?:\s*\/)?>(?:(.*?)<\/artifact:create>)?/gs.test(text);
		return refMatch || createMatch;
	}
	/**
	* Check if text has incomplete artifact marker (for streaming)
	* More robust detection that handles streaming fragments
	*/
	hasIncompleteArtifact(text) {
		return ArtifactParser.ARTIFACT_PATTERNS.some((pattern) => text.endsWith(pattern)) || /<artifact:ref[^>]+$/.test(text) || /<artifact:create(?:"[^"]*"|'[^']*'|[^>])*$/.test(text) || ArtifactParser.INCOMPLETE_CREATE_REGEX.test(text) && !text.includes("</artifact:create>") || this.findSafeTextBoundary(text) < text.length;
	}
	/**
	* Find safe text boundary before incomplete artifacts (for streaming)
	* Enhanced to handle streaming chunks that split in the middle of artifacts
	*/
	findSafeTextBoundary(text) {
		for (const pattern of [/<artifact:ref(?![^>]*\/>).*$/, /<artifact:create(?!(?:"[^"]*"|'[^']*'|[^>])*(?:\/>|<\/artifact:create>)).*$/]) {
			const match = text.match(pattern);
			if (match && match.index !== void 0) return match.index;
		}
		for (const pattern of ArtifactParser.ARTIFACT_PATTERNS) {
			const lastIndex = text.lastIndexOf(pattern);
			if (lastIndex !== -1) {
				const textAfterPattern = text.slice(lastIndex);
				if (!textAfterPattern.includes("/>") && !textAfterPattern.includes("</artifact:")) return lastIndex;
			}
		}
		return text.length;
	}
	/**
	* Get all artifacts for a context - delegates to service
	*/
	async getContextArtifacts(contextId) {
		return this.artifactService.getContextArtifacts(contextId);
	}
	/**
	* Resolve artifact refs embedded in tool call arguments.
	* Recursively walks the args object/array; any object of the shape
	* { $artifact: "artifact-id", $tool: "tool-call-id" } is replaced with
	* the full artifact data so the tool receives the real content.
	*/
	async resolveArgs(args) {
		if (args !== null && typeof args === "object" && !Array.isArray(args)) {
			if (typeof args[SENTINEL_KEY.ARTIFACT] === "string" && typeof args[SENTINEL_KEY.TOOL] === "string") {
				const fullData = await this.artifactService.getArtifactFull(args[SENTINEL_KEY.ARTIFACT], args[SENTINEL_KEY.TOOL]);
				if (fullData?.data) {
					logger.debug({
						artifactId: args[SENTINEL_KEY.ARTIFACT],
						toolCallId: args[SENTINEL_KEY.TOOL]
					}, "Resolved artifact ref in tool arg");
					return fullData.data;
				}
				throw new ToolChainResolutionError(args[SENTINEL_KEY.TOOL], `Artifact '${args[SENTINEL_KEY.ARTIFACT]}' from tool call '${args[SENTINEL_KEY.TOOL]}' could not be resolved`);
			}
			if (typeof args[SENTINEL_KEY.TOOL] === "string" && !(SENTINEL_KEY.ARTIFACT in args)) {
				const raw = this.artifactService.getToolResultRaw(args[SENTINEL_KEY.TOOL]);
				if (raw !== void 0) {
					logger.debug({ toolCallId: args[SENTINEL_KEY.TOOL] }, "Resolved ephemeral tool result ref in tool arg");
					return raw;
				}
				throw new ToolChainResolutionError(args[SENTINEL_KEY.TOOL], `Tool result for call '${args[SENTINEL_KEY.TOOL]}' not found or failed`);
			}
			const result = {};
			for (const [key, value] of Object.entries(args)) result[key] = await this.resolveArgs(value);
			return result;
		}
		if (Array.isArray(args)) {
			const resolved = [];
			for (const item of args) resolved.push(await this.resolveArgs(item));
			return resolved;
		}
		return args;
	}
	/**
	* Parse attributes from the artifact:create tag
	*/
	parseCreateAttributes(attrString) {
		const attrs = {};
		let match = null;
		ArtifactParser.ATTR_REGEX.lastIndex = 0;
		while ((match = ArtifactParser.ATTR_REGEX.exec(attrString)) !== null) {
			const key = match[1] || match[3] || match[5];
			let value = match[2] || match[4] || match[6];
			if (value?.startsWith("{")) try {
				value = JSON.parse(value);
			} catch {}
			attrs[key] = value;
		}
		if (!attrs.id || !attrs.tool || !attrs.type || !attrs.base) {
			logger.warn({
				attrs,
				attrString
			}, "Missing required attributes in artifact annotation");
			return null;
		}
		return {
			artifactId: attrs.id,
			toolCallId: attrs.tool,
			type: attrs.type,
			baseSelector: attrs.base,
			detailsSelector: attrs.details || {}
		};
	}
	/**
	* Parse artifact creation annotations from text
	*/
	parseCreateAnnotations(text) {
		const annotations = [];
		const matches = [...text.matchAll(/<artifact:create\s+((?:"[^"]*"|'[^']*'|[^>])+?)(?:\s*\/)?>(?:(.*?)<\/artifact:create>)?/gs)];
		for (const match of matches) {
			const [fullMatch, attributes] = match;
			const annotation = this.parseCreateAttributes(attributes);
			if (annotation) {
				annotation.raw = fullMatch;
				annotations.push(annotation);
			}
		}
		return annotations;
	}
	/**
	* Extract artifact data from a create annotation - delegates to service
	*/
	async extractFromCreateAnnotation(annotation, subAgentId) {
		const request = {
			artifactId: annotation.artifactId,
			toolCallId: annotation.toolCallId,
			type: annotation.type,
			baseSelector: annotation.baseSelector,
			detailsSelector: annotation.detailsSelector
		};
		return this.artifactService.createArtifact(request, subAgentId, this.contextWindowSize);
	}
	/**
	* Parse text with artifact markers into parts array
	* Handles both artifact:ref and artifact:create tags
	* Can work with or without pre-fetched artifact map
	*/
	async parseText(text, artifactMap, subAgentId) {
		let processedText = text;
		const createAnnotations = this.parseCreateAnnotations(text);
		const createdArtifactData = /* @__PURE__ */ new Map();
		const failedAnnotations = [];
		for (const annotation of createAnnotations) try {
			const artifactData = await this.extractFromCreateAnnotation(annotation, subAgentId);
			if (artifactData && annotation.raw) createdArtifactData.set(annotation.raw, artifactData);
			else if (annotation.raw) {
				failedAnnotations.push(`Failed to create artifact "${annotation.artifactId}": Missing or invalid data`);
				processedText = processedText.replace(annotation.raw, "");
				logger.warn({
					annotation,
					artifactData
				}, "Removed failed artifact:create annotation from output");
			}
		} catch (error) {
			const errorMsg = error instanceof Error ? error.message : "Unknown error";
			failedAnnotations.push(`Failed to create artifact "${annotation.artifactId}": ${errorMsg}`);
			if (annotation.raw) processedText = processedText.replace(annotation.raw, "");
			logger.error({
				annotation,
				error
			}, "Failed to extract artifact from create annotation");
		}
		if (failedAnnotations.length > 0) logger.warn({
			failedCount: failedAnnotations.length,
			failures: failedAnnotations
		}, "Some artifact creation attempts failed");
		const parts = [];
		const createRegex = /<artifact:create\s+((?:"[^"]*"|'[^']*'|[^>])+?)(?:\s*\/)?>(?:(.*?)<\/artifact:create>)?/gs;
		const refRegex = /<artifact:ref\s+id=(["'])([^"']*?)\1\s+tool=(["'])([^"']*?)\3\s*\/>/gs;
		const createMatches = [...text.matchAll(createRegex)];
		const refMatches = [...processedText.matchAll(refRegex)];
		const allMatches = [...createMatches.map((match) => ({
			match,
			type: "create"
		})), ...refMatches.map((match) => ({
			match,
			type: "ref"
		}))].sort((a, b) => (a.match.index || 0) - (b.match.index || 0));
		if (allMatches.length === 0) return [{
			kind: "text",
			text: processedText
		}];
		let lastIndex = 0;
		for (const { match, type } of allMatches) {
			if (match.index === void 0) continue;
			const matchStart = match.index;
			const fullMatch = match[0];
			if (matchStart > lastIndex) {
				const textBefore = text.slice(lastIndex, matchStart);
				if (textBefore) parts.push({
					kind: "text",
					text: textBefore
				});
			}
			let artifactData = null;
			if (type === "create") artifactData = createdArtifactData.get(fullMatch) || null;
			else {
				const [, , artifactId, , toolCallId] = match;
				artifactData = await this.getArtifactData(artifactId, toolCallId, artifactMap);
			}
			if (artifactData) parts.push(this.buildArtifactDataPart(artifactData));
			lastIndex = matchStart + fullMatch.length;
		}
		if (lastIndex < text.length) {
			const remainingText = text.slice(lastIndex);
			if (remainingText) parts.push({
				kind: "text",
				text: remainingText
			});
		}
		return parts;
	}
	/**
	* Process object/dataComponents for artifact components
	*/
	async parseObject(obj, artifactMap, subAgentId) {
		if (obj?.dataComponents && Array.isArray(obj.dataComponents)) {
			const parts = [];
			for (const component of obj.dataComponents) if (this.isArtifactComponent(component)) {
				const artifactData = await this.getArtifactData(component.props.artifact_id, component.props.tool_call_id, artifactMap);
				if (artifactData) parts.push(this.buildArtifactDataPart(artifactData));
			} else if (this.isArtifactCreateComponent(component)) {
				const createData = await this.extractFromArtifactCreateComponent(component, subAgentId);
				if (createData) parts.push(this.buildArtifactDataPart(createData));
			} else {
				const cleanProps = Object.fromEntries(Object.entries(component.props ?? {}).filter(([, v]) => v !== null));
				parts.push({
					kind: "data",
					data: {
						...component,
						props: cleanProps
					}
				});
			}
			return parts;
		}
		if (this.isArtifactComponent(obj)) {
			const artifactData = await this.getArtifactData(obj.props.artifact_id, obj.props.tool_call_id, artifactMap);
			return artifactData ? [this.buildArtifactDataPart(artifactData)] : [];
		}
		if (this.isArtifactCreateComponent(obj)) {
			const createData = await this.extractFromArtifactCreateComponent(obj, subAgentId);
			return createData ? [this.buildArtifactDataPart(createData)] : [];
		}
		return [{
			kind: "data",
			data: obj
		}];
	}
	/**
	* Check if object is an artifact component
	*/
	isArtifactComponent(obj) {
		return obj?.name === "Artifact" && obj?.props?.artifact_id && obj?.props?.tool_call_id;
	}
	/**
	* Check if object is an artifact create component
	*/
	isArtifactCreateComponent(obj) {
		return obj?.name?.startsWith("ArtifactCreate_") && obj?.props?.id && obj?.props?.tool_call_id;
	}
	/**
	* Extract artifact from ArtifactCreate component
	*/
	async extractFromArtifactCreateComponent(component, subAgentId) {
		const props = component.props;
		if (!props) return null;
		const annotation = {
			artifactId: props.id,
			toolCallId: props.tool_call_id,
			type: props.type,
			baseSelector: props.base_selector,
			detailsSelector: props.details_selector || {}
		};
		return await this.extractFromCreateAnnotation(annotation, subAgentId);
	}
	/**
	* Get artifact data - delegates to service
	*/
	async getArtifactData(artifactId, toolCallId, artifactMap) {
		return await this.artifactService.getArtifactSummary(artifactId, toolCallId, artifactMap);
	}
};

//#endregion
export { ArtifactParser, ToolChainResolutionError };