import { runWithLogContext, updateAsyncLogContext } from "../utils/logger/context.js";
import { createLogger } from "../utils/logger/index.js";
import { init_logger } from "../utils/logger.js";
import { formatEnvelopeTimestamp } from "./envelope-timestamp.js";
import { randomUUID } from "node:crypto";
//#region src/channels/pipeline.ts
/**
* Message Processing Pipeline
* 
* Three-stage pipeline:
* - Preflight: Filter empty messages, self-messages, detect commands
* - Process: Transform format, extract metadata
* - Delivery: Send to Agent
*/
init_logger();
const log = createLogger("Pipeline");
function pipelineLogRequestId(ctx) {
	const raw = ctx.metadata?.requestId;
	if (typeof raw === "string" && raw.trim().length > 0) return raw.trim();
	return randomUUID();
}
function pipelineLogSessionId(ctx) {
	const sk = ctx.metadata?.sessionKey;
	if (typeof sk === "string" && sk.trim().length > 0) return sk.trim();
	return `${ctx.channel}:${ctx.chatId}`;
}
var MessagePipeline = class {
	channel;
	preflightHandlers;
	processHandlers;
	deliveryHandlers;
	agentInvoke;
	onError;
	constructor(options) {
		this.channel = options.channel;
		this.preflightHandlers = options.preflightHandlers ?? [];
		this.processHandlers = options.processHandlers ?? [];
		this.deliveryHandlers = options.deliveryHandlers ?? [];
		this.agentInvoke = options.agentInvoke;
		this.onError = options.onError;
	}
	/**
	* Handle inbound message
	*/
	async handleMessage(ctx) {
		const channel = this.channel;
		await runWithLogContext({
			requestId: pipelineLogRequestId(ctx),
			sessionId: pipelineLogSessionId(ctx)
		}, async () => {
			let processedCtx = await this.runPreflight(ctx);
			if (!processedCtx) {
				log.debug({
					channel,
					chatId: ctx.chatId
				}, "Message filtered in preflight");
				return;
			}
			try {
				processedCtx = await this.runProcess(processedCtx);
			} catch (err) {
				log.error({
					channel,
					err
				}, "Process handler error");
				this.onError?.(err, processedCtx);
				return;
			}
			const resolvedSk = processedCtx.metadata?.sessionKey;
			if (typeof resolvedSk === "string" && resolvedSk.trim().length > 0) updateAsyncLogContext({ sessionId: resolvedSk.trim() });
			if (!this.agentInvoke) {
				log.warn({ channel }, "No agentInvoke configured");
				return;
			}
			let response;
			try {
				response = await this.agentInvoke(processedCtx);
			} catch (err) {
				log.error({
					channel,
					err
				}, "Agent invocation error");
				this.onError?.(err, processedCtx);
				return;
			}
			try {
				await this.runDelivery(processedCtx, response);
			} catch (err) {
				log.error({
					channel,
					err
				}, "Delivery handler error");
				this.onError?.(err, processedCtx);
			}
		});
	}
	async runPreflight(ctx) {
		for (const handler of this.preflightHandlers) try {
			const result = await handler.preflight?.(ctx);
			if (!result) {
				log.debug({
					channel: this.channel,
					handler: handler.name
				}, "Preflight filtered message");
				return null;
			}
			ctx = result;
		} catch (err) {
			log.error({
				channel: this.channel,
				handler: handler.name,
				err
			}, "Preflight handler error");
			return null;
		}
		return ctx;
	}
	async runProcess(ctx) {
		for (const handler of this.processHandlers) try {
			ctx = await handler.process(ctx);
		} catch (err) {
			log.error({
				channel: this.channel,
				handler: handler.name,
				err
			}, "Process handler error");
			throw err;
		}
		return ctx;
	}
	async runDelivery(ctx, response) {
		for (const handler of this.deliveryHandlers) try {
			await handler.deliver(ctx, response);
		} catch (err) {
			log.error({
				channel: this.channel,
				handler: handler.name,
				err
			}, "Delivery handler error");
			throw err;
		}
	}
};
/**
* Create filter-self handler
*/
function createFilterSelfHandler(currentBotId) {
	return {
		name: "filterSelf",
		preflight: async (ctx) => {
			if (ctx.senderId === currentBotId) return null;
			return ctx;
		}
	};
}
/**
* Create filter-empty handler
*/
function createFilterEmptyHandler() {
	return {
		name: "filterEmpty",
		preflight: async (ctx) => {
			if (!(ctx.content?.trim() ?? "") && (!ctx.metadata.media || ctx.metadata.media.length === 0)) return null;
			return ctx;
		}
	};
}
/**
* Create filter-commands handler
*/
function createFilterCommandsHandler(commands) {
	const commandSet = new Set(commands.map((c) => c.toLowerCase()));
	return {
		name: "filterCommands",
		preflight: async (ctx) => {
			const firstWord = ctx.content?.split(/\s/)[0]?.toLowerCase() ?? "";
			if (firstWord.startsWith("/") && commandSet.has(firstWord.slice(1))) {
				ctx.metadata.isCommand = true;
				ctx.metadata.command = firstWord.slice(1);
			}
			return ctx;
		}
	};
}
/**
* Create standard preflight handlers
*/
function standardPreflightHandlers(botId) {
	return [
		createFilterSelfHandler(botId),
		createFilterEmptyHandler(),
		createFilterCommandsHandler([
			"start",
			"help",
			"status",
			"stop"
		])
	];
}
/**
* Prepends a per-turn `[YYYY-MM-DD HH:MM TZ]` prefix to inbound text so the model has
* a stable "now" without changing the system prompt (prompt-cache friendly).
*/
function createEnvelopeTimestampHandler(timezone) {
	return {
		name: "envelopeTimestamp",
		process: async (ctx) => {
			if (!ctx.content?.trim()) return ctx;
			const timestamp = formatEnvelopeTimestamp(timezone);
			return {
				...ctx,
				content: `[${timestamp}] ${ctx.content}`
			};
		}
	};
}
/**
* Create standard process handlers
*/
function standardProcessHandlers(timezone) {
	return [createEnvelopeTimestampHandler(timezone)];
}
/**
* Create message processing pipeline
*/
function createPipeline(params) {
	return new MessagePipeline({
		channel: params.channel,
		preflightHandlers: standardPreflightHandlers(params.botId),
		processHandlers: standardProcessHandlers(params.timezone),
		agentInvoke: params.agentInvoke,
		onError: params.onError
	});
}
//#endregion
export { MessagePipeline, createEnvelopeTimestampHandler, createFilterCommandsHandler, createFilterEmptyHandler, createFilterSelfHandler, createPipeline, standardPreflightHandlers, standardProcessHandlers };

//# sourceMappingURL=pipeline.js.map