// SF Memory Tools — Phase 1 executors for log_reasoning, memory_search, memory_graph
//
// These executors back the three memory-layer tools the LLM can call at any
// point in a session. They build on the existing `memory-store.ts` layer
// (SQLite memories table) and degrade gracefully when the DB is unavailable.
import { traverseGraph } from "../memory-relations.js";
import {
	createMemory,
	getActiveMemoriesRanked,
	reinforceMemory,
} from "../memory-store.js";
import { computeStaticMemoryScore, isDbAvailable } from "../sf-db.js";

function dbUnavailable(operation) {
	return {
		content: [
			{
				type: "text",
				text: "Error: SF database is not available. Memory tools require an initialized .sf/ project.",
			},
		],
		details: { operation, error: "db_unavailable" },
		isError: true,
	};
}
const VALID_CATEGORIES = new Set([
	"architecture",
	"convention",
	"gotcha",
	"preference",
	"environment",
	"pattern",
]);
export function executeMemoryCapture(params) {
	if (!isDbAvailable()) return dbUnavailable("memory_capture");
	const category = (params.category ?? "").trim().toLowerCase();
	const content = (params.content ?? "").trim();
	if (!category || !content) {
		return {
			content: [
				{ type: "text", text: "Error: category and content are required." },
			],
			details: { operation: "memory_capture", error: "missing_fields" },
			isError: true,
		};
	}
	if (!VALID_CATEGORIES.has(category)) {
		return {
			content: [
				{
					type: "text",
					text: `Error: invalid category "${category}". Must be one of: ${[...VALID_CATEGORIES].join(", ")}.`,
				},
			],
			details: { operation: "memory_capture", error: "invalid_category" },
			isError: true,
		};
	}
	const confidence = clampConfidence(params.confidence);
	const id = createMemory({ category, content, confidence });
	if (!id) {
		return {
			content: [{ type: "text", text: "Error: failed to create memory." }],
			details: { operation: "memory_capture", error: "create_failed" },
			isError: true,
		};
	}
	return {
		content: [
			{ type: "text", text: `Captured ${id} (${category}): ${content}` },
		],
		details: { operation: "memory_capture", id, category, confidence },
	};
}
function clampConfidence(value) {
	if (typeof value !== "number" || !Number.isFinite(value)) return 0.8;
	if (value < 0.1) return 0.1;
	if (value > 0.99) return 0.99;
	return value;
}
export function executeMemoryQuery(params) {
	if (!isDbAvailable()) return dbUnavailable("memory_search");
	const query = (params.query ?? "").trim().toLowerCase();
	const k = clampTopK(params.k, 10);
	const category = params.category?.trim().toLowerCase() || undefined;
	try {
		const candidates = getActiveMemoriesRanked(200);
		const filtered = candidates.filter((m) => {
			if (category && m.category.toLowerCase() !== category) return false;
			if (
				query &&
				!m.content.toLowerCase().includes(query) &&
				!m.category.toLowerCase().includes(query)
			)
				return false;
			return true;
		});
		const ranked = filtered.slice(0, k).map((memory) => ({
			memory,
			score: computeStaticMemoryScore(memory.confidence, memory.hit_count),
		}));
		const hits = ranked.map((r) => ({
			id: r.memory.id,
			category: r.memory.category,
			content: r.memory.content,
			confidence: r.memory.confidence,
			hit_count: r.memory.hit_count,
			score: r.score,
		}));
		if (params.reinforce_hits) {
			for (const h of hits) reinforceMemory(h.id);
		}
		const summary =
			hits.length === 0
				? "No matching memories."
				: hits
						.map((h) => `- [${h.id}] (${h.category}) ${h.content}`)
						.join("\n");
		return {
			content: [{ type: "text", text: summary }],
			details: {
				operation: "memory_search",
				query,
				k,
				returned: hits.length,
				hits,
			},
		};
	} catch (err) {
		return {
			content: [
				{
					type: "text",
					text: `Error: memory query failed: ${err.message}`,
				},
			],
			details: { operation: "memory_search", error: err.message },
			isError: true,
		};
	}
}
function clampTopK(value, fallback) {
	if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
	if (value < 1) return 1;
	if (value > 50) return 50;
	return Math.floor(value);
}
export function executeSfGraph(params) {
	if (!isDbAvailable()) return dbUnavailable("memory_graph");
	if (params.mode === "build") {
		return {
			content: [
				{
					type: "text",
					text:
						"memory_graph build acknowledged. Graph edges are populated incrementally by memory " +
						"extraction (including LINK actions). Use `/memory extract <SRC-...>` to trigger " +
						"extraction against a specific ingested source.",
				},
			],
			details: { operation: "memory_graph", mode: "build", built: 0 },
		};
	}
	if (params.mode !== "query") {
		return {
			content: [
				{
					type: "text",
					text: `Error: unknown mode "${params.mode}". Must be "build" or "query".`,
				},
			],
			details: { operation: "memory_graph", error: "invalid_mode" },
			isError: true,
		};
	}
	const memoryId = params.memoryId?.trim();
	if (!memoryId) {
		return {
			content: [
				{ type: "text", text: "Error: memoryId is required for mode=query." },
			],
			details: { operation: "memory_graph", error: "missing_memory_id" },
			isError: true,
		};
	}
	try {
		const graph = traverseGraph(memoryId, clampDepth(params.depth));
		const rel = params.rel?.trim().toLowerCase() || null;
		const edges = rel ? graph.edges.filter((e) => e.rel === rel) : graph.edges;
		const relevantIds = new Set([memoryId]);
		for (const e of edges) {
			relevantIds.add(e.from);
			relevantIds.add(e.to);
		}
		const nodes = graph.nodes.filter((n) => relevantIds.has(n.id));
		if (nodes.length === 0) {
			return {
				content: [
					{ type: "text", text: `No memory found with id ${memoryId}.` },
				],
				details: {
					operation: "memory_graph",
					mode: "query",
					memoryId,
					nodes: [],
					edges: [],
				},
			};
		}
		const summary = [
			`Memory ${memoryId} — ${nodes.length} node(s), ${edges.length} edge(s).`,
			...nodes.map((n) => `  [${n.id}] (${n.category}) ${n.content}`),
			...edges.map((e) => `  ${e.from} --${e.rel}-> ${e.to}`),
		].join("\n");
		return {
			content: [{ type: "text", text: summary }],
			details: {
				operation: "memory_graph",
				mode: "query",
				memoryId,
				nodes: nodes.map((n) => ({
					id: n.id,
					category: n.category,
					content: n.content,
				})),
				edges: edges.map((e) => ({ from: e.from, to: e.to, rel: e.rel })),
			},
		};
	} catch (err) {
		return {
			content: [
				{
					type: "text",
					text: `Error: graph query failed: ${err.message}`,
				},
			],
			details: { operation: "memory_graph", error: err.message },
			isError: true,
		};
	}
}
function clampDepth(value) {
	if (typeof value !== "number" || !Number.isFinite(value)) return 1;
	if (value < 0) return 0;
	if (value > 5) return 5;
	return Math.floor(value);
}
