/**
 * Parallel Worker Intent/Claim Registry
 *
 * Purpose: before editing files, parallel workers declare intent so the
 * coordinator can detect conflicts without waiting for git merge failures.
 * This is SF's implementation of Wit-style symbol-level coordination for
 * parallel milestone workers.
 *
 * Consumer: parallel-orchestrator.js before dispatching overlapping milestones.
 */
import { mkdirSync } from "node:fs";
import { join } from "node:path";
import { getDatabase, getDbPath, openDatabase } from "./sf-db.js";
import { UokCoordinationStore } from "./uok/coordination-store.js";
import { logWarning } from "./workflow-logger.js";

const INTENT_KEY_PREFIX = "parallel:intent:";
const INTENT_STREAM = "parallel:intents";
const DEFAULT_INTENT_TTL_MS = 300_000; // 5 minutes

function isExpired(record) {
	if (!record?.expiresAt) return false;
	return Date.now() > record.expiresAt;
}

function projectDbPath(basePath) {
	const sfDir = join(basePath, ".sf");
	mkdirSync(sfDir, { recursive: true });
	return join(sfDir, "sf.db");
}

const _storeCache = new Map();

function getStore(basePath) {
	const dbPath = projectDbPath(basePath);
	let store = _storeCache.get(dbPath);
	if (store) return store;

	if (!getDatabase() || getDbPath() !== dbPath) {
		openDatabase(dbPath);
	}
	const db = getDatabase();
	if (!db) throw new Error("SF database is not open");
	store = new UokCoordinationStore(db);
	_storeCache.set(dbPath, store);
	return store;
}

function intentKey(milestoneId) {
	return `${INTENT_KEY_PREFIX}${milestoneId}`;
}

function normalizeFiles(files) {
	return files.map((f) => f.replace(/^\/+/, ""));
}

/**
 * Declare editing intent before making changes.
 *
 * Purpose: let other workers know which files this worker plans to modify,
 * so conflicts can be detected proactively.
 *
 * @param {string} basePath — project root
 * @param {string} milestoneId — worker's milestone
 * @param {string[]} files — relative file paths worker intends to edit
 * @param {object} opts — optional: symbolRanges (for fine-grained claims)
 */
export function declareIntent(basePath, milestoneId, files, opts = {}) {
	try {
		const store = getStore(basePath);
		const record = {
			milestoneId,
			files: normalizeFiles(files),
			symbolRanges: opts.symbolRanges ?? [],
			declaredAt: new Date().toISOString(),
			expiresAt: Date.now() + (opts.ttlMs ?? DEFAULT_INTENT_TTL_MS),
			status: "claimed",
		};
		store.set(intentKey(milestoneId), record);
		store.xadd(INTENT_STREAM, "intent-claimed", record);
		return { ok: true };
	} catch (err) {
		logWarning(
			"parallel-intent",
			`declareIntent failed for ${milestoneId}: ${err.message}`,
		);
		return { ok: false, error: err.message };
	}
}

/**
 * Release intent claim when editing is complete or abandoned.
 */
export function releaseIntent(basePath, milestoneId) {
	try {
		const store = getStore(basePath);
		const record = store.get(intentKey(milestoneId));
		if (record) {
			record.status = "released";
			record.releasedAt = new Date().toISOString();
			store.set(intentKey(milestoneId), record);
			store.xadd(INTENT_STREAM, "intent-released", record);
		}
		return { ok: true };
	} catch (err) {
		logWarning(
			"parallel-intent",
			`releaseIntent failed for ${milestoneId}: ${err.message}`,
		);
		return { ok: false, error: err.message };
	}
}

/**
 * Check if any active worker has claimed overlapping files.
 *
 * @returns {Array<{milestoneId: string, files: string[]}>} conflicts
 */
export function checkIntentConflicts(basePath, milestoneId, files) {
	const conflicts = [];
	const normalized = normalizeFiles(files);
	try {
		for (const record of getActiveIntents(basePath)) {
			if (record.milestoneId === milestoneId) continue;
			const overlap = normalized.filter((f) => record.files.includes(f));
			if (overlap.length > 0) {
				conflicts.push({ milestoneId: record.milestoneId, files: overlap });
			}
		}
	} catch (err) {
		logWarning(
			"parallel-intent",
			`checkIntentConflicts failed: ${err.message}`,
		);
	}
	return conflicts;
}

/**
 * Get all active intent claims.
 */
export function getActiveIntents(basePath) {
	try {
		return getStore(basePath)
			.entries(INTENT_KEY_PREFIX)
			.map((entry) => entry.value)
			.filter((record) => record?.status === "claimed" && !isExpired(record));
	} catch (err) {
		logWarning("parallel-intent", `getActiveIntents failed: ${err.message}`);
		return [];
	}
}

/**
 * Clear all intent records (used on orchestrator shutdown).
 */
export function clearAllIntents(basePath) {
	try {
		const store = getStore(basePath);
		for (const entry of store.entries(INTENT_KEY_PREFIX)) {
			store.delete(entry.key);
		}
	} catch (err) {
		logWarning("parallel-intent", `clearAllIntents failed: ${err.message}`);
	}
}
