/**
 * Memory Repository — Structured fact/snippet/note storage for SF units.
 *
 * Purpose: Provide RA.Aid-style structured memory with DB persistence.
 * Stores key facts, code snippets, research notes, and human inputs per session.
 *
 * Consumer: research units, planning units, execute-task units, /memory command.
 *
 * Design:
 * - SQLite-backed with JSONB-like flexibility
 * - Session-scoped: memories belong to a session
 * - Unit-scoped: memories can be tagged with unitId for filtering
 * - Auto-summarization when count exceeds threshold
 * - Deduplication via content hash
 */

import { createHash } from "node:crypto";
import { debugLog } from "./debug-logger.js";
import { isDbAvailable } from "./sf-db.js";
import { logWarning } from "./workflow-logger.js";

const MEMORY_TABLE = "sf_memory";
const AUTO_SUMMARIZE_THRESHOLD = 50;

/**
 * Memory types matching RA.Aid's structured memory model.
 */
export const MEMORY_TYPES = {
	KEY_FACT: "key_fact", // Important discovery, rule, or finding
	KEY_SNIPPET: "key_snippet", // Code snippet with file location
	RESEARCH_NOTE: "research_note", // Detailed research findings
	HUMAN_INPUT: "human_input", // User-provided input or clarification
	WORK_LOG: "work_log", // Unit completion or milestone event
	DECISION: "decision", // Architectural or design decision
};

/**
 * Ensure the memory table exists.
 */
export function ensureMemoryTable(db) {
	if (!db) return false;
	try {
		db.exec(`
			CREATE TABLE IF NOT EXISTS ${MEMORY_TABLE} (
				id INTEGER PRIMARY KEY AUTOINCREMENT,
				session_id TEXT NOT NULL,
				unit_id TEXT,
				type TEXT NOT NULL CHECK(type IN ('key_fact', 'key_snippet', 'research_note', 'human_input', 'work_log', 'decision')),
				content TEXT NOT NULL,
				metadata TEXT, -- JSON: {filepath, line_number, description, source, tags}
				content_hash TEXT NOT NULL,
				created_at TEXT NOT NULL DEFAULT (datetime('now')),
				updated_at TEXT NOT NULL DEFAULT (datetime('now'))
			);
			CREATE INDEX IF NOT EXISTS idx_memory_session ON ${MEMORY_TABLE}(session_id);
			CREATE INDEX IF NOT EXISTS idx_memory_type ON ${MEMORY_TABLE}(type);
			CREATE INDEX IF NOT EXISTS idx_memory_unit ON ${MEMORY_TABLE}(unit_id);
			CREATE INDEX IF NOT EXISTS idx_memory_hash ON ${MEMORY_TABLE}(content_hash);
		`);
		return true;
	} catch (err) {
		logWarning("memory", "Failed to ensure memory table", {
			error: String(err),
		});
		return false;
	}
}

function computeHash(content) {
	return createHash("sha256").update(content).digest("hex").slice(0, 16);
}

/**
 * Store a memory entry.
 *
 * @param {object} params
 * @param {string} params.sessionId — required
 * @param {string} [params.unitId] — optional unit tag
 * @param {string} params.type — MEMORY_TYPES value
 * @param {string} params.content — memory content
 * @param {object} [params.metadata] — optional structured metadata
 * @param {object} [params.db] — database connection
 * @returns {{id: number, created: boolean, deduped: boolean}|null}
 */
export function storeMemory({
	sessionId,
	unitId,
	type,
	content,
	metadata,
	db,
}) {
	if (!sessionId || !type || !content) {
		logWarning("memory", "storeMemory missing required fields", {
			sessionId,
			type,
		});
		return null;
	}
	if (!Object.values(MEMORY_TYPES).includes(type)) {
		logWarning("memory", "Invalid memory type", { type });
		return null;
	}

	const hash = computeHash(content);
	const dbConn = db || (isDbAvailable() ? require("./sf-db.js").getDb() : null);
	if (!dbConn) return null;

	ensureMemoryTable(dbConn);

	try {
		// Deduplication: check for existing hash in same session
		const existing = dbConn
			.prepare(
				`SELECT id FROM ${MEMORY_TABLE} WHERE session_id = ? AND content_hash = ? LIMIT 1`,
			)
			.get(sessionId, hash);

		if (existing) {
			debugLog("memory-dedup", { sessionId, type, hash: hash.slice(0, 8) });
			return { id: existing.id, created: false, deduped: true };
		}

		const result = dbConn
			.prepare(
				`INSERT INTO ${MEMORY_TABLE} (session_id, unit_id, type, content, metadata, content_hash)
			 VALUES (?, ?, ?, ?, ?, ?)`,
			)
			.run(
				sessionId,
				unitId || null,
				type,
				content,
				metadata ? JSON.stringify(metadata) : null,
				hash,
			);

		// Auto-summarize check
		maybeAutoSummarize(dbConn, sessionId);

		return {
			id: Number(result.lastInsertRowid),
			created: true,
			deduped: false,
		};
	} catch (err) {
		logWarning("memory", "storeMemory failed", {
			error: String(err),
			sessionId,
			type,
		});
		return null;
	}
}

