import { getLogger } from "../../../logger.js";
import { STREAM_PARSER_MAX_COLLECTED_PARTS, STREAM_PARSER_MAX_SNAPSHOT_SIZE, STREAM_PARSER_MAX_STREAMED_SIZE } from "../constants/execution-limits/index.js";
import { ArtifactParser } from "./ArtifactParser.js";
import { agentSessionManager } from "./AgentSession.js";

//#region src/domains/run/services/IncrementalStreamParser.ts
const logger = getLogger("IncrementalStreamParser");
/**
* Incremental parser that processes streaming text and formats artifacts/objects as they become complete
* Uses the unified ArtifactParser to eliminate redundancy
*/
var IncrementalStreamParser = class IncrementalStreamParser {
	buffer = "";
	pendingTextBuffer = "";
	streamHelper;
	artifactParser;
	hasStartedRole = false;
	collectedParts = [];
	contextId;
	lastChunkWasToolResult = false;
	componentAccumulator = {};
	lastStreamedComponents = /* @__PURE__ */ new Map();
	componentSnapshots = /* @__PURE__ */ new Map();
	artifactMap;
	subAgentId;
	allStreamedContent = [];
	static MAX_SNAPSHOT_SIZE = STREAM_PARSER_MAX_SNAPSHOT_SIZE;
	static MAX_STREAMED_SIZE = STREAM_PARSER_MAX_STREAMED_SIZE;
	static MAX_COLLECTED_PARTS = STREAM_PARSER_MAX_COLLECTED_PARTS;
	constructor(streamHelper, executionContext, contextId, artifactParserOptions) {
		this.streamHelper = streamHelper;
		this.contextId = contextId;
		this.subAgentId = artifactParserOptions?.subAgentId;
		if (artifactParserOptions?.streamRequestId) {
			const sessionParser = agentSessionManager.getArtifactParser(artifactParserOptions.streamRequestId);
			if (sessionParser) {
				this.artifactParser = sessionParser;
				return;
			}
		}
		let sharedArtifactService = null;
		if (artifactParserOptions?.streamRequestId && typeof agentSessionManager.getArtifactService === "function") try {
			sharedArtifactService = agentSessionManager.getArtifactService(artifactParserOptions.streamRequestId);
		} catch (_error) {}
		this.artifactParser = new ArtifactParser(executionContext, {
			...artifactParserOptions,
			contextId,
			artifactService: sharedArtifactService
		});
	}
	/**
	* Initialize artifact map for artifact:ref lookups during streaming
	* Should be called before processing chunks
	*/
	async initializeArtifactMap() {
		try {
			this.artifactMap = await this.artifactParser.getContextArtifacts(this.contextId);
			logger.debug({
				contextId: this.contextId,
				artifactMapSize: this.artifactMap.size
			}, "Initialized artifact map for streaming");
		} catch (error) {
			logger.warn({
				error,
				contextId: this.contextId
			}, "Failed to initialize artifact map");
			this.artifactMap = /* @__PURE__ */ new Map();
		}
	}
	/**
	* Mark that a tool result just completed, so next text should have spacing
	*/
	markToolResult() {
		this.lastChunkWasToolResult = true;
	}
	/**
	* Process a new text chunk for text streaming (handles artifact markers)
	*/
	async processTextChunk(chunk) {
		if (this.lastChunkWasToolResult && this.buffer === "" && chunk) {
			chunk = `\n\n${chunk}`;
			this.lastChunkWasToolResult = false;
		}
		this.buffer += chunk;
		const parseResult = await this.parseTextBuffer();
		for (const part of parseResult.completeParts) await this.streamPart(part);
		this.buffer = parseResult.remainingBuffer;
	}
	/**
	* Process object deltas directly from Vercel AI SDK's fullStream
	* Accumulates components and streams them when they're stable (unchanged between deltas)
	*/
	async processObjectDelta(delta) {
		if (!delta || typeof delta !== "object") return;
		this.componentAccumulator = this.deepMerge(this.componentAccumulator, delta);
		if (this.componentAccumulator.dataComponents && Array.isArray(this.componentAccumulator.dataComponents)) {
			const components = this.componentAccumulator.dataComponents;
			const currentComponentIds = new Set(components.filter((c) => c?.id).map((c) => c.id));
			for (const [componentId, snapshot] of this.componentSnapshots.entries()) if (!currentComponentIds.has(componentId) && !this.lastStreamedComponents.has(componentId)) try {
				const component = JSON.parse(snapshot);
				if (this.isComponentComplete(component)) await this.streamComponent(component);
			} catch (_e) {}
			for (let i = 0; i < components.length; i++) {
				const component = components[i];
				if (!component?.id) continue;
				const componentKey = component.id;
				if (this.lastStreamedComponents.has(componentKey)) continue;
				const currentSnapshot = JSON.stringify(component);
				const previousSnapshot = this.componentSnapshots.get(componentKey);
				this.componentSnapshots.set(componentKey, currentSnapshot);
				if (this.componentSnapshots.size > IncrementalStreamParser.MAX_SNAPSHOT_SIZE) {
					const firstKey = this.componentSnapshots.keys().next().value;
					if (firstKey) this.componentSnapshots.delete(firstKey);
				}
				if (component.name === "Text" && component.props?.text) {
					const previousTextContent = previousSnapshot ? JSON.parse(previousSnapshot).props?.text || "" : "";
					const currentTextContent = component.props.text || "";
					if (currentTextContent.length > previousTextContent.length) {
						const newText = currentTextContent.slice(previousTextContent.length);
						if (!this.hasStartedRole) {
							await this.streamHelper.writeRole("assistant");
							this.hasStartedRole = true;
						}
						await this.streamHelper.streamText(newText, 50);
						const textPart = {
							kind: "text",
							text: newText
						};
						this.collectedParts.push(textPart);
						this.allStreamedContent.push(textPart);
					}
					continue;
				}
				if (this.isComponentComplete(component)) {
					const currentPropsSnapshot = JSON.stringify(component.props);
					if ((previousSnapshot ? JSON.stringify(JSON.parse(previousSnapshot).props) : null) === currentPropsSnapshot) await this.streamComponent(component);
				}
			}
		}
	}
	/**
	* Stream a component and mark it as streamed
	* Note: Text components are handled separately with incremental streaming
	*/
	async streamComponent(component) {
		const parts = await this.artifactParser.parseObject({ dataComponents: [component] }, this.artifactMap, this.subAgentId);
		if (!Array.isArray(parts)) {
			console.warn("parseObject returned non-array:", parts);
			return;
		}
		for (const part of parts) await this.streamPart(part);
		this.lastStreamedComponents.set(component.id, true);
		if (this.lastStreamedComponents.size > IncrementalStreamParser.MAX_STREAMED_SIZE) {
			const firstKey = this.lastStreamedComponents.keys().next().value;
			if (firstKey) this.lastStreamedComponents.delete(firstKey);
		}
		this.componentSnapshots.delete(component.id);
	}
	/**
	* Check if a component has the basic structure required for streaming
	* Requires id, name, and props object with content
	*/
	isComponentComplete(component) {
		if (!component || !component.id || !component.name) return false;
		if (!component.props || typeof component.props !== "object") return false;
		if (component.name === "Artifact" || component.props.artifact_id && (component.props.tool_call_id || component.props.task_id)) return Boolean(component.props.artifact_id && (component.props.tool_call_id || component.props.task_id));
		return true;
	}
	/**
	* Deep merge helper for object deltas
	*/
	deepMerge(target, source) {
		if (!source) return target;
		if (!target) return source;
		const result = { ...target };
		for (const key in source) if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key])) result[key] = this.deepMerge(target[key], source[key]);
		else result[key] = source[key];
		return result;
	}
	/**
	* Legacy method for backward compatibility - defaults to text processing
	*/
	async processChunk(chunk) {
		await this.processTextChunk(chunk);
	}
	/**
	* Process any remaining buffer content at the end of stream
	*/
	async finalize() {
		if (this.componentAccumulator.dataComponents && Array.isArray(this.componentAccumulator.dataComponents)) {
			const components = this.componentAccumulator.dataComponents;
			for (let i = 0; i < components.length; i++) {
				const component = components[i];
				if (!component?.id) continue;
				const componentKey = component.id;
				if (!this.lastStreamedComponents.has(componentKey) && this.isComponentComplete(component) && component.name !== "Text") {
					const parts = await this.artifactParser.parseObject({ dataComponents: [component] }, this.artifactMap, this.subAgentId);
					for (const part of parts) await this.streamPart(part);
					this.lastStreamedComponents.set(componentKey, true);
					if (this.lastStreamedComponents.size > IncrementalStreamParser.MAX_STREAMED_SIZE) {
						const firstKey = this.lastStreamedComponents.keys().next().value;
						if (firstKey) this.lastStreamedComponents.delete(firstKey);
					}
					this.componentSnapshots.delete(componentKey);
				}
			}
		}
		if (this.buffer) {
			const part = {
				kind: "text",
				text: this.buffer
			};
			await this.streamPart(part);
		}
		if (this.pendingTextBuffer) {
			const cleanedText = this.pendingTextBuffer.replace(/<\/?artifact:ref(?:\s[^>]*)?>\/?>/g, "").replace(/<\/?artifact(?:\s[^>]*)?>\/?>/g, "").replace(/<\/artifact:ref>/g, "").replace(/<\/(?:\w+:)?artifact>/g, "");
			if (cleanedText) {
				const textPart = {
					kind: "text",
					text: cleanedText
				};
				this.collectedParts.push(textPart);
				this.allStreamedContent.push(textPart);
				await this.streamHelper.streamText(cleanedText, 50);
			}
			this.pendingTextBuffer = "";
		}
		this.componentSnapshots.clear();
		this.lastStreamedComponents.clear();
		this.componentAccumulator = {};
	}
	/**
	* Get all collected parts for building the final response
	*/
	getCollectedParts() {
		return [...this.collectedParts];
	}
	/**
	* Get all streamed content that was actually sent to the user
	*/
	getAllStreamedContent() {
		return [...this.allStreamedContent];
	}
	/**
	* Parse buffer for complete artifacts and text parts (for text streaming)
	*/
	async parseTextBuffer() {
		const completeParts = [];
		const workingBuffer = this.buffer;
		if (this.artifactParser.hasIncompleteArtifact(workingBuffer)) {
			const safeEnd = this.artifactParser.findSafeTextBoundary(workingBuffer);
			if (safeEnd > 0) {
				const safeText = workingBuffer.slice(0, safeEnd);
				const parts$1 = await this.artifactParser.parseText(safeText, this.artifactMap, this.subAgentId);
				completeParts.push(...parts$1);
				return {
					completeParts,
					remainingBuffer: workingBuffer.slice(safeEnd)
				};
			}
			return {
				completeParts: [],
				remainingBuffer: workingBuffer
			};
		}
		const parts = await this.artifactParser.parseText(workingBuffer, this.artifactMap, this.subAgentId);
		if (parts.length > 0 && parts[parts.length - 1].kind === "text") {
			const lastText = parts[parts.length - 1].text || "";
			if (this.mightBeArtifactStart(lastText)) {
				parts.pop();
				return {
					completeParts: parts,
					remainingBuffer: lastText
				};
			}
		}
		return {
			completeParts: parts,
			remainingBuffer: ""
		};
	}
	/**
	* Check if text might be the start of an artifact marker
	*/
	mightBeArtifactStart(text) {
		const lastChars = text.slice(-20);
		return lastChars.includes("<") && !lastChars.includes("/>");
	}
	/**
	* Stream a formatted part (text or data) with smart buffering
	*/
	async streamPart(part) {
		this.collectedParts.push({ ...part });
		this.allStreamedContent.push({ ...part });
		if (this.collectedParts.length > IncrementalStreamParser.MAX_COLLECTED_PARTS) {
			const excess = this.collectedParts.length - IncrementalStreamParser.MAX_COLLECTED_PARTS;
			this.collectedParts.splice(0, excess);
		}
		if (this.allStreamedContent.length > IncrementalStreamParser.MAX_COLLECTED_PARTS) {
			const excess = this.allStreamedContent.length - IncrementalStreamParser.MAX_COLLECTED_PARTS;
			this.allStreamedContent.splice(0, excess);
		}
		if (!this.hasStartedRole) {
			await this.streamHelper.writeRole("assistant");
			this.hasStartedRole = true;
		}
		if (part.kind === "text" && part.text) {
			this.pendingTextBuffer += part.text;
			if (!this.artifactParser.hasIncompleteArtifact(this.pendingTextBuffer)) {
				const cleanedText = this.pendingTextBuffer.replace(/<\/?artifact:ref(?:\s[^>]*)?>\/?>/g, "").replace(/<\/?artifact(?:\s[^>]*)?>\/?>/g, "").replace(/<\/artifact:ref>/g, "").replace(/<\/(?:\w+:)?artifact>/g, "");
				if (cleanedText) await this.streamHelper.streamText(cleanedText, 50);
				this.pendingTextBuffer = "";
			}
		} else if (part.kind === "data" && part.data) {
			if (this.pendingTextBuffer) {
				const cleanedText = this.pendingTextBuffer.replace(/<\/?artifact:ref(?:\s[^>]*)?>\/?>/g, "").replace(/<\/?artifact(?:\s[^>]*)?>\/?>/g, "").replace(/<\/artifact:ref>/g, "").replace(/<\/(?:\w+:)?artifact>/g, "");
				if (cleanedText) await this.streamHelper.streamText(cleanedText, 50);
				this.pendingTextBuffer = "";
			}
			if (part.data.artifactId && part.data.toolCallId) await this.streamHelper.writeData("data-artifact", part.data);
			else await this.streamHelper.writeData("data-component", part.data);
		}
	}
};

//#endregion
export { IncrementalStreamParser };