import { createLogger } from "../../../src/utils/logger/index.js";
import { init_logger } from "../../../src/utils/logger.js";
import { renderTelegramHtmlText, stripUnknownHtmlTags } from "./format.js";
import { createFinalizableDraftLifecycle } from "./draft-stream-lifecycle.js";
import { isSafeToRetrySendError, isTelegramClientRejection, isTelegramThreadNotFoundError, shouldFallbackFromDraftTransport } from "./telegram-draft-errors.js";
//#region extensions/telegram/src/draft-stream.ts
init_logger();
const log = createLogger("DraftStream");
const TELEGRAM_STREAM_MAX_CHARS = 4096;
const DEFAULT_THROTTLE_MS = 1e3;
const TELEGRAM_DRAFT_ID_MAX = 2147483647;
let draftStreamState;
function getDraftStreamState() {
	draftStreamState ??= { nextDraftId: 0 };
	return draftStreamState;
}
function allocateTelegramDraftId() {
	const state = getDraftStreamState();
	state.nextDraftId = state.nextDraftId >= TELEGRAM_DRAFT_ID_MAX ? 1 : state.nextDraftId + 1;
	return state.nextDraftId;
}
function resolveSendMessageDraftApi(api) {
	const sendMessageDraft = api.sendMessageDraft;
	if (typeof sendMessageDraft !== "function") return;
	return sendMessageDraft.bind(api);
}
const STAGE_CONFIG = {
	thinking: {
		emoji: "🤔",
		label: "Thinking"
	},
	searching: {
		emoji: "🔍",
		label: "Searching"
	},
	reading: {
		emoji: "📖",
		label: "Reading"
	},
	writing: {
		emoji: "✍️",
		label: "Writing"
	},
	executing: {
		emoji: "⚙️",
		label: "Executing"
	},
	analyzing: {
		emoji: "📊",
		label: "Analyzing"
	},
	idle: {
		emoji: "💬",
		label: "Ready"
	}
};
function isProbablyPrivateChat(chatId) {
	const n = typeof chatId === "string" ? Number(chatId) : chatId;
	if (Number.isFinite(n) && n < 0) return false;
	return true;
}
function defaultRenderText(trimmed, parseMode) {
	if (parseMode === "HTML") return {
		text: stripUnknownHtmlTags(trimmed),
		parseMode: "HTML"
	};
	return {
		text: renderTelegramHtmlText(trimmed),
		parseMode: "HTML"
	};
}
/**
* Create a draft stream for streaming message previews.
*/
function createTelegramDraftStream(options) {
	const maxChars = Math.min(options.maxChars ?? TELEGRAM_STREAM_MAX_CHARS, TELEGRAM_STREAM_MAX_CHARS);
	const throttleMs = Math.max(250, options.throttleMs ?? DEFAULT_THROTTLE_MS);
	const chatId = options.chatId;
	const requestedPreviewTransport = options.previewTransport ?? "auto";
	const prefersDraftTransport = requestedPreviewTransport === "draft" ? true : requestedPreviewTransport === "message" ? false : isProbablyPrivateChat(chatId);
	const resolvedDraftApi = prefersDraftTransport ? resolveSendMessageDraftApi(options.api) : void 0;
	const usesDraftTransport = Boolean(prefersDraftTransport && resolvedDraftApi);
	if (prefersDraftTransport && !usesDraftTransport) options.warn?.("telegram stream preview: sendMessageDraft unavailable; falling back to sendMessage/editMessageText");
	const threadParams = {};
	if (options.threadId != null) threadParams.message_thread_id = options.threadId;
	if (options.replyToMessageId != null) threadParams.reply_to_message_id = options.replyToMessageId;
	const replyParams = { ...threadParams };
	const streamState = {
		stopped: false,
		final: false
	};
	let messageSendAttempted = false;
	let streamMessageId;
	let streamDraftId = usesDraftTransport ? allocateTelegramDraftId() : void 0;
	let previewTransport = usesDraftTransport ? "draft" : "message";
	let lastSentText = "";
	let lastDeliveredText = "";
	let lastSentParseMode;
	let previewRevision = 0;
	let generation = 0;
	const minInitialChars = options.minInitialChars;
	const renderPreview = (trimmed) => {
		if (options.renderText) return options.renderText(trimmed);
		return defaultRenderText(trimmed, options.parseMode);
	};
	const sendRenderedMessageWithThreadFallback = async (sendArgs) => {
		const sendParams = sendArgs.renderedParseMode ? {
			...replyParams,
			parse_mode: sendArgs.renderedParseMode
		} : replyParams;
		const usedThreadParams = "message_thread_id" in (sendParams ?? {}) && typeof sendParams.message_thread_id === "number";
		try {
			return {
				sent: await options.api.sendMessage(chatId, sendArgs.renderedText, sendParams),
				usedThreadParams
			};
		} catch (err) {
			if (!usedThreadParams || !isTelegramThreadNotFoundError(err)) throw err;
			const threadlessParams = { ...sendParams };
			delete threadlessParams.message_thread_id;
			options.warn?.(sendArgs.fallbackWarnMessage);
			return {
				sent: await options.api.sendMessage(chatId, sendArgs.renderedText, Object.keys(threadlessParams).length > 0 ? threadlessParams : void 0),
				usedThreadParams: false
			};
		}
	};
	const sendMessageTransportPreview = async ({ renderedText, renderedParseMode, sendGeneration }) => {
		if (typeof streamMessageId === "number") {
			if (renderedParseMode) await options.api.editMessageText(chatId, streamMessageId, renderedText, { parse_mode: renderedParseMode });
			else await options.api.editMessageText(chatId, streamMessageId, renderedText);
			return true;
		}
		messageSendAttempted = true;
		let sent;
		try {
			({sent} = await sendRenderedMessageWithThreadFallback({
				renderedText,
				renderedParseMode,
				fallbackWarnMessage: "telegram stream preview send failed with message_thread_id, retrying without thread"
			}));
		} catch (err) {
			if (isSafeToRetrySendError(err) || isTelegramClientRejection(err)) messageSendAttempted = false;
			throw err;
		}
		const sentMessageId = sent?.message_id;
		if (typeof sentMessageId !== "number" || !Number.isFinite(sentMessageId)) {
			streamState.stopped = true;
			options.warn?.("telegram stream preview stopped (missing message id from sendMessage)");
			return false;
		}
		const normalizedMessageId = Math.trunc(sentMessageId);
		if (sendGeneration !== generation) {
			options.onSupersededPreview?.({
				messageId: normalizedMessageId,
				textSnapshot: renderedText,
				parseMode: renderedParseMode
			});
			return true;
		}
		streamMessageId = normalizedMessageId;
		return true;
	};
	const sendDraftTransportPreview = async ({ renderedText, renderedParseMode }) => {
		const draftId = streamDraftId ?? allocateTelegramDraftId();
		streamDraftId = draftId;
		const draftParams = {
			...threadParams.message_thread_id != null ? { message_thread_id: threadParams.message_thread_id } : {},
			...renderedParseMode ? { parse_mode: renderedParseMode } : {}
		};
		await resolvedDraftApi(chatId, draftId, renderedText, Object.keys(draftParams).length > 0 ? draftParams : void 0);
		return true;
	};
	const sendOrEditStreamMessage = async (text) => {
		if (streamState.stopped && !streamState.final) return false;
		const trimmed = text.trimEnd();
		if (!trimmed) return false;
		const rendered = renderPreview(trimmed);
		const renderedText = rendered.text.trimEnd();
		const renderedParseMode = rendered.parseMode;
		if (!renderedText) return false;
		if (renderedText.length > maxChars) {
			streamState.stopped = true;
			options.warn?.(`telegram stream preview stopped (text length ${renderedText.length} > ${maxChars})`);
			return false;
		}
		if (renderedText === lastSentText && renderedParseMode === lastSentParseMode) return true;
		const sendGeneration = generation;
		if (typeof streamMessageId !== "number" && minInitialChars != null && !streamState.final) {
			if (renderedText.length < minInitialChars) return false;
		}
		lastSentText = renderedText;
		lastSentParseMode = renderedParseMode;
		try {
			let sent = false;
			if (previewTransport === "draft") try {
				sent = await sendDraftTransportPreview({
					renderedText,
					renderedParseMode,
					sendGeneration
				});
			} catch (err) {
				if (!shouldFallbackFromDraftTransport(err)) throw err;
				previewTransport = "message";
				streamDraftId = void 0;
				options.warn?.("telegram stream preview: sendMessageDraft rejected by API; falling back to sendMessage/editMessageText");
				sent = await sendMessageTransportPreview({
					renderedText,
					renderedParseMode,
					sendGeneration
				});
			}
			else sent = await sendMessageTransportPreview({
				renderedText,
				renderedParseMode,
				sendGeneration
			});
			if (sent) {
				previewRevision += 1;
				lastDeliveredText = trimmed;
			}
			return sent;
		} catch (err) {
			streamState.stopped = true;
			options.warn?.(`telegram stream preview failed: ${err instanceof Error ? err.message : String(err)}`);
			return false;
		}
	};
	const { loop, update, stop, clear } = createFinalizableDraftLifecycle({
		throttleMs,
		state: streamState,
		sendOrEditStreamMessage,
		readMessageId: () => streamMessageId,
		clearMessageId: () => {
			streamMessageId = void 0;
		},
		isValidMessageId: (value) => typeof value === "number" && Number.isFinite(value),
		deleteMessage: async (messageId) => {
			await options.api.deleteMessage(chatId, messageId);
		},
		onDeleteSuccess: (messageId) => {
			log.debug({
				chatId,
				messageId
			}, "telegram stream preview deleted");
		},
		warn: (m) => options.warn?.(m),
		warnPrefix: "telegram stream preview cleanup failed"
	});
	const forceNewMessage = () => {
		streamState.final = false;
		streamState.stopped = false;
		generation += 1;
		messageSendAttempted = false;
		streamMessageId = void 0;
		if (previewTransport === "draft") streamDraftId = allocateTelegramDraftId();
		lastSentText = "";
		lastSentParseMode = void 0;
		loop.resetPending();
		loop.resetThrottleWindow();
	};
	const materialize = async () => {
		await stop();
		if (previewTransport === "message" && typeof streamMessageId === "number") return streamMessageId;
		const renderedText = lastSentText || lastDeliveredText;
		if (!renderedText) return;
		const renderedParseMode = lastSentText ? lastSentParseMode : void 0;
		try {
			const { sent, usedThreadParams } = await sendRenderedMessageWithThreadFallback({
				renderedText,
				renderedParseMode,
				fallbackWarnMessage: "telegram stream preview materialize send failed with message_thread_id, retrying without thread"
			});
			const sentId = sent?.message_id;
			if (typeof sentId === "number" && Number.isFinite(sentId)) {
				streamMessageId = Math.trunc(sentId);
				if (resolvedDraftApi != null && streamDraftId != null) {
					const clearDraftId = streamDraftId;
					const clearThreadParams = usedThreadParams && threadParams.message_thread_id != null ? { message_thread_id: threadParams.message_thread_id } : void 0;
					try {
						await resolvedDraftApi(chatId, clearDraftId, "", clearThreadParams);
					} catch {}
				}
				return streamMessageId;
			}
		} catch (err) {
			options.warn?.(`telegram stream preview materialize failed: ${err instanceof Error ? err.message : String(err)}`);
		}
	};
	let currentProgressStage = null;
	let currentProgressDetail = "";
	const setProgress = (stage, detail) => {
		if (!options.enableProgress) return;
		currentProgressStage = stage;
		currentProgressDetail = detail || "";
	};
	const formatProgressIndicator = (stage, detail) => {
		const config = STAGE_CONFIG[stage] || STAGE_CONFIG.idle;
		let indicator = `${config.emoji} ${config.label}`;
		if (detail) indicator += `\n${detail}`;
		return indicator;
	};
	const updateWithProgress = (text, stage, detail) => {
		if (streamState.stopped || streamState.final) return;
		if (stage) setProgress(stage, detail);
		let combinedText = text;
		if (options.enableProgress && currentProgressStage && currentProgressStage !== "idle") combinedText = `${formatProgressIndicator(currentProgressStage, currentProgressDetail)}\n\n${text}`;
		update(combinedText);
	};
	log.debug({
		chatId,
		maxChars,
		throttleMs,
		previewTransport
	}, "Draft stream ready");
	return {
		update,
		updateWithProgress,
		flush: loop.flush,
		messageId: () => streamMessageId,
		previewMode: () => previewTransport,
		previewRevision: () => previewRevision,
		lastDeliveredText: () => lastDeliveredText,
		clear,
		stop,
		materialize,
		forceNewMessage,
		sendMayHaveLanded: () => messageSendAttempted && typeof streamMessageId !== "number",
		setProgress
	};
}
var DraftStreamManager = class {
	streams = /* @__PURE__ */ new Map();
	getOrCreate(key, options) {
		const existing = this.streams.get(key);
		if (existing) return existing;
		const stream = createTelegramDraftStream(options);
		this.streams.set(key, stream);
		const originalStop = stream.stop;
		stream.stop = async () => {
			await originalStop.call(stream);
			this.streams.delete(key);
		};
		return stream;
	}
	get(key) {
		return this.streams.get(key);
	}
	async stop(key) {
		const stream = this.streams.get(key);
		if (stream) {
			await stream.flush();
			await stream.stop();
			this.streams.delete(key);
		}
	}
	async stopAll() {
		const promises = [];
		for (const [key, stream] of this.streams.entries()) promises.push((async () => {
			await stream.flush();
			await stream.stop();
			this.streams.delete(key);
		})());
		await Promise.all(promises);
	}
	async clear(key) {
		const stream = this.streams.get(key);
		if (stream) await stream.clear();
	}
};
const draftStreamManager = new DraftStreamManager();
const __testing = { resetTelegramDraftStreamForTests() {
	getDraftStreamState().nextDraftId = 0;
} };
//#endregion
export { DraftStreamManager, __testing, createTelegramDraftStream, draftStreamManager };

//# sourceMappingURL=draft-stream.js.map