import { createLogger } from "../../../utils/logger/index.js";
import { init_logger } from "../../../utils/logger.js";
import { MEMORY_MD_FILENAME } from "./constants.js";
import { clamp01, compareCandidatesByScore, computeCandidateScore, extractPromotionMarkers, isContaminatedSnippet, isExpiredEntry, isoDay, readFileLines, resolveDeepDefaults, sliceRange, snippetHash } from "./utils.js";
import { loadDreamingStore, saveDreamingStore, withDreamingPromotionLock } from "./short-term-store.js";
import { emptyDeepPhaseSkipped, writeDreamingDeepLastRun } from "./last-run.js";
import fs from "node:fs/promises";
import path from "node:path";
//#region src/agent/memory/dreaming/deep-promotion.ts
init_logger();
const log = createLogger("Dreaming:Deep");
function markerForPromotion(key, hash) {
	return `<!-- xopc-memory-promotion key="${key}" hash="${hash}" -->`;
}
async function rehydrateSnippet(params) {
	const lines = await readFileLines(path.join(params.workspaceDir, params.candidate.path));
	if (!lines) return null;
	const startLine = Math.max(1, Math.floor(params.candidate.startLine));
	const endLine = Math.max(startLine, Math.floor(params.candidate.endLine));
	const snippet = sliceRange(lines, startLine, endLine);
	if (!snippet) return null;
	return {
		snippet,
		startLine,
		endLine
	};
}
function buildDeepLastRun(base) {
	const durationMs = Math.max(0, Date.now() - base.t0);
	return {
		version: 2,
		phase: "deep",
		runId: base.runId,
		startedAt: base.startedAt,
		finishedAt: base.finishedAt,
		durationMs,
		ok: base.ok,
		reason: base.reason,
		config: base.config,
		memoryPath: base.memoryPath,
		...base.errorMessage ? { errorMessage: base.errorMessage } : {},
		deep: base.deep
	};
}
async function runDreamingDeepPromotion(params) {
	const cfg = resolveDeepDefaults(params.config);
	const now = params.now ?? /* @__PURE__ */ new Date();
	const startedAt = now.toISOString();
	const runId = `${startedAt}:${process.pid}:${Math.random().toString(16).slice(2)}`;
	const memoryPath = path.join(params.workspaceDir, MEMORY_MD_FILENAME);
	const t0 = Date.now();
	const earlyExitReason = !cfg.enabled ? "dreaming disabled" : cfg.limit === 0 ? "dreaming limit=0" : null;
	if (earlyExitReason) {
		const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
		const empty = emptyDeepPhaseSkipped();
		await writeDreamingDeepLastRun({
			workspaceDir: params.workspaceDir,
			lastRun: buildDeepLastRun({
				runId,
				startedAt,
				finishedAt,
				t0,
				ok: true,
				reason: earlyExitReason,
				config: cfg,
				memoryPath,
				deep: {
					candidatesRanked: 0,
					applied: 0,
					skipped: empty
				}
			})
		}).catch(() => void 0);
		return {
			ok: true,
			reason: earlyExitReason,
			candidates: 0,
			applied: 0,
			memoryPath
		};
	}
	try {
		const result = await withDreamingPromotionLock(params.workspaceDir, async () => {
			const { store } = await loadDreamingStore({ workspaceDir: params.workspaceDir });
			const nowMs = now.getTime();
			const ranked = Object.values(store.entries ?? {}).filter((e) => {
				if (!e || typeof e !== "object") return false;
				if (e.promotedAt) return false;
				if (!e.path || !e.path.startsWith("memory/")) return false;
				if (e.recallCount < cfg.minRecallCount) return false;
				if ((e.queryHashes?.length ?? 0) < cfg.minUniqueQueries) return false;
				if (isExpiredEntry(e.lastRecalledAt, nowMs, cfg.maxAgeDays)) return false;
				return clamp01(e.recallCount > 0 ? e.totalScore / e.recallCount : 0) >= cfg.minScore;
			}).map((e) => {
				const { avgScore, score, recencyDecay } = computeCandidateScore(e, nowMs, cfg.recencyHalfLifeDays);
				return {
					...e,
					avgScore,
					score,
					recencyDecay
				};
			}).sort(compareCandidatesByScore).slice(0, cfg.limit);
			if (ranked.length === 0) return {
				ok: true,
				reason: "no eligible candidates",
				candidates: 0,
				applied: 0,
				memoryPath,
				skipped: emptyDeepPhaseSkipped()
			};
			const existing = await fs.readFile(memoryPath, "utf-8").catch((err) => {
				if (err?.code === "ENOENT") return "";
				throw err;
			});
			const existingMarkers = extractPromotionMarkers(existing);
			const appliedCandidates = [];
			const skipped = emptyDeepPhaseSkipped();
			for (const candidate of ranked) {
				if (existingMarkers.keys.has(candidate.key)) {
					skipped.alreadyPromotedKey += 1;
					const entry = store.entries[candidate.key];
					if (entry && !entry.promotedAt) entry.promotedAt = now.toISOString();
					continue;
				}
				const rehydrated = await rehydrateSnippet({
					workspaceDir: params.workspaceDir,
					candidate
				});
				if (!rehydrated) {
					skipped.rehydrateFailed += 1;
					continue;
				}
				if (isContaminatedSnippet(rehydrated.snippet)) {
					skipped.contaminated += 1;
					continue;
				}
				const hash = snippetHash(rehydrated.snippet);
				if (existingMarkers.hashes.has(hash)) {
					skipped.hashDuplicate += 1;
					const entry = store.entries[candidate.key];
					if (entry && !entry.promotedAt) entry.promotedAt = now.toISOString();
					continue;
				}
				appliedCandidates.push({
					key: candidate.key,
					hash,
					snippet: rehydrated.snippet,
					path: candidate.path,
					startLine: rehydrated.startLine,
					endLine: rehydrated.endLine,
					score: candidate.score,
					recallCount: candidate.recallCount,
					avgScore: candidate.avgScore
				});
			}
			if (appliedCandidates.length === 0) {
				await saveDreamingStore({
					workspaceDir: params.workspaceDir,
					store
				});
				return {
					ok: true,
					reason: "candidates were stale or already applied",
					candidates: ranked.length,
					applied: 0,
					memoryPath,
					skipped
				};
			}
			const day = isoDay(now);
			const header = existing.trim().length > 0 ? "" : "# Long-Term Memory\n\n";
			const sectionLines = [
				"",
				`## Promoted From Short-Term Memory (${day})`,
				""
			];
			for (const c of appliedCandidates) {
				const src = `${c.path}:${c.startLine}-${c.endLine}`;
				sectionLines.push(markerForPromotion(c.key, c.hash));
				sectionLines.push(`- ${c.snippet} [score=${c.score.toFixed(3)} recalls=${c.recallCount} avg=${c.avgScore.toFixed(3)} source=${src}]`);
			}
			sectionLines.push("");
			const next = `${header}${existing.endsWith("\n") || existing.length === 0 ? existing : `${existing}\n`}${sectionLines.join("\n")}`;
			await fs.writeFile(memoryPath, next, "utf-8");
			const nowIso = now.toISOString();
			for (const c of appliedCandidates) {
				const entry = store.entries[c.key];
				if (entry) {
					entry.promotedAt = nowIso;
					entry.snippet = c.snippet;
					entry.startLine = c.startLine;
					entry.endLine = c.endLine;
				}
			}
			store.updatedAt = nowIso;
			await saveDreamingStore({
				workspaceDir: params.workspaceDir,
				store
			});
			log.info({
				workspaceDir: params.workspaceDir,
				candidates: ranked.length,
				applied: appliedCandidates.length
			}, "Dreaming deep promotion complete");
			return {
				ok: true,
				reason: "applied promotions",
				candidates: ranked.length,
				applied: appliedCandidates.length,
				memoryPath,
				skipped
			};
		});
		const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
		const empty = emptyDeepPhaseSkipped();
		const resultSkipped = "skipped" in result ? result.skipped : empty;
		await writeDreamingDeepLastRun({
			workspaceDir: params.workspaceDir,
			lastRun: buildDeepLastRun({
				runId,
				startedAt,
				finishedAt,
				t0,
				ok: result.ok,
				reason: result.reason,
				config: cfg,
				memoryPath: result.memoryPath,
				deep: {
					candidatesRanked: result.candidates,
					applied: result.applied,
					skipped: resultSkipped
				}
			})
		}).catch(() => void 0);
		return {
			ok: result.ok,
			reason: result.reason,
			candidates: result.candidates,
			applied: result.applied,
			memoryPath: result.memoryPath
		};
	} catch (err) {
		const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
		const em = err instanceof Error ? err.message : String(err);
		const z = emptyDeepPhaseSkipped();
		await writeDreamingDeepLastRun({
			workspaceDir: params.workspaceDir,
			lastRun: buildDeepLastRun({
				runId,
				startedAt,
				finishedAt,
				t0,
				ok: false,
				reason: "error",
				config: cfg,
				memoryPath,
				errorMessage: em,
				deep: {
					candidatesRanked: 0,
					applied: 0,
					skipped: z
				}
			})
		}).catch(() => void 0);
		throw err;
	}
}
//#endregion
export { runDreamingDeepPromotion };

//# sourceMappingURL=deep-promotion.js.map