/**
 * phases-pre-dispatch-idle-triage.test.mjs — verify the bail-time
 * self-feedback queue notifier (sf-mp4rxkwb-l4baga partial).
 *
 * Scope: unit-test the surfaceSelfFeedbackQueueOnIdle helper in isolation.
 * The wider runPreDispatch integration (which calls this helper from the
 * "no-active-milestone" / "milestone-complete" bail paths) is not unit-
 * tested here — the helper is the contract and the call sites are
 * straightforward two-line additions verified by code review + the full
 * SF suite passing.
 */
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, test } from "vitest";
import { surfaceSelfFeedbackQueueOnIdle } from "../auto/phases-pre-dispatch.js";

let tempDirs = [];

function makeForgeProject() {
	const dir = mkdtempSync(join(tmpdir(), "sf-idle-triage-test-"));
	tempDirs.push(dir);
	writeFileSync(
		join(dir, "package.json"),
		JSON.stringify({ name: "singularity-forge" }),
		"utf-8",
	);
	mkdirSync(join(dir, ".sf"), { recursive: true });
	return dir;
}

function writeEntries(basePath, entries) {
	const path = join(basePath, ".sf", "self-feedback.jsonl");
	writeFileSync(
		path,
		`${entries.map((e) => JSON.stringify(e)).join("\n")}\n`,
		"utf-8",
	);
}

function entry(overrides = {}) {
	return {
		schemaVersion: 1,
		id: `sf-test-${Math.random().toString(36).slice(2, 8)}`,
		ts: new Date().toISOString(),
		kind: "gap:test",
		severity: "high",
		blocking: false,
		summary: "test entry",
		source: "runtime",
		...overrides,
	};
}

function makeFakeCtx() {
	const calls = [];
	return {
		calls,
		ctx: {
			ui: {
				notify: (msg, level) => {
					calls.push({ msg, level });
				},
			},
		},
	};
}

afterEach(() => {
	for (const dir of tempDirs) {
		rmSync(dir, { recursive: true, force: true });
	}
	tempDirs = [];
});

describe("surfaceSelfFeedbackQueueOnIdle", () => {
	test("notifies when open candidates exist with the canonical pointer to triage", () => {
		const dir = makeForgeProject();
		writeEntries(dir, [
			entry({ id: "sf-a", severity: "high", summary: "first" }),
			entry({ id: "sf-b", severity: "high", summary: "second" }),
		]);
		const { ctx, calls } = makeFakeCtx();
		surfaceSelfFeedbackQueueOnIdle(ctx, dir, "no-active-milestone");
		expect(calls).toHaveLength(1);
		expect(calls[0].level).toBe("warning");
		expect(calls[0].msg).toContain("Idle (no-active-milestone)");
		expect(calls[0].msg).toContain("2 self-feedback entries");
		expect(calls[0].msg).toContain("sf headless triage --list");
		expect(calls[0].msg).toContain("sf headless triage --run");
	});

	test("uses singular 'entry' for n=1", () => {
		const dir = makeForgeProject();
		writeEntries(dir, [entry({ id: "sf-only" })]);
		const { ctx, calls } = makeFakeCtx();
		surfaceSelfFeedbackQueueOnIdle(ctx, dir, "milestone-complete");
		expect(calls).toHaveLength(1);
		expect(calls[0].msg).toContain("1 self-feedback entry");
		expect(calls[0].msg).toContain("milestone-complete");
	});

	test("is silent when no open candidates remain", () => {
		const dir = makeForgeProject();
		writeEntries(dir, [
			entry({
				id: "sf-resolved",
				resolvedAt: "2026-05-14T00:00:00Z",
				resolvedReason: "fixed",
				resolvedEvidence: { kind: "agent-fix", commitSha: "abc123" },
			}),
		]);
		const { ctx, calls } = makeFakeCtx();
		surfaceSelfFeedbackQueueOnIdle(ctx, dir, "no-active-milestone");
		expect(calls).toHaveLength(0);
	});

	test("is silent on a non-forge basePath (best-effort no-op)", () => {
		const dir = mkdtempSync(join(tmpdir(), "sf-idle-triage-nonforge-"));
		tempDirs.push(dir);
		writeFileSync(
			join(dir, "package.json"),
			JSON.stringify({ name: "some-other-app" }),
			"utf-8",
		);
		const { ctx, calls } = makeFakeCtx();
		surfaceSelfFeedbackQueueOnIdle(ctx, dir, "no-active-milestone");
		expect(calls).toHaveLength(0);
	});

	test("does not throw when ctx.ui.notify itself throws", () => {
		// Bail-path safety: a downstream notification failure must never
		// take down the loop's clean exit.
		const dir = makeForgeProject();
		writeEntries(dir, [entry({ id: "sf-x" })]);
		const ctx = {
			ui: {
				notify: () => {
					throw new Error("ui crash");
				},
			},
		};
		expect(() =>
			surfaceSelfFeedbackQueueOnIdle(ctx, dir, "no-active-milestone"),
		).not.toThrow();
	});
});
