/**
 * phases-merge-error-stops-auto.test.ts — Regression test for #2766.
 *
 * When mergeAndExit throws a non-MergeConflictError, the auto loop must
 * stop instead of continuing with unmerged work. This test verifies that
 * all catch blocks in auto/phases.ts that handle mergeAndExit errors
 * call stopAuto and return { action: "break" } for non-conflict errors.
 */

import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, it } from "vitest";
import assert from "node:assert/strict";

const phasesPath = join(import.meta.dirname, "..", "auto", "phases.ts");
const phasesSrc = readFileSync(phasesPath, "utf-8");

describe("#2766: Non-MergeConflictError stops auto mode", () => {
	it("phases.ts file exists and is readable", () => {
		assert.ok(
			phasesPath.length > 0 && phasesPath.endsWith("phases.ts"),
			"phases.ts file exists and is readable",
		);
	});

	it("all mergeAndExit call sites have catch (mergeErr) blocks", () => {
		const mergeErrCatchCount = [...phasesSrc.matchAll(/\} catch \(mergeErr\)/g)]
			.length;
		assert.ok(
			mergeErrCatchCount >= 3,
			`all mergeAndExit call sites have catch (mergeErr) blocks (found ${mergeErrCatchCount}, expected >= 3)`,
		);
	});

	it("every mergeErr catch block handles non-MergeConflictError", () => {
		const catchPattern = /\} catch \(mergeErr\) \{/g;
		let match: RegExpExecArray | null;
		let blocksWithNonConflictHandling = 0;
		let blocksTotal = 0;

		match = catchPattern.exec(phasesSrc);
		while (match !== null) {
			blocksTotal++;
			const afterCatch = phasesSrc.slice(match.index, match.index + 1200);

			const hasInstanceofCheck = afterCatch.includes(
				"instanceof MergeConflictError",
			);
			const hasNonConflictStop = afterCatch.includes('reason: "merge-failed"');
			const hasStopAuto = afterCatch.includes("stopAuto");
			const hasLogError = afterCatch.includes("logError");

			if (hasInstanceofCheck && hasNonConflictStop && hasStopAuto && hasLogError) {
				blocksWithNonConflictHandling++;
			}
			match = catchPattern.exec(phasesSrc);
		}

		assert.strictEqual(
			blocksWithNonConflictHandling,
			blocksTotal,
			`all ${blocksTotal} mergeAndExit catch blocks stop auto on non-conflict errors (${blocksWithNonConflictHandling}/${blocksTotal})`,
		);
		assert.ok(
			blocksTotal >= 3,
			`expected at least 3 catch blocks, found ${blocksTotal}`,
		);
	});

	it("non-conflict handler returns break (does not continue)", () => {
		const mergeFailedReasons = [...phasesSrc.matchAll(/reason: "merge-failed"/g)]
			.length;
		assert.ok(
			mergeFailedReasons >= 3,
			`all catch blocks return reason: "merge-failed" (found ${mergeFailedReasons}, expected >= 3)`,
		);
	});

	it("non-conflict handler notifies user", () => {
		const notifyErrorPattern =
			/Merge failed:.*Resolve and run \/sf autonomous to resume/g;
		const notifyCount = [...phasesSrc.matchAll(notifyErrorPattern)].length;
		assert.ok(
			notifyCount >= 3,
			`all catch blocks notify user about merge failure (found ${notifyCount}, expected >= 3)`,
		);
	});

	it("logError replaces logWarning for non-conflict merge errors", () => {
		const logWarningMergePattern =
			/logWarning\(.*Milestone merge failed with non-conflict error/g;
		const logWarningCount = [...phasesSrc.matchAll(logWarningMergePattern)].length;
		assert.strictEqual(
			logWarningCount,
			0,
			"logWarning is no longer used for non-conflict merge errors (replaced by logError)",
		);

		const logErrorMergePattern =
			/logError\(.*Milestone merge failed with non-conflict error/g;
		const logErrorCount = [...phasesSrc.matchAll(logErrorMergePattern)].length;
		assert.ok(
			logErrorCount >= 3,
			`logError is used for non-conflict merge errors (found ${logErrorCount}, expected >= 3)`,
		);
	});
});
