/**
 * stageExplicitIncludePaths .sf/ filter contract tests — vitest unit tests.
 *
 * Purpose: verify that stageExplicitIncludePaths filters out any path whose
 * first segment is `.sf`, preventing those paths from reaching nativeAddPaths.
 * Consumer: CI gate via `npx vitest run ...`.
 */

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

// ─── Mock nativeAddPaths to capture what reaches it ────────────────────────

const nativeAddPathsMock = vi.hoisted(() => vi.fn());

vi.mock("../native-git-bridge.js", async (importOriginal) => {
	const actual = await importOriginal();
	return {
		...actual,
		nativeAddPaths: nativeAddPathsMock,
		nativeHasStagedChanges: vi.fn().mockReturnValue(false),
		nativeCommit: vi.fn(),
		nativeAddAllWithExclusions: vi.fn(),
	};
});

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

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

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

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

describe("stageExplicitIncludePaths .sf/ filter", () => {
	test("passes non-.sf/ paths through to nativeAddPaths", async () => {
		const base = makeTempDir("sf-git-test-");
		try {
			mkdirSync(join(base, ".git"), { recursive: true });
			mkdirSync(join(base, "src"), { recursive: true });
			writeFileSync(join(base, "src/index.ts"), "export {}");

			nativeAddPathsMock.mockClear();
			const { GitService } = await import("../git-service.js");
			const git = new GitService(base);
			git.stageExplicitIncludePaths(["src/index.ts"], []);

			expect(nativeAddPathsMock).toHaveBeenCalledOnce();
			expect(nativeAddPathsMock.mock.calls[0][1]).toContain("src/index.ts");
		} finally {
			cleanup(base);
		}
	});

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

			nativeAddPathsMock.mockClear();
			const { GitService } = await import("../git-service.js");
			const git = new GitService(base);
			git.stageExplicitIncludePaths([".sf/plans/foo.md"], []);

			expect(nativeAddPathsMock).not.toHaveBeenCalled();
		} finally {
			cleanup(base);
		}
	});

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

			nativeAddPathsMock.mockClear();
			const { GitService } = await import("../git-service.js");
			const git = new GitService(base);
			git.stageExplicitIncludePaths([".sf/milestones/M001/SLICE.md"], []);

			expect(nativeAddPathsMock).not.toHaveBeenCalled();
		} finally {
			cleanup(base);
		}
	});

	test("mixed .sf/ and non-.sf/ paths — only non-.sf/ reaches nativeAddPaths", async () => {
		const base = makeTempDir("sf-git-test-");
		try {
			mkdirSync(join(base, ".git"), { recursive: true });
			mkdirSync(join(base, "src"), { recursive: true });
			mkdirSync(join(base, ".sf", "plans"), { recursive: true });
			writeFileSync(join(base, "src/index.ts"), "export {}");
			writeFileSync(join(base, ".sf", "plans", "foo.md"), "# plan");

			nativeAddPathsMock.mockClear();
			const { GitService } = await import("../git-service.js");
			const git = new GitService(base);
			git.stageExplicitIncludePaths([".sf/plans/foo.md", "src/index.ts"], []);

			expect(nativeAddPathsMock).toHaveBeenCalledOnce();
			expect(nativeAddPathsMock.mock.calls[0][1]).toEqual(["src/index.ts"]);
		} finally {
			cleanup(base);
		}
	});

	test("Windows-style .sf\\... path is filtered (first segment after normalization)", async () => {
		const base = makeTempDir("sf-git-test-");
		try {
			mkdirSync(join(base, ".git"), { recursive: true });

			nativeAddPathsMock.mockClear();
			const { GitService } = await import("../git-service.js");
			const git = new GitService(base);
			// Windows-style: .sf\plans\foo.md → normalized to .sf/plans/foo.md
			// First segment is .sf → should be filtered
			git.stageExplicitIncludePaths([".sf\\plans\\foo.md"], []);

			expect(nativeAddPathsMock).not.toHaveBeenCalled();
		} finally {
			cleanup(base);
		}
	});

	test("Windows-style .sf-as-prefix\\... is NOT filtered (first segment is not .sf)", async () => {
		const base = makeTempDir("sf-git-test-");
		try {
			mkdirSync(join(base, ".git"), { recursive: true });
			mkdirSync(join(base, "foo", ".sf", "plans"), { recursive: true });
			writeFileSync(join(base, "foo", ".sf", "plans", "foo.md"), "# plan");

			nativeAddPathsMock.mockClear();
			const { GitService } = await import("../git-service.js");
			const git = new GitService(base);
			// foo\.sf\... → normalized to foo/.sf/... → first segment = foo → NOT filtered
			// (this is a subdirectory named .sf under foo/, not the SF state dir)
			git.stageExplicitIncludePaths(["foo\\.sf\\plans\\foo.md"], []);

			expect(nativeAddPathsMock).toHaveBeenCalledOnce();
		} finally {
			cleanup(base);
		}
	});
});
