/**
 * SF Parallel Merge — Worktree reconciliation for parallel milestones.
 *
 * Handles merging completed milestone worktrees back to main branch
 * with safety checks for parallel execution context.
 */
import { existsSync, readdirSync } from "node:fs";
import { join } from "node:path";
import { DatabaseSync } from "node:sqlite";
import { mergeMilestoneToMain } from "./auto-worktree.js";
import { getErrorMessage } from "./error-utils.js";
import { loadFile } from "./files.js";
import { MergeConflictError } from "./git-service.js";
import { resolveMilestoneFile } from "./paths.js";
import { removeSessionStatus } from "./session-status-io.js";
import { logWarning } from "./workflow-logger.js";
// ─── Merge Queue ───────────────────────────────────────────────────────────
/**
 * Check whether a milestone is complete by querying its worktree SQLite DB.
 * Uses Node's built-in SQLite provider to avoid disrupting the global DB singleton.
 * Returns true when milestones.status = 'complete' in the worktree's sf.db.
 */
/**
 * Check if a milestone is marked as complete in its worktree database.
 */
export function isMilestoneCompleteInWorktreeDb(basePath, mid) {
	const dbPath = join(basePath, ".sf", "worktrees", mid, ".sf", "sf.db");
	if (!existsSync(dbPath)) return false;
	try {
		const db = new DatabaseSync(dbPath);
		try {
			const stmt = db.prepare(
				"SELECT status FROM milestones WHERE id = ? LIMIT 1",
			);
			const row = stmt.get(mid);
			return row?.status === "complete";
		} finally {
			db.close();
		}
	} catch (e) {
		logWarning(
			"parallel",
			`milestone completion check failed for ${mid}: ${e.message}`,
		);
		return false;
	}
}
/**
 * Discover milestone IDs with status='complete' in their worktree DB,
 * scanning .sf/worktrees/<MID>/.sf/sf.db for each worktree directory.
 */
function discoverDbCompletedMilestones(basePath) {
	const completed = new Set();
	const worktreeDir = join(basePath, ".sf", "worktrees");
	try {
		for (const entry of readdirSync(worktreeDir)) {
			if (
				entry.startsWith("M") &&
				isMilestoneCompleteInWorktreeDb(basePath, entry)
			) {
				completed.add(entry);
			}
		}
	} catch (e) {
		logWarning(
			"parallel",
			`readdirSync for completed set failed: ${e.message}`,
		);
	}
	return completed;
}
/**
 * Determine safe merge order for completed milestones.
 * Sequential: merge in milestone ID order (M001 before M002).
 * By-completion: merge in the order milestones finished.
 *
 * When basePath is provided, also checks worktree SQLite DBs as the
 * source of truth — workers with stale orchestrator state (e.g. "error")
 * are included if their worktree DB shows status='complete'.
 * See: https://github.com/singularity-forge/sf-run/issues/2812
 */
/**
 * Determine merge order for completed milestones (sequential or by-completion).
 */
export function determineMergeOrder(workers, order = "sequential", basePath) {
	// Start with workers the orchestrator already knows are stopped
	const stoppedIds = new Set(
		workers.filter((w) => w.state === "stopped").map((w) => w.milestoneId),
	);
	// When basePath is available, also check worktree DBs for milestones
	// whose orchestrator state is stale but are actually complete (#2812)
	const dbCompleted = basePath
		? discoverDbCompletedMilestones(basePath)
		: new Set();
	// Union: milestone is mergeable if stopped OR DB-complete
	const mergeableIds = new Set([...stoppedIds, ...dbCompleted]);
	// Build the list from tracked workers + any DB-discovered milestones
	// not tracked by the orchestrator at all
	const workerMap = new Map(workers.map((w) => [w.milestoneId, w]));
	const allMergeable = [];
	for (const mid of mergeableIds) {
		const w = workerMap.get(mid);
		if (w) {
			allMergeable.push(w);
		} else {
			// Milestone discovered from worktree DB but not in workers list
			allMergeable.push({
				milestoneId: mid,
				title: mid,
				pid: 0,
				process: null,
				worktreePath: basePath ? join(basePath, ".sf", "worktrees", mid) : "",
				startedAt: 0,
				state: "stopped",
				cost: 0,
			});
		}
	}
	if (order === "by-completion") {
		return allMergeable
			.sort((a, b) => a.startedAt - b.startedAt) // earliest first
			.map((w) => w.milestoneId);
	}
	return allMergeable
		.sort((a, b) => a.milestoneId.localeCompare(b.milestoneId))
		.map((w) => w.milestoneId);
}
/**
 * Attempt to merge a single milestone's worktree back to main.
 * Wraps mergeMilestoneToMain with error handling for parallel context.
 */
/**
 * Merge a completed milestone to main branch, returning success or conflict details.
 */
export async function mergeCompletedMilestone(basePath, milestoneId) {
	try {
		// Load the roadmap content (needed by mergeMilestoneToMain)
		const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
		if (!roadmapPath) {
			return {
				milestoneId,
				success: false,
				error: `No roadmap found for ${milestoneId}`,
			};
		}
		const roadmapContent = await loadFile(roadmapPath);
		if (!roadmapContent) {
			return {
				milestoneId,
				success: false,
				error: `Could not read roadmap for ${milestoneId}`,
			};
		}
		// Attempt the merge
		const result = mergeMilestoneToMain(basePath, milestoneId, roadmapContent);
		// Clean up parallel session status
		removeSessionStatus(basePath, milestoneId);
		return {
			milestoneId,
			success: true,
			commitMessage: result.commitMessage,
			pushed: result.pushed,
		};
	} catch (err) {
		if (err instanceof MergeConflictError) {
			return {
				milestoneId,
				success: false,
				error: `Merge conflict: ${err.conflictedFiles.length} conflicting file(s)`,
				conflictFiles: err.conflictedFiles,
			};
		}
		return {
			milestoneId,
			success: false,
			error: getErrorMessage(err),
		};
	}
}
/**
 * Merge all completed milestones in sequence.
 * Stops on first conflict and returns results so far.
 */
/**
 * Merge all completed milestones in order, stopping on first conflict.
 */
export async function mergeAllCompleted(
	basePath,
	workers,
	order = "sequential",
) {
	const mergeOrder = determineMergeOrder(workers, order, basePath);
	const results = [];
	for (const mid of mergeOrder) {
		const result = await mergeCompletedMilestone(basePath, mid);
		results.push(result);
		// Stop on first conflict — later merges may depend on this one
		if (!result.success && result.conflictFiles) {
			break;
		}
	}
	return results;
}
/**
 * Format merge results for display.
 */
/**
 * Format merge operation results into a markdown report with conflicts and errors.
 */
export function formatMergeResults(results) {
	if (results.length === 0) return "No completed milestones to merge.";
	const lines = ["# Merge Results\n"];
	for (const r of results) {
		if (r.success) {
			const pushStatus = r.pushed ? " (pushed)" : "";
			lines.push(`- **${r.milestoneId}** — merged successfully${pushStatus}`);
		} else if (r.conflictFiles) {
			lines.push(
				`- **${r.milestoneId}** — CONFLICT (${r.conflictFiles.length} file(s)):`,
			);
			for (const f of r.conflictFiles) {
				lines.push(`  - \`${f}\``);
			}
			lines.push(
				`  Resolve conflicts manually and run \`/parallel merge ${r.milestoneId}\` to retry.`,
			);
		} else {
			lines.push(`- **${r.milestoneId}** — failed: ${r.error}`);
		}
	}
	return lines.join("\n");
}
