import runDbClient_default from "../../../data/db/runDbClient.js";
import { getLogger as getLogger$1 } from "../../../logger.js";
import { deleteStreamChunks, getStreamChunks, insertStreamChunks, markStreamComplete } from "@inkeep/agents-core";

//#region src/domains/run/stream/stream-buffer-registry.ts
const logger = getLogger$1("stream-buffer-registry");
/** How often the write buffer is flushed to Postgres */
const FLUSH_INTERVAL_MS = 100;
/** How often the readable polls Postgres for new chunks when the local buffer is empty */
const POLL_INTERVAL_MS = 200;
/** Maximum time a readable will poll before giving up (prevents indefinite hangs) */
const MAX_POLL_DURATION_MS = 300 * 1e3;
var PgStreamBufferRegistry = class PgStreamBufferRegistry {
	writeBuffers = /* @__PURE__ */ new Map();
	decoder = new TextDecoder();
	static bufferKey(scope) {
		return `${scope.tenantId}:${scope.projectId}:${scope.conversationId}`;
	}
	register(scope) {
		const key = PgStreamBufferRegistry.bufferKey(scope);
		const existing = this.writeBuffers.get(key);
		if (existing?.flushTimer) clearInterval(existing.flushTimer);
		deleteStreamChunks(runDbClient_default)(scope).catch((err) => {
			logger.warn({
				err,
				conversationId: scope.conversationId
			}, "Failed to clear old stream chunks");
		});
		const buffer = {
			scope,
			pendingChunks: [],
			nextIdx: 0,
			flushTimer: null,
			done: false
		};
		buffer.flushTimer = setInterval(() => {
			this.flush(key).catch((err) => {
				logger.error({
					err,
					conversationId: scope.conversationId
				}, "Failed to flush stream chunks");
			});
		}, FLUSH_INTERVAL_MS);
		this.writeBuffers.set(key, buffer);
		logger.debug({ conversationId: scope.conversationId }, "Pg stream buffer registered");
	}
	push(scope, chunk) {
		const key = PgStreamBufferRegistry.bufferKey(scope);
		const buffer = this.writeBuffers.get(key);
		if (!buffer || buffer.done) return;
		buffer.pendingChunks.push({
			idx: buffer.nextIdx++,
			data: this.decoder.decode(chunk)
		});
	}
	async complete(scope) {
		const key = PgStreamBufferRegistry.bufferKey(scope);
		const buffer = this.writeBuffers.get(key);
		if (!buffer) return;
		buffer.done = true;
		if (buffer.flushTimer) {
			clearInterval(buffer.flushTimer);
			buffer.flushTimer = null;
		}
		await this.flush(key);
		try {
			await markStreamComplete(runDbClient_default)({
				...buffer.scope,
				finalIdx: buffer.nextIdx
			});
		} catch (err) {
			logger.error({
				err,
				conversationId: scope.conversationId
			}, "Failed to mark stream complete — resume clients may hang");
		}
		this.writeBuffers.delete(key);
		logger.debug({ conversationId: scope.conversationId }, "Pg stream buffer completed");
	}
	createReadable(scope, afterIdx = -1) {
		const encoder = new TextEncoder();
		let lastIdx = afterIdx;
		let cancelled = false;
		let buffer = [];
		const pollDeadline = Date.now() + MAX_POLL_DURATION_MS;
		return new ReadableStream({
			async pull(controller) {
				if (cancelled) return;
				try {
					while (buffer.length === 0) {
						if (cancelled) return;
						if (Date.now() > pollDeadline) {
							controller.close();
							return;
						}
						const rows = await getStreamChunks(runDbClient_default)({
							...scope,
							afterIdx: lastIdx
						});
						if (rows.length > 0) buffer = rows;
						else await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
					}
					const row = buffer.shift();
					if (!row) return;
					if (row.isFinal) {
						controller.close();
						return;
					}
					controller.enqueue(encoder.encode(row.data));
					lastIdx = row.idx;
				} catch (err) {
					if (!cancelled) controller.error(err);
				}
			},
			cancel() {
				cancelled = true;
			}
		});
	}
	async hasChunks(scope) {
		try {
			return (await getStreamChunks(runDbClient_default)({
				...scope,
				afterIdx: -1
			})).length > 0;
		} catch (err) {
			logger.error({
				err,
				...scope
			}, "Failed to check stream chunks, returning false");
			return false;
		}
	}
	async flush(bufferKey) {
		const buffer = this.writeBuffers.get(bufferKey);
		if (!buffer || buffer.pendingChunks.length === 0) return;
		const toFlush = [...buffer.pendingChunks];
		try {
			await insertStreamChunks(runDbClient_default)({
				...buffer.scope,
				chunks: toFlush
			});
			buffer.pendingChunks.splice(0, toFlush.length);
		} catch (err) {
			logger.error({
				err,
				conversationId: buffer.scope.conversationId,
				count: toFlush.length
			}, "Failed to insert stream chunks — will retry on next flush");
		}
	}
};
const streamBufferRegistry = new PgStreamBufferRegistry();

//#endregion
export { streamBufferRegistry };