/**
 * validateStagedFileChanges .sf/ safety contract tests — vitest unit tests.
 *
 * Purpose: verify that validateStagedFileChanges detects .sf/ paths in the git
 * staging area and emits a high-severity warning via logWarning.
 * Consumer: CI gate via `npx vitest run ...`.
 */
import { beforeEach, describe, expect, test, vi } from "vitest";
import { validateStagedFileChanges } from "../safety/file-change-validator.js";

// ─── Mock logWarning ───────────────────────────────────────────────────────
vi.mock("../workflow-logger.js", () => ({
	logWarning: vi.fn(),
}));

import { logWarning } from "../workflow-logger.js";

const mockLogWarning = vi.mocked(logWarning);

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

describe("validateStagedFileChanges", () => {
	beforeEach(() => {
		mockLogWarning.mockClear();
	});

	test("returns hasSfPaths=false when no files are staged", () => {
		const result = validateStagedFileChanges("/fake/base", []);
		expect(result.hasSfPaths).toBe(false);
		expect(result.sfPaths).toEqual([]);
		expect(mockLogWarning).not.toHaveBeenCalled();
	});

	test("returns hasSfPaths=false when only non-.sf/ files are staged", () => {
		const result = validateStagedFileChanges("/fake/base", [
			"src/index.ts",
			"README.md",
			"docs/plans/test.md",
		]);
		expect(result.hasSfPaths).toBe(false);
		expect(result.sfPaths).toEqual([]);
		expect(mockLogWarning).not.toHaveBeenCalled();
	});

	test("returns hasSfPaths=true with one .sf/ path and emits high-severity warning", () => {
		const result = validateStagedFileChanges("/fake/base", [
			".sf/milestones/M009/M009-ROADMAP.md",
		]);
		expect(result.hasSfPaths).toBe(true);
		expect(result.sfPaths).toEqual([".sf/milestones/M009/M009-ROADMAP.md"]);
		expect(mockLogWarning).toHaveBeenCalledOnce();
		const [category, msg] = mockLogWarning.mock.calls[0];
		expect(category).toBe("safety");
		expect(msg).toContain("High severity");
		expect(msg).toContain(".sf/");
		expect(msg).toContain("git restore --staged .sf/");
	});

	test("returns hasSfPaths=true with multiple .sf/ paths", () => {
		const result = validateStagedFileChanges("/fake/base", [
			".sf/milestones/M001/M001-SUMMARY.md",
			".gitignore",
			".sf/milestones/M002/M002-ROADMAP.md",
		]);
		expect(result.hasSfPaths).toBe(true);
		expect(result.sfPaths).toEqual([
			".sf/milestones/M001/M001-SUMMARY.md",
			".sf/milestones/M002/M002-ROADMAP.md",
		]);
		expect(mockLogWarning).toHaveBeenCalledOnce();
	});

	test("filters out .sf/ paths nested under other directories (not first segment)", () => {
		const result = validateStagedFileChanges("/fake/base", [
			"foo/.sf/bar.txt",
			"bar/.sf/baz.txt",
			"src/.sf/config",
		]);
		expect(result.hasSfPaths).toBe(false);
		expect(result.sfPaths).toEqual([]);
		expect(mockLogWarning).not.toHaveBeenCalled();
	});

	test("handles Windows backslash paths correctly", () => {
		const result = validateStagedFileChanges("/fake/base", [
			".sf\\milestones\\M009\\M009-ROADMAP.md",
		]);
		// After normalization: .sf/milestones/... → first segment = ".sf" → DETECTED
		expect(result.hasSfPaths).toBe(true);
		expect(result.sfPaths).toEqual([".sf\\milestones\\M009\\M009-ROADMAP.md"]);
		expect(mockLogWarning).toHaveBeenCalledOnce();
	});

	test("null staged paths returns hasSfPaths=false without high-severity warning", () => {
		const result = validateStagedFileChanges("/fake/base", null);
		expect(result.hasSfPaths).toBe(false);
		expect(result.sfPaths).toEqual([]);
		// The high-severity .sf/ warning should NOT be emitted when staged paths is null
		const ourWarning = mockLogWarning.mock.calls.find(([, m]) =>
			m.includes("High severity"),
		);
		expect(ourWarning).toBeUndefined();
	});
});