/**
 * Retrieve memories with optional filtering.
 *
 * @param {object} params
 * @param {string} params.sessionId — required
 * @param {string} [params.type] — filter by memory type
 * @param {string} [params.unitId] — filter by unit
 * @param {string} [params.query] — substring search in content
 * @param {number} [params.limit=50] — max results
 * @param {object} [params.db]
 * @returns {Array<{id, sessionId, unitId, type, content, metadata, createdAt}>}
 */
export function getMemories({
	sessionId,
	type,
	unitId,
	query,
	limit = 50,
	db,
}) {
	const dbConn = db || (isDbAvailable() ? require("./sf-db.js").getDb() : null);
	if (!dbConn) return [];

	ensureMemoryTable(dbConn);

	try {
		const conditions = ["session_id = ?"];
		const params = [sessionId];

		if (type) {
			conditions.push("type = ?");
			params.push(type);
		}
		if (unitId) {
			conditions.push("unit_id = ?");
			params.push(unitId);
		}
		if (query) {
			conditions.push("content LIKE ?");
			params.push(`%${query}%`);
		}

		const whereClause = conditions.join(" AND ");
		params.push(limit);

		const rows = dbConn
			.prepare(
				`SELECT id, session_id, unit_id, type, content, metadata, created_at
			 FROM ${MEMORY_TABLE}
			 WHERE ${whereClause}
			 ORDER BY created_at DESC
			 LIMIT ?`,
			)
			.all(...params);

		return rows.map((r) => ({
			id: r.id,
			sessionId: r.session_id,
			unitId: r.unit_id,
			type: r.type,
			content: r.content,
			metadata: r.metadata ? JSON.parse(r.metadata) : null,
			createdAt: r.created_at,
		}));
	} catch (err) {
		logWarning("memory", "getMemories failed", {
			error: String(err),
			sessionId,
		});
		return [];
	}
}

/**
 * Get memory counts by type for a session.
 */
export function getMemoryCounts(sessionId, db) {
	const dbConn = db || (isDbAvailable() ? require("./sf-db.js").getDb() : null);
	if (!dbConn) return {};

	ensureMemoryTable(dbConn);

	try {
		const rows = dbConn
			.prepare(
				`SELECT type, COUNT(*) as count FROM ${MEMORY_TABLE} WHERE session_id = ? GROUP BY type`,
			)
			.all(sessionId);

		const counts = {};
		for (const r of rows) counts[r.type] = r.count;
		return counts;
	} catch (err) {
		logWarning("memory", "getMemoryCounts failed", { error: String(err) });
		return {};
	}
}

/**
 * Format memories for prompt injection (RA.Aid-style).
 */
export function formatMemoriesForPrompt(memories, options = {}) {
	if (!memories || memories.length === 0) return "";

	const { type = null, maxChars = 4000, header = null } = options;
	const filtered = type ? memories.filter((m) => m.type === type) : memories;

	const parts = [];
	if (header) parts.push(header);

	let totalChars = 0;
	for (const m of filtered) {
		const line = `[${m.type}] ${m.content}`;
		if (totalChars + line.length > maxChars) {
			parts.push(
				`... (${filtered.length - parts.length + (header ? 1 : 0)} more)`,
			);
			break;
		}
		parts.push(line);
		totalChars += line.length + 1;
	}

	return parts.join("\n");
}

/**
 * Auto-summarize when memory count exceeds threshold.
 */
function maybeAutoSummarize(db, sessionId) {
	try {
		const count = db
			.prepare(`SELECT COUNT(*) as c FROM ${MEMORY_TABLE} WHERE session_id = ?`)
			.get(sessionId).c;

		if (count >= AUTO_SUMMARIZE_THRESHOLD) {
			debugLog("memory-auto-summarize", {
				sessionId,
				count,
				threshold: AUTO_SUMMARIZE_THRESHOLD,
			});
			// Future: dispatch a summarization unit or compress oldest memories
		}
	} catch {
		// Non-fatal
	}
}

/**
 * Delete memories older than a given date.
 */
export function pruneMemories(sessionId, olderThan, db) {
	const dbConn = db || (isDbAvailable() ? require("./sf-db.js").getDb() : null);
	if (!dbConn) return 0;

	ensureMemoryTable(dbConn);

	try {
		const result = dbConn
			.prepare(
				`DELETE FROM ${MEMORY_TABLE} WHERE session_id = ? AND created_at < ?`,
			)
			.run(sessionId, olderThan);
		return result.changes;
	} catch (err) {
		logWarning("memory", "pruneMemories failed", { error: String(err) });
		return 0;
	}
}
