import { execFileSync } from "node:child_process";
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { sfRoot } from "./paths.js";
export function writeRunawayRecoveryArtifact(
	basePath,
	metadata,
	now = new Date(),
) {
	try {
		const dir = join(sfRoot(basePath), "runtime", "runaway-recovery");
		mkdirSync(dir, { recursive: true });
		const stamp = now.toISOString().replace(/[:.]/g, "-");
		const fileStem = `${stamp}-${safeSegment(metadata.unitType)}-${safeSegment(metadata.unitId)}`;
		const jsonPath = join(dir, `${fileStem}.json`);
		const markdownPath = join(dir, `${fileStem}.md`);
		const changedFiles = listChangedFiles(basePath);
		const topTools = metadata.metrics.topTools ?? {};
		const recommendedAction = buildRecommendedAction(metadata, changedFiles);
		const resumePrompt = buildResumePrompt(metadata, changedFiles);
		const artifact = {
			version: 1,
			kind: "runaway-recovery",
			createdAt: now.toISOString(),
			unitType: metadata.unitType,
			unitId: metadata.unitId,
			reason: metadata.reason,
			metrics: metadata.metrics,
			thresholdReasons: metadata.thresholdReasons,
			lastWarningMetrics: metadata.lastWarningMetrics,
			topTools,
			changedFiles,
			recommendedAction,
			resumePrompt,
			jsonPath: relativeToSfRoot(jsonPath, basePath),
			markdownPath: relativeToSfRoot(markdownPath, basePath),
		};
		writeFileSync(jsonPath, JSON.stringify(artifact, null, 2) + "\n", "utf-8");
		writeFileSync(markdownPath, renderMarkdown(artifact), "utf-8");
		return artifact;
	} catch {
		return null;
	}
}
function listChangedFiles(basePath) {
	try {
		const out = execFileSync("git", ["status", "--porcelain=v1"], {
			cwd: basePath,
			encoding: "utf8",
			stdio: ["ignore", "pipe", "ignore"],
			timeout: 2000,
		});
		return out
			.split("\n")
			.map((line) => line.trimEnd())
			.filter(Boolean)
			.map((line) => line.slice(3))
			.slice(0, 200);
	} catch {
		return [];
	}
}
function buildRecommendedAction(metadata, changedFiles) {
	if (changedFiles.length === 0) {
		return "Create a narrower follow-up unit before resuming. The paused unit consumed budget without durable file changes.";
	}
	if (metadata.unitType === "execute-task" && changedFiles.length <= 8) {
		return "Resume with a verification-first prompt: inspect the listed dirty files, finish the failing tests, and avoid new exploration.";
	}
	return "Split the remaining work into smaller units with disjoint write sets before resuming. Keep the listed dirty files as the handoff boundary.";
}
function buildResumePrompt(metadata, changedFiles) {
	const fileList =
		changedFiles.length === 0
			? "No changed files were detected."
			: changedFiles.map((file) => `- ${file}`).join("\n");
	return [
		`Resume paused ${metadata.unitType} ${metadata.unitId} after runaway guard pause.`,
		"",
		"First read the runtime recovery artifact and the dirty files below.",
		"Do not broaden scope. Do not restart research from scratch.",
		"Classify the previous run as one of: legitimately large, blocked, or stuck/churning.",
		"Then either finish the smallest verifiable slice or create a narrower follow-up unit.",
		"",
		`Pause reason: ${metadata.reason}`,
		"",
		"Dirty files:",
		fileList,
	].join("\n");
}
function renderMarkdown(artifact) {
	const changedFiles =
		artifact.changedFiles.length === 0
			? "- none"
			: artifact.changedFiles.map((file) => `- \`${file}\``).join("\n");
	const topTools =
		Object.entries(artifact.topTools)
			.sort(([, a], [, b]) => b - a)
			.slice(0, 10)
			.map(([tool, count]) => `- \`${tool}\`: ${count}`)
			.join("\n") || "- none recorded";
	return [
		"# Runaway Recovery Handoff",
		"",
		`Created: ${artifact.createdAt}`,
		`Unit: ${artifact.unitType} ${artifact.unitId}`,
		"",
		"## Reason",
		"",
		artifact.reason,
		"",
		"## Recommended Action",
		"",
		artifact.recommendedAction,
		"",
		"## Metrics",
		"",
		`- Tool calls: ${artifact.metrics.toolCalls}`,
		`- Session tokens: ${artifact.metrics.sessionTokens}`,
		`- Elapsed ms: ${artifact.metrics.elapsedMs}`,
		`- Changed files: ${artifact.metrics.changedFiles ?? "unknown"}`,
		"",
		"## Threshold Reasons",
		"",
		artifact.thresholdReasons.map((reason) => `- ${reason}`).join("\n"),
		"",
		"## Top Tools",
		"",
		topTools,
		"",
		"## Dirty Files",
		"",
		changedFiles,
		"",
		"## Resume Prompt",
		"",
		"```text",
		artifact.resumePrompt,
		"```",
		"",
	].join("\n");
}
function safeSegment(value) {
	return value.replace(/[^A-Za-z0-9._-]+/g, "-").slice(0, 80) || "unit";
}
function relativeToSfRoot(path, basePath) {
	const root = sfRoot(basePath);
	if (!existsSync(root)) return path;
	return path.startsWith(`${root}/`)
		? `.sf/${path.slice(root.length + 1)}`
		: path;
}
