/**
 * nativeAddPaths .sf/ skip contract tests — vitest unit tests.
 *
 * Purpose: verify that nativeAddPaths skips any path whose first segment is
 * `.sf`, regardless of whether `.sf` is a real directory or a symlink.
 * Consumer: CI gate via `npx vitest run ...`.
 */

import {
	mkdirSync,
	mkdtempSync,
	rmSync,
	symlinkSync,
	writeFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, test, vi } from "vitest";

// ─── Hoisted mock for gitFileExec so we can capture calls ─────────────────

const gitMock = vi.hoisted(() => ({
	calls: [],
}));

vi.mock("node:child_process", async (importOriginal) => {
	const actual = await importOriginal();
	return {
		...actual,
		execFileSync: vi.fn((cmd, args, _opts) => {
			if (cmd === "git" && args[0] === "add") {
				gitMock.calls.push({ cmd, args });
				return "";
			}
			// Passthrough for other git commands (e.g. rev-parse, status, etc.)
			return actual.execFileSync(cmd, args, _opts);
		}),
	};
});

// ─── Helpers ───────────────────────────────────────────────────────────────

function makeTempDir(prefix) {
	return mkdtempSync(join(tmpdir(), prefix));
}

function cleanup(dir) {
	try {
		rmSync(dir, { recursive: true, force: true });
	} catch {}
}

// ─── Tests ─────────────────────────────────────────────────────────────────

describe("nativeAddPaths .sf/ skip", () => {
	test("(a) symlinked .sf/ path is skipped", async () => {
		const base = makeTempDir("sf-git-test-");
		const sfTarget = makeTempDir("sf-git-sf-target-");
		try {
			mkdirSync(join(base, ".git"));
			mkdirSync(join(sfTarget, "plans"), { recursive: true });
			symlinkSync(sfTarget, join(base, ".sf"));
			writeFileSync(join(sfTarget, "plans", "foo.md"), "# plan");

			gitMock.calls.length = 0;
			const { nativeAddPaths } = await import("../native-git-bridge.js");
			nativeAddPaths(base, [".sf/plans/foo.md"]);

			expect(gitMock.calls.length).toBe(0);
		} finally {
			cleanup(base);
			cleanup(sfTarget);
		}
	});

	test("(b) real-directory .sf/ path is skipped", async () => {
		const base = makeTempDir("sf-git-test-");
		try {
			mkdirSync(join(base, ".git"));
			mkdirSync(join(base, ".sf", "plans"), { recursive: true });
			writeFileSync(join(base, ".sf", "plans", "foo.md"), "# plan");

			gitMock.calls.length = 0;
			const { nativeAddPaths } = await import("../native-git-bridge.js");
			nativeAddPaths(base, [".sf/plans/foo.md"]);

			expect(gitMock.calls.length).toBe(0);
		} finally {
			cleanup(base);
		}
	});

	test("(c) deep path under .sf/ is skipped", async () => {
		const base = makeTempDir("sf-git-test-");
		try {
			mkdirSync(join(base, ".git"));
			mkdirSync(join(base, ".sf", "milestones", "M001"), { recursive: true });
			writeFileSync(
				join(base, ".sf", "milestones", "M001", "SLICE.md"),
				"# slice",
			);

			gitMock.calls.length = 0;
			const { nativeAddPaths } = await import("../native-git-bridge.js");
			nativeAddPaths(base, [".sf/milestones/M001/SLICE.md"]);

			expect(gitMock.calls.length).toBe(0);
		} finally {
			cleanup(base);
		}
	});

	test("(d) non-.sf/ path is passed through to git add", async () => {
		const base = makeTempDir("sf-git-test-");
		try {
			mkdirSync(join(base, ".git"));
			mkdirSync(join(base, "src"), { recursive: true });
			writeFileSync(join(base, "src", "index.ts"), "export {}");

			gitMock.calls.length = 0;
			const { nativeAddPaths } = await import("../native-git-bridge.js");
			nativeAddPaths(base, ["src/index.ts"]);

			expect(gitMock.calls.length).toBe(1);
			expect(gitMock.calls[0].args).toContain("src/index.ts");
		} finally {
			cleanup(base);
		}
	});
});
