/**
 * Pre-unit git checkpoint and rollback for autonomous mode safety harness.
 * Uses the existing refs/next/ namespace (already pruned by doctor).
 *
 * Creates a lightweight ref at HEAD before unit execution. On failure,
 * the ref can be used to rollback the branch to the pre-unit state.
 *
 * Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
 */
import { execFileSync } from "node:child_process";
import { logWarning } from "../workflow-logger.js";

// ─── Constants ──────────────────────────────────────────────────────────────
const CHECKPOINT_PREFIX = "refs/next/checkpoints/";
/**
 * Sanitize a unitId for use as a git ref component.
 * Git ref names prohibit: spaces, control chars, ~, ^, :, ?, *, [, \, ..,
 * @{, leading dot, trailing .lock, and path component starting with ".".
 * We replace all disallowed characters with "-" and collapse runs.
 */
function sanitizeForRef(unitId) {
	return (
		unitId
			.replace(/\//g, "-") // path separators → dashes
			.replace(/[^a-zA-Z0-9._-]/g, "-") // anything else disallowed → dash
			.replace(/\.{2,}/g, "-") // ".." sequences → dash
			.replace(/\.lock$/i, "-lock") // trailing .lock suffix
			.replace(/^\./, "_") // leading dot
			.replace(/-{2,}/g, "-") // collapse consecutive dashes
			.replace(/^-|-$/g, "") || // strip leading/trailing dash
		"unit"
	); // fallback if everything was stripped
}
// ─── Public API ─────────────────────────────────────────────────────────────
/**
 * Create a checkpoint ref at the current HEAD for the given unit.
 * Returns the SHA of HEAD, or null if the operation fails.
 */
export function createCheckpoint(basePath, unitId) {
	try {
		const sha = execFileSync("git", ["rev-parse", "--verify", "HEAD"], {
			cwd: basePath,
			stdio: ["ignore", "pipe", "pipe"],
			encoding: "utf-8",
		}).trim();
		if (!sha || sha.length < 7) return null;
		const safeUnitId = sanitizeForRef(unitId);
		execFileSync(
			"git",
			["update-ref", `${CHECKPOINT_PREFIX}${safeUnitId}`, sha],
			{
				cwd: basePath,
				stdio: ["ignore", "pipe", "pipe"],
			},
		);
		return sha;
	} catch (e) {
		const stderr = e.stderr;
		const stderrText = Buffer.isBuffer(stderr)
			? stderr.toString("utf-8")
			: String(stderr ?? "");
		if (
			stderrText.includes("Needed a single revision") ||
			stderrText.includes("unknown revision") ||
			stderrText.includes("ambiguous argument 'HEAD'")
		) {
			return null;
		}
		logWarning("safety", `checkpoint creation failed: ${e.message}`);
		return null;
	}
}
/**
 * Rollback the current branch to a checkpoint SHA.
 * Returns true on success, false on failure.
 *
 * WARNING: This is a destructive operation — it discards all changes
 * since the checkpoint. Only call when the user has opted in via
 * safety_harness.auto_rollback or an explicit manual trigger.
 */
export function rollbackToCheckpoint(basePath, unitId, sha) {
	try {
		// Get current branch name
		const branch = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
			cwd: basePath,
			stdio: ["ignore", "pipe", "pipe"],
			encoding: "utf-8",
		}).trim();
		if (!branch || branch === "HEAD") {
			logWarning("safety", "rollback: detached HEAD state, cannot rollback");
			return false;
		}
		// Reset branch pointer and working tree to checkpoint SHA in one step.
		// Using `git reset --hard <sha>` works on the currently checked-out branch
		// (unlike `git branch -f` which is rejected for checked-out branches).
		execFileSync("git", ["reset", "--hard", sha], {
			cwd: basePath,
			stdio: ["ignore", "pipe", "pipe"],
		});
		// Cleanup checkpoint ref
		cleanupCheckpoint(basePath, unitId);
		return true;
	} catch (e) {
		logWarning("safety", `rollback failed: ${e.message}`);
		return false;
	}
}
/**
 * Remove a checkpoint ref after successful unit completion.
 */
export function cleanupCheckpoint(basePath, unitId) {
	try {
		const safeUnitId = sanitizeForRef(unitId);
		execFileSync(
			"git",
			["update-ref", "-d", `${CHECKPOINT_PREFIX}${safeUnitId}`],
			{
				cwd: basePath,
				stdio: ["ignore", "pipe", "pipe"],
			},
		);
	} catch {
		// Non-fatal — ref may already have been cleaned up
	}
}
