import { DEFAULT_DEEP_CRON, DIVERSITY_WEIGHT, MS_PER_DAY, REINFORCEMENT_WEIGHT } from "./constants.js";
import { createHash } from "node:crypto";
import fs from "node:fs/promises";
import path from "node:path";
//#region src/agent/memory/dreaming/utils.ts
/** Normalize a workspace-relative memory path: forward slashes, no odd ../ escapes at start. */
function normalizeMemoryPath(rel) {
	if (typeof rel !== "string" || !rel.trim()) return "";
	const s = rel.trim().replace(/\\/g, "/");
	return path.posix.normalize(s).replace(/^(\.\/)+/, "").replace(/\/+/g, "/");
}
/**
* Stable id for a short-term store entry (path + line range).
* Format: `{normalizedPath}#{start}-{end}` (1-based, inclusive end).
*/
function buildEntryKey(parts) {
	const p = normalizeMemoryPath(parts.path);
	const a = Math.max(1, Math.floor(parts.startLine));
	return `${p}#${a}-${Math.max(a, Math.floor(parts.endLine))}`;
}
/** YYYY-MM-DD in local time (for daily `memory/YYYY-MM-DD.md` names). */
function isoDay(d) {
	return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
}
function clamp01(value) {
	if (!Number.isFinite(value)) return 0;
	return Math.max(0, Math.min(1, value));
}
function toPositiveInt(value, fallback) {
	const num = typeof value === "string" ? Number(value) : Number(value);
	if (!Number.isFinite(num)) return fallback;
	const floored = Math.floor(num);
	return floored > 0 ? floored : fallback;
}
function toNonNegInt(value, fallback) {
	const num = typeof value === "string" ? Number(value) : Number(value);
	if (!Number.isFinite(num)) return fallback;
	const floored = Math.floor(num);
	return floored >= 0 ? floored : fallback;
}
function clampScore(value, fallback) {
	if (!Number.isFinite(value)) return fallback;
	return Math.max(0, Math.min(1, value));
}
function trimmedCron(value, fallback) {
	return typeof value === "string" && value.trim() ? value.trim() : fallback;
}
/** Fills in deep-phase defaults; mirrors `resolveDreamingConfig` deep object when only partial overrides are given. */
function resolveDeepDefaults(overrides) {
	return {
		enabled: overrides?.enabled !== false,
		cron: trimmedCron(overrides?.cron, DEFAULT_DEEP_CRON),
		minScore: clampScore(Number(overrides?.minScore), .8),
		minRecallCount: toPositiveInt(overrides?.minRecallCount, 3),
		minUniqueQueries: toPositiveInt(overrides?.minUniqueQueries, 3),
		limit: toNonNegInt(overrides?.limit, 10),
		recencyHalfLifeDays: toPositiveInt(overrides?.recencyHalfLifeDays, 14),
		maxAgeDays: toPositiveInt(overrides?.maxAgeDays, 30)
	};
}
function computeCandidateScore(entry, nowMs, recencyHalfLifeDays) {
	const recall = Math.max(0, entry.recallCount);
	const avgScore = clamp01(recall > 0 ? entry.totalScore / recall : 0);
	let recencyDecay = 1;
	if (entry.lastRecalledAt) {
		const last = Date.parse(entry.lastRecalledAt);
		if (Number.isFinite(last)) recencyDecay = .5 ** (Math.max(0, (nowMs - last) / MS_PER_DAY) / Math.max(.1, recencyHalfLifeDays));
	}
	const logRecall = Math.log(1 + recall);
	const reinforcement = REINFORCEMENT_WEIGHT * Math.min(logRecall, 3);
	const qCount = entry.queryHashes?.length ?? 0;
	const uniqueQueryTerm = Math.min(1, qCount / 6) * DIVERSITY_WEIGHT;
	const signalDiversity = [
		entry.dailyCount,
		entry.groundedCount,
		entry.lightHits,
		entry.remHits
	].filter((n) => n > 0).length / 4 * DIVERSITY_WEIGHT / 2;
	return {
		avgScore,
		score: clamp01(avgScore * recencyDecay + reinforcement * .25 + uniqueQueryTerm + signalDiversity),
		recencyDecay
	};
}
function compareCandidatesByScore(a, b) {
	if (b.score !== a.score) return b.score - a.score;
	return a.key.localeCompare(b.key);
}
async function readFileLines(fullPath) {
	try {
		return (await fs.readFile(fullPath, "utf-8")).split(/\r?\n/);
	} catch (err) {
		if (err?.code === "ENOENT") return null;
		throw err;
	}
}
/** 1-based inclusive line range. Joins with newlines. */
function sliceRange(lines, startLine, endLine) {
	const a = Math.max(1, Math.floor(startLine));
	const b = Math.max(a, Math.floor(endLine));
	return lines.slice(a - 1, b).join("\n").trimEnd();
}
function isExpiredEntry(lastRecalledAt, nowMs, maxAgeDays) {
	if (!lastRecalledAt) return true;
	const t = Date.parse(lastRecalledAt);
	if (!Number.isFinite(t)) return true;
	return (nowMs - t) / MS_PER_DAY > maxAgeDays;
}
/** Reject empty, tiny, or absurdly long snippets; blocks obvious “tool error” text. */
function isContaminatedSnippet(snippet) {
	const s = snippet.trim();
	if (s.length < 3) return true;
	if (s.length > 12e3) return true;
	if (!/\p{L}/u.test(s) && !/\d/.test(s)) return true;
	return false;
}
function normalizeSnippetForHash(text) {
	return text.toLowerCase().replace(/\s+/g, " ").replace(/['"`]/g, "").trim();
}
function snippetHash(text) {
	const n = normalizeSnippetForHash(text);
	return createHash("sha1").update(n, "utf8").digest("hex").slice(0, 20);
}
function extractPromotionMarkers(markdown) {
	const keys = /* @__PURE__ */ new Set();
	const hashes = /* @__PURE__ */ new Set();
	let m;
	const re = /<!--\s*xopc-memory-promotion\s+key="([^"]*)"\s+hash="([^"]*)"\s*-->/g;
	while ((m = re.exec(markdown)) !== null) {
		if (m[1]) keys.add(m[1]);
		if (m[2]) hashes.add(m[2]);
	}
	return {
		keys,
		hashes
	};
}
//#endregion
export { buildEntryKey, clamp01, compareCandidatesByScore, computeCandidateScore, extractPromotionMarkers, isContaminatedSnippet, isExpiredEntry, isoDay, normalizeMemoryPath, normalizeSnippetForHash, readFileLines, resolveDeepDefaults, sliceRange, snippetHash };

//# sourceMappingURL=utils.js.map