/**
 * Tests for fork-skillset command
 * Tests that the command correctly copies an existing skillset to a new name
 */
import * as fs from "fs/promises";
import * as os from "os";
import * as path from "path";
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { forkSkillsetMain } from "./forkSkillset.js";
// Mock os.homedir so getNoriSkillsetsDir() resolves to the test directory
vi.mock("os", async (importOriginal) => {
    const actual = await importOriginal();
    return {
        ...actual,
        homedir: vi.fn().mockReturnValue(actual.homedir()),
    };
});
// Mock @clack/prompts for output
const mockLogError = vi.fn();
const mockNote = vi.fn();
const mockOutro = vi.fn();
vi.mock("@clack/prompts", () => ({
    log: {
        error: (msg) => mockLogError(msg),
    },
    note: (content, title) => mockNote(content, title),
    outro: (msg) => mockOutro(msg),
}));
// Mock process.exit
const mockExit = vi
    .spyOn(process, "exit")
    .mockImplementation(() => undefined);
describe("forkSkillsetMain", () => {
    let testHomeDir;
    let skillsetsDir;
    beforeEach(async () => {
        testHomeDir = await fs.mkdtemp(path.join(os.tmpdir(), "fork-skillset-test-"));
        vi.mocked(os.homedir).mockReturnValue(testHomeDir);
        skillsetsDir = path.join(testHomeDir, ".nori", "profiles");
        await fs.mkdir(skillsetsDir, { recursive: true });
        mockLogError.mockClear();
        mockNote.mockClear();
        mockOutro.mockClear();
        mockExit.mockClear();
    });
    afterEach(async () => {
        if (testHomeDir) {
            await fs.rm(testHomeDir, { recursive: true, force: true });
        }
    });
    it("should copy a flat skillset to a new name with all contents", async () => {
        // Create source skillset with multiple files/directories
        const sourceDir = path.join(skillsetsDir, "senior-swe");
        await fs.mkdir(sourceDir, { recursive: true });
        await fs.writeFile(path.join(sourceDir, "nori.json"), JSON.stringify({ name: "senior-swe", version: "1.0.0" }));
        const skillsDir = path.join(sourceDir, "skills", "my-skill");
        await fs.mkdir(skillsDir, { recursive: true });
        await fs.writeFile(path.join(skillsDir, "SKILL.md"), "# My Skill");
        const result = await forkSkillsetMain({
            baseSkillset: "senior-swe",
            newSkillset: "my-custom",
        });
        // Verify destination exists with all contents
        const destDir = path.join(skillsetsDir, "my-custom");
        const skillMd = await fs.readFile(path.join(destDir, "skills", "my-skill", "SKILL.md"), "utf-8");
        expect(skillMd).toBe("# My Skill");
        const noriJson = JSON.parse(await fs.readFile(path.join(destDir, "nori.json"), "utf-8"));
        expect(noriJson.name).toBe("my-custom");
        expect(noriJson.version).toBe("1.0.0");
        // Verify return status contains both source and destination
        expect(result.success).toBe(true);
        expect(result.message).toContain("senior-swe");
        expect(result.message).toContain("my-custom");
        expect(mockOutro).not.toHaveBeenCalled();
        expect(mockExit).not.toHaveBeenCalled();
    });
    it("should return failure status when base skillset does not exist", async () => {
        const result = await forkSkillsetMain({
            baseSkillset: "nonexistent",
            newSkillset: "my-copy",
        });
        expect(mockLogError).toHaveBeenCalledWith(expect.stringContaining("nonexistent"));
        expect(result.success).toBe(false);
    });
    it("should return failure status when base skillset directory exists but has no nori.json", async () => {
        // Create a directory that is not a valid skillset
        const invalidDir = path.join(skillsetsDir, "not-a-skillset");
        await fs.mkdir(invalidDir, { recursive: true });
        await fs.writeFile(path.join(invalidDir, "readme.txt"), "not a profile");
        const result = await forkSkillsetMain({
            baseSkillset: "not-a-skillset",
            newSkillset: "my-copy",
        });
        expect(mockLogError).toHaveBeenCalledWith(expect.stringContaining("not-a-skillset"));
        expect(result.success).toBe(false);
    });
    it("should error when destination skillset already exists", async () => {
        // Create source
        const sourceDir = path.join(skillsetsDir, "base-profile");
        await fs.mkdir(sourceDir, { recursive: true });
        await fs.writeFile(path.join(sourceDir, "nori.json"), JSON.stringify({ name: "base-profile", version: "1.0.0" }));
        // Create destination that already exists
        const destDir = path.join(skillsetsDir, "existing-profile");
        await fs.mkdir(destDir, { recursive: true });
        await fs.writeFile(path.join(destDir, "nori.json"), JSON.stringify({ name: "existing-profile", version: "1.0.0" }));
        const result = await forkSkillsetMain({
            baseSkillset: "base-profile",
            newSkillset: "existing-profile",
        });
        expect(mockLogError).toHaveBeenCalledWith(expect.stringContaining("existing-profile"));
        expect(mockLogError).toHaveBeenCalledWith(expect.stringContaining("already exists"));
        expect(result.success).toBe(false);
    });
    it("should work with namespaced profile names", async () => {
        // Create a namespaced source skillset
        const sourceDir = path.join(skillsetsDir, "myorg", "base-profile");
        await fs.mkdir(sourceDir, { recursive: true });
        await fs.writeFile(path.join(sourceDir, "nori.json"), JSON.stringify({ name: "base-profile", version: "1.0.0" }));
        await forkSkillsetMain({
            baseSkillset: "myorg/base-profile",
            newSkillset: "myorg/forked-profile",
        });
        // Verify destination exists with updated name
        const destDir = path.join(skillsetsDir, "myorg", "forked-profile");
        const noriJson = JSON.parse(await fs.readFile(path.join(destDir, "nori.json"), "utf-8"));
        expect(noriJson.name).toBe("myorg/forked-profile");
        expect(noriJson.version).toBe("1.0.0");
        expect(mockExit).not.toHaveBeenCalled();
    });
    it("should create parent directory for namespaced destination", async () => {
        // Create a flat source
        const sourceDir = path.join(skillsetsDir, "senior-swe");
        await fs.mkdir(sourceDir, { recursive: true });
        await fs.writeFile(path.join(sourceDir, "nori.json"), JSON.stringify({ name: "senior-swe", version: "1.0.0" }));
        // Fork to a new org namespace that doesn't exist yet
        await forkSkillsetMain({
            baseSkillset: "senior-swe",
            newSkillset: "neworg/my-fork",
        });
        const destDir = path.join(skillsetsDir, "neworg", "my-fork");
        const noriJson = JSON.parse(await fs.readFile(path.join(destDir, "nori.json"), "utf-8"));
        expect(noriJson.name).toBe("neworg/my-fork");
        expect(noriJson.version).toBe("1.0.0");
        expect(mockExit).not.toHaveBeenCalled();
    });
    it("should print instructions for switching and editing after fork", async () => {
        const sourceDir = path.join(skillsetsDir, "senior-swe");
        await fs.mkdir(sourceDir, { recursive: true });
        await fs.writeFile(path.join(sourceDir, "nori.json"), JSON.stringify({ name: "senior-swe", version: "1.0.0" }));
        await forkSkillsetMain({
            baseSkillset: "senior-swe",
            newSkillset: "my-fork",
        });
        // Should show note with next steps containing switch and edit instructions
        expect(mockNote).toHaveBeenCalledWith(expect.stringContaining("switch my-fork"), "Next Steps");
        expect(mockNote).toHaveBeenCalledWith(expect.stringContaining("~/.nori/profiles/my-fork"), "Next Steps");
    });
});
//# sourceMappingURL=forkSkillset.test.js.map