// SF Compaction Snapshot — writes a ≤2 KB markdown digest of durable
// project state before the session context is compacted. On resume, an
// agent can `resume_agent` (or Read .sf/last-snapshot.md) to re-orient
// without re-deriving the same memories.
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { resolve } from "node:path";
import { listExecHistory } from "./exec-history.js";
import { getActiveMemoriesRanked } from "./memory-store.js";
export const DEFAULT_SNAPSHOT_BYTES = 2048;
export const SNAPSHOT_FILENAME = "last-snapshot.md";
/**
 * Build a priority-tiered markdown snapshot. Pure — no I/O. Tiers:
 *   1. Active context (if any)
 *   2. Top memories by rank
 *   3. Recent exec runs (failures highlighted)
 */
export function buildSnapshot(sources, opts = {}) {
	const maxBytes = opts.maxBytes ?? DEFAULT_SNAPSHOT_BYTES;
	const maxMemories = opts.maxMemories ?? 6;
	const maxExec = opts.maxExec ?? 5;
	const lines = [];
	lines.push(`# SF context snapshot (${sources.generatedAt.toISOString()})`);
	lines.push("");
	if (sources.activeContext && sources.activeContext.trim().length > 0) {
		lines.push("## Active context");
		lines.push(sources.activeContext.trim());
		lines.push("");
	}
	const memories = sources.memories.slice(0, maxMemories);
	if (memories.length > 0) {
		lines.push("## Top project memories");
		for (const memory of memories) {
			lines.push(
				`- [${memory.id}] (${memory.category}) ${memory.content.trim()}`,
			);
		}
		lines.push("");
	}
	const exec = sources.execHistory.slice(0, maxExec);
	if (exec.length > 0) {
		lines.push("## Recent exec history");
		for (const entry of exec) {
			const status = entry.timed_out
				? "timeout"
				: entry.exit_code === null
					? "exit:null"
					: `exit:${entry.exit_code}`;
			const purpose = entry.purpose ? ` — ${entry.purpose}` : "";
			lines.push(`- [${entry.id}] ${entry.runtime} ${status}${purpose}`);
		}
		lines.push("");
	}
	if (memories.length === 0 && exec.length === 0 && !sources.activeContext) {
		lines.push(
			"_No durable memories, active context, or exec history to surface._",
		);
	}
	return enforceByteCap(lines.join("\n").trimEnd(), maxBytes);
}
function enforceByteCap(input, maxBytes) {
	if (Buffer.byteLength(input, "utf-8") <= maxBytes) return input;
	const marker = "\n…[truncated]";
	const markerBytes = Buffer.byteLength(marker, "utf-8");
	const budget = Math.max(0, maxBytes - markerBytes);
	const buf = Buffer.from(input, "utf-8").subarray(0, budget);
	return `${buf.toString("utf-8")}${marker}`;
}
export function writeCompactionSnapshot(baseDir, opts = {}) {
	const memories = safeGetMemories();
	const execHistory = safeListExec(baseDir);
	const content = buildSnapshot(
		{
			memories,
			execHistory,
			generatedAt: (opts.now ?? (() => new Date()))(),
			activeContext: opts.activeContext ?? null,
		},
		opts,
	);
	const sfDir = resolve(baseDir, ".sf");
	if (!existsSync(sfDir)) mkdirSync(sfDir, { recursive: true });
	const path = resolve(sfDir, SNAPSHOT_FILENAME);
	const finalContent = `${content}\n`;
	writeFileSync(path, finalContent, "utf-8");
	return {
		path,
		bytes: Buffer.byteLength(finalContent, "utf-8"),
		memories: memories.length,
		execRuns: execHistory.length,
	};
}
export function readCompactionSnapshot(baseDir) {
	const path = resolve(baseDir, ".sf", SNAPSHOT_FILENAME);
	if (!existsSync(path)) return null;
	try {
		return readFileSync(path, "utf-8");
	} catch {
		return null;
	}
}
function safeGetMemories() {
	try {
		return getActiveMemoriesRanked(12);
	} catch {
		return [];
	}
}
function safeListExec(baseDir) {
	try {
		return listExecHistory(baseDir);
	} catch {
		return [];
	}
}
