import { createLogger } from "../../utils/logger/index.js";
import { init_logger } from "../../utils/logger.js";
import { shouldSilence, stripHeartbeatToken } from "../../heartbeat/tokens.js";
import { appendCronEventLines } from "../../heartbeat/event-prompt.js";
import { isWithinActiveHours } from "../../heartbeat/active-hours.js";
import { isHeartbeatContentEmpty } from "../../heartbeat/content-check.js";
import { createHeartbeatWake } from "../../heartbeat/wake.js";
import { resolveHeartbeatMdPath } from "../workspace-heartbeat-path.js";
import { readFile } from "fs/promises";
//#region src/gateway/heartbeat/service.ts
init_logger();
const log = createLogger("HeartbeatService");
const DEFAULT_PROMPT = "Read HEARTBEAT.md if it exists. Follow it strictly. If nothing needs attention, reply HEARTBEAT_OK.";
function mapConfigToRunner(cfg) {
	const h = cfg?.gateway?.heartbeat;
	return {
		enabled: h?.enabled ?? true,
		intervalMs: h?.intervalMs ?? 18e5,
		target: h?.target,
		targetChatId: h?.targetChatId,
		prompt: h?.prompt,
		ackMaxChars: h?.ackMaxChars,
		isolatedSession: h?.isolatedSession,
		activeHours: h?.activeHours
	};
}
/** Map persisted gateway config to runner options (gateway start / reload). */
function heartbeatRunnerConfigFromConfig(cfg) {
	return mapConfigToRunner(cfg);
}
var HeartbeatService = class {
	intervalId = null;
	wake;
	lastHeartbeatText = "";
	lastHeartbeatAt = 0;
	runnerConfig = null;
	constructor(deps) {
		this.deps = deps;
		this.wake = createHeartbeatWake((reasons) => this.runHeartbeatOnce(reasons));
	}
	start(config) {
		if (!config.enabled) {
			log.info("Heartbeat disabled");
			this.runnerConfig = null;
			return;
		}
		this.runnerConfig = config;
		log.info({ intervalMs: config.intervalMs }, "Heartbeat timer started (interval wake)");
		this.intervalId = setInterval(() => {
			this.wake.request({ reason: "interval" });
		}, config.intervalMs);
		this.intervalId.unref?.();
	}
	/** Cron, exec completion, manual triggers, etc. */
	requestNow(opts) {
		this.wake.request({ reason: opts?.reason ?? "manual" });
	}
	stop() {
		if (this.intervalId) {
			clearInterval(this.intervalId);
			this.intervalId = null;
		}
		this.wake.stop();
		this.runnerConfig = null;
		log.info("Heartbeat stopped");
	}
	updateConfig(config) {
		const mapped = mapConfigToRunner(config);
		this.stop();
		if (mapped.enabled) this.start(mapped);
		log.info("Heartbeat config updated");
	}
	isRunning() {
		return this.intervalId !== null;
	}
	async runHeartbeatOnce(reasons) {
		const reasonSummary = [...new Set(reasons)].join(", ") || "unknown";
		const cfg = this.runnerConfig;
		if (!cfg?.enabled) {
			log.debug({ reasons: reasonSummary }, "Heartbeat: skip (disabled)");
			return;
		}
		try {
			const metrics = await this.deps.cronService.getMetrics();
			log.trace({
				runningJobs: metrics.runningJobs,
				enabledJobs: metrics.enabledJobs
			}, "Heartbeat: cron metrics");
		} catch {}
		if (cfg.activeHours && !isWithinActiveHours(cfg.activeHours)) {
			log.debug({ reasons: reasonSummary }, "Heartbeat: skip (outside active hours)");
			return;
		}
		const heartbeatPath = resolveHeartbeatMdPath(this.deps.getConfig());
		if (!heartbeatPath) {
			log.debug({ reasons: reasonSummary }, "Heartbeat: skip (no HEARTBEAT path)");
			return;
		}
		let heartbeatContent;
		try {
			const raw = await readFile(heartbeatPath, "utf-8");
			if (isHeartbeatContentEmpty(raw)) {
				log.debug({
					path: heartbeatPath,
					reasons: reasonSummary
				}, "Heartbeat: skip (HEARTBEAT.md empty)");
				return;
			}
			heartbeatContent = raw.trim();
		} catch {
			log.debug({
				path: heartbeatPath,
				reasons: reasonSummary
			}, "Heartbeat: HEARTBEAT.md missing; continuing");
		}
		const sessionKey = cfg.isolatedSession ? `heartbeat:isolated:${Date.now()}` : "heartbeat:main";
		let basePrompt = (cfg.prompt?.trim() || DEFAULT_PROMPT).trim();
		if (heartbeatContent) basePrompt = `${basePrompt}\n\n---\nHEARTBEAT.md:\n${heartbeatContent}\n---`;
		basePrompt = appendCronEventLines(basePrompt, reasons);
		const prompt = `${basePrompt}\n\nCurrent time: ${(/* @__PURE__ */ new Date()).toISOString()}`;
		const ackMax = cfg.ackMaxChars ?? 300;
		log.debug({
			sessionKey,
			reasons: reasonSummary
		}, "Heartbeat: invoking agent");
		if (sessionKey === "heartbeat:main") try {
			await this.deps.sessionStore.saveMessages(sessionKey, []);
		} catch (err) {
			log.warn({
				err,
				sessionKey
			}, "Heartbeat: failed to reset main session transcript");
		}
		let reply;
		try {
			reply = await this.deps.agentService.turnDispatcher.processDirect(prompt, sessionKey);
		} catch (error) {
			log.error({ err: error }, "Heartbeat: agent call failed");
			return;
		}
		if (!reply?.trim()) {
			log.debug({ reasons: reasonSummary }, "Heartbeat: skip (empty model reply)");
			return;
		}
		if (shouldSilence(reply, ackMax)) {
			log.info({
				ackMax,
				replyChars: reply.length,
				reasons: reasonSummary
			}, "Heartbeat: not sent — silent (HEARTBEAT_OK / short ack)");
			return;
		}
		const { stripped } = stripHeartbeatToken(reply);
		const finalText = stripped || reply.trim();
		if (this.isDuplicate(finalText)) {
			log.info({
				finalTextChars: finalText.length,
				reasons: reasonSummary
			}, "Heartbeat: not sent — duplicate within 24h");
			return;
		}
		const target = cfg.target?.trim();
		const targetChatId = cfg.targetChatId?.trim();
		if (Boolean(target && targetChatId)) {
			await this.deps.messageBus.publishOutbound({
				channel: target,
				chat_id: targetChatId,
				content: finalText,
				type: "message"
			});
			log.info({
				reasons: reasonSummary,
				channel: target,
				chatId: targetChatId,
				contentChars: finalText.length
			}, "Heartbeat: sent — outbound queued");
		} else log.info({
			reasons: reasonSummary,
			finalTextChars: finalText.length
		}, "Heartbeat: not sent — no delivery target (set gateway.heartbeat.target + targetChatId)");
		this.lastHeartbeatText = finalText;
		this.lastHeartbeatAt = Date.now();
	}
	isDuplicate(text) {
		return text.trim() === this.lastHeartbeatText.trim() && Date.now() - this.lastHeartbeatAt < 1440 * 60 * 1e3;
	}
};
//#endregion
export { HeartbeatService, heartbeatRunnerConfigFromConfig };

//# sourceMappingURL=service.js.map