import { createLogger } from "../../../utils/logger/index.js";
import { init_logger } from "../../../utils/logger.js";
import { SHORT_TERM_PROMOTION_LOCK_RELATIVE, SHORT_TERM_RECALL_STORE_RELATIVE } from "./constants.js";
import { buildEntryKey, clamp01, isoDay, normalizeMemoryPath } from "./utils.js";
import { createHash, randomUUID } from "node:crypto";
import fs from "node:fs/promises";
import path from "node:path";
//#region src/agent/memory/dreaming/short-term-store.ts
init_logger();
const log = createLogger("Dreaming:Store");
function isDailyWorkspaceMemoryPath(rel) {
	const p = normalizeMemoryPath(rel);
	return /^memory\/\d{4}-\d{2}-\d{2}\.md$/i.test(p);
}
function hashQuery(query) {
	return createHash("sha1").update(query.trim().toLowerCase()).digest("hex").slice(0, 12);
}
function mergeRecentDistinct(existing, nextValue, limit) {
	const seen = /* @__PURE__ */ new Set();
	const normalized = existing.filter((v) => typeof v === "string").map((v) => v.trim()).filter((v) => v.length > 0 && !seen.has(v) && (seen.add(v), true));
	if (nextValue && !normalized.includes(nextValue)) normalized.push(nextValue);
	return normalized.length <= limit ? normalized : normalized.slice(-limit);
}
function mergeQueryHashes(existing, nextHash) {
	return mergeRecentDistinct(existing, nextHash, 32);
}
function emptyStore(nowIso) {
	return {
		version: 1,
		updatedAt: nowIso,
		entries: {}
	};
}
async function ensureDreamDir(workspaceDir) {
	const dir = path.join(workspaceDir, path.dirname(SHORT_TERM_RECALL_STORE_RELATIVE));
	await fs.mkdir(dir, { recursive: true });
	return dir;
}
async function readStore(workspaceDir, nowIso) {
	const storePath = path.join(workspaceDir, SHORT_TERM_RECALL_STORE_RELATIVE);
	try {
		const raw = await fs.readFile(storePath, "utf-8");
		const parsed = JSON.parse(raw);
		if (!parsed || typeof parsed !== "object") return emptyStore(nowIso);
		const rec = parsed;
		if (rec.version !== 1 || !rec.entries || typeof rec.entries !== "object") return emptyStore(nowIso);
		return {
			version: 1,
			updatedAt: typeof rec.updatedAt === "string" ? rec.updatedAt : nowIso,
			entries: rec.entries
		};
	} catch (err) {
		if (err?.code === "ENOENT") return emptyStore(nowIso);
		log.warn({
			err,
			workspaceDir
		}, "Failed to read dreaming store; resetting");
		return emptyStore(nowIso);
	}
}
async function writeStore(workspaceDir, store) {
	await ensureDreamDir(workspaceDir);
	const storePath = path.join(workspaceDir, SHORT_TERM_RECALL_STORE_RELATIVE);
	const tmp = `${storePath}.${process.pid}.${Date.now()}.${randomUUID()}.tmp`;
	await fs.writeFile(tmp, `${JSON.stringify(store, null, 2)}\n`, "utf-8");
	await fs.rename(tmp, storePath);
}
async function recordDreamingRecalls(params) {
	const workspaceDir = params.workspaceDir.trim();
	const query = params.query.trim();
	if (!workspaceDir || !query) return {
		recorded: 0,
		skipped: params.matches.length,
		storePath: SHORT_TERM_RECALL_STORE_RELATIVE
	};
	const now = params.now ?? /* @__PURE__ */ new Date();
	const nowIso = now.toISOString();
	const dayBucket = isoDay(now);
	const qHash = hashQuery(query);
	const store = await readStore(workspaceDir, nowIso);
	let recorded = 0;
	let skipped = 0;
	for (const match of params.matches) {
		const file = typeof match?.file === "string" ? match.file : "";
		if (!file || !isDailyWorkspaceMemoryPath(file)) {
			skipped += 1;
			continue;
		}
		const lines = typeof match?.lines === "string" ? match.lines.trim() : "";
		const lineNumbers = Array.isArray(match?.lineNumbers) ? match.lineNumbers : [];
		const startLine = Math.max(1, Math.min(...lineNumbers.filter((n) => Number.isFinite(n) && n > 0)));
		const endLine = Math.max(startLine, Math.max(...lineNumbers.filter((n) => Number.isFinite(n) && n > 0)));
		if (!Number.isFinite(startLine) || !Number.isFinite(endLine)) {
			skipped += 1;
			continue;
		}
		const score = clamp01(Number(match.score));
		const key = buildEntryKey({
			path: file,
			startLine,
			endLine
		});
		const existing = store.entries[key];
		const snippet = lines.length > 0 ? lines.slice(0, 360) : file;
		const next = existing ? {
			...existing,
			snippet,
			recallCount: Math.max(0, Math.floor(existing.recallCount + 1)),
			dailyCount: existing.dailyCount ?? 0,
			groundedCount: existing.groundedCount ?? 0,
			lightHits: existing.lightHits ?? 0,
			remHits: existing.remHits ?? 0,
			phaseHitCount: existing.phaseHitCount ?? 0,
			totalSignalCount: Math.max(0, (existing.totalSignalCount ?? existing.recallCount ?? 0) + 1),
			totalScore: Math.max(0, existing.totalScore + score),
			maxScore: Math.max(existing.maxScore, score),
			queryHashes: mergeQueryHashes(existing.queryHashes ?? [], qHash),
			recallDays: mergeRecentDistinct(existing.recallDays ?? [], dayBucket, 16),
			lastRecalledAt: nowIso
		} : {
			key,
			path: normalizeMemoryPath(file),
			startLine,
			endLine,
			snippet,
			recallCount: 1,
			dailyCount: 0,
			groundedCount: 0,
			lightHits: 0,
			remHits: 0,
			phaseHitCount: 0,
			totalSignalCount: 1,
			totalScore: score,
			maxScore: score,
			queryHashes: [qHash],
			recallDays: [dayBucket],
			firstRecalledAt: nowIso,
			lastRecalledAt: nowIso
		};
		store.entries[key] = next;
		recorded += 1;
	}
	if (recorded > 0) {
		store.updatedAt = nowIso;
		await writeStore(workspaceDir, store);
	}
	return {
		recorded,
		skipped,
		storePath: SHORT_TERM_RECALL_STORE_RELATIVE
	};
}
async function withDreamingPromotionLock(workspaceDir, task) {
	const lockPath = path.join(workspaceDir, SHORT_TERM_PROMOTION_LOCK_RELATIVE);
	await ensureDreamDir(workspaceDir);
	const startedAt = Date.now();
	const timeoutMs = 1e4;
	const retryDelayMs = 50;
	while (true) try {
		const handle = await fs.open(lockPath, "wx");
		await handle.writeFile(`${process.pid}:${Date.now()}\n`, "utf-8").catch(() => void 0);
		try {
			return await task();
		} finally {
			await handle.close().catch(() => void 0);
			await fs.unlink(lockPath).catch(() => void 0);
		}
	} catch (err) {
		if (err?.code !== "EEXIST") throw err;
		if (Date.now() - startedAt >= timeoutMs) throw new Error(`Timed out waiting for dreaming promotion lock at ${lockPath}`);
		await new Promise((r) => setTimeout(r, retryDelayMs));
	}
}
async function loadDreamingStore(params) {
	const nowIso = (/* @__PURE__ */ new Date()).toISOString();
	return {
		store: await readStore(params.workspaceDir, nowIso),
		storePath: SHORT_TERM_RECALL_STORE_RELATIVE
	};
}
async function saveDreamingStore(params) {
	await writeStore(params.workspaceDir, params.store);
}
/**
* Increment a phase-specific signal counter on an existing store entry.
* Also bumps `phaseHitCount` and `totalSignalCount`.
* Returns `true` if the entry existed and was updated.
*/
function bumpEntryPhaseSignal(entry, field, increment = 1) {
	entry[field] = (entry[field] ?? 0) + increment;
	entry.phaseHitCount = (entry.phaseHitCount ?? 0) + increment;
	entry.totalSignalCount = (entry.totalSignalCount ?? 0) + increment;
}
//#endregion
export { bumpEntryPhaseSignal, loadDreamingStore, recordDreamingRecalls, saveDreamingStore, withDreamingPromotionLock };

//# sourceMappingURL=short-term-store.js.map