/**
 * stop-auto-race-null-unit.test.ts — Regression test for #2939.
 *
 * When the user stops auto-mode while a unit is executing, stopAuto()
 * calls s.reset() which sets s.currentUnit = null. The resumed
 * runUnitPhase() then hits s.currentUnit.startedAt on the closeout
 * line and throws a TypeError.
 *
 * The fix adds null guards (matching the existing pattern at lines 136
 * and 344) so that closeout and subsequent accesses are skipped when
 * s.currentUnit has been nulled by a concurrent stopAuto().
 */

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

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

describe("#2939: stopAuto race — null guard on s.currentUnit", () => {
	it("closeoutUnit call is guarded by if (s.currentUnit)", () => {
		const closeoutComment = "Immediate unit closeout";
		const closeoutIdx = phasesSrc.indexOf(closeoutComment);
		assert.ok(closeoutIdx > 0);

		const closeoutRegion = phasesSrc.slice(closeoutIdx, closeoutIdx + 500);
		assert.ok(closeoutRegion.includes("if (s.currentUnit)"));
	});

	it("zero-tool-call guard no longer uses non-null assertion on s.currentUnit", () => {
		const zeroToolComment = "Zero tool-call guard";
		const zeroToolIdx = phasesSrc.indexOf(zeroToolComment);
		assert.ok(zeroToolIdx > 0);

		const zeroToolRegion = phasesSrc.slice(zeroToolIdx, zeroToolIdx + 600);
		assert.ok(!zeroToolRegion.includes("s.currentUnit!.startedAt"));
	});

	it("no non-null assertions s.currentUnit!.startedAt after closeout comment", () => {
		const closeoutComment = "Immediate unit closeout";
		const closeoutIdx = phasesSrc.indexOf(closeoutComment);
		const afterCloseout = phasesSrc.slice(closeoutIdx);

		const nonNullPattern = /s\.currentUnit!\.startedAt/g;
		const nonNullAfterCloseout = [...afterCloseout.matchAll(nonNullPattern)];
		assert.strictEqual(nonNullAfterCloseout.length, 0);
	});

	it("no return statements use bare s.currentUnit.startedAt after closeout comment", () => {
		const closeoutComment = "Immediate unit closeout";
		const closeoutIdx = phasesSrc.indexOf(closeoutComment);
		const afterCloseout = phasesSrc.slice(closeoutIdx);

		const returnWithBareAccess = /return\s*\{[^}]*s\.currentUnit\.startedAt/g;
		const bareReturnCount = [...afterCloseout.matchAll(returnWithBareAccess)]
			.length;
		assert.strictEqual(bareReturnCount, 0);
	});

	it("final return uses s.currentUnit?.startedAt with optional chaining", () => {
		const closeoutComment = "Immediate unit closeout";
		const closeoutIdx = phasesSrc.indexOf(closeoutComment);
		const afterCloseout = phasesSrc.slice(closeoutIdx);

		const finalReturnPattern = /unitStartedAt:\s*s\.currentUnit\?\.startedAt/;
		assert.ok(finalReturnPattern.test(afterCloseout));
	});
});
