/**
 * Tests for shared skills loader
 * Verifies that skillsLoader copies skills from a skillset into the
 * agent's skills directory, applying template substitution to .md files.
 */
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 { skillsLoader } from "../../../cli/features/shared/skillsLoader.js";
// Suppress clack output during tests
vi.mock("@clack/prompts", () => ({
    log: { info: vi.fn(), warn: vi.fn(), success: vi.fn(), error: vi.fn() },
    note: vi.fn(),
}));
// Mock os.homedir to point at the temp directory
let mockHomeDir;
vi.mock("os", async (importOriginal) => {
    const actual = (await importOriginal());
    return {
        ...actual,
        homedir: () => mockHomeDir,
        tmpdir: actual.tmpdir,
    };
});
let mockNoriDir;
// Mock bundled skillsets installer
const mockCopyBundledSkills = vi.fn();
vi.mock("@/cli/features/bundled-skillsets/installer.js", () => ({
    getBundledSkillsDir: () => "/nonexistent-bundled-skills",
    copyBundledSkills: (...args) => mockCopyBundledSkills(...args),
}));
// ---- helpers ----------------------------------------------------------------
const createTestAgent = (args) => {
    const { agentDir } = args;
    const dirName = path.basename(agentDir);
    return {
        name: "claude-code",
        displayName: "Test Agent",
        description: "Test agent for shared loader tests",
        getAgentDir: (a) => path.join(a.installDir, dirName),
        getSkillsDir: (a) => path.join(a.installDir, dirName, "skills"),
        getSubagentsDir: (a) => path.join(a.installDir, dirName, "agents"),
        getSlashcommandsDir: (a) => path.join(a.installDir, dirName, "commands"),
        getInstructionsFilePath: (a) => path.join(a.installDir, dirName, "CLAUDE.md"),
        getLoaders: () => [],
    };
};
const createTestConfig = (args) => {
    const { installDir, activeSkillset } = args;
    return { installDir, activeSkillset };
};
const createTestSkillset = async (args) => {
    const { skillsetsDir, skillsetName, skills, extraFiles } = args;
    const skillsetDir = path.join(skillsetsDir, skillsetName);
    await fs.mkdir(skillsetDir, { recursive: true });
    await fs.writeFile(path.join(skillsetDir, "nori.json"), JSON.stringify({ name: skillsetName, version: "1.0.0" }));
    let skillsDir = null;
    if (skills != null && Object.keys(skills).length > 0) {
        const sDir = path.join(skillsetDir, "skills");
        for (const [skillName, content] of Object.entries(skills)) {
            const dir = path.join(sDir, skillName);
            await fs.mkdir(dir, { recursive: true });
            await fs.writeFile(path.join(dir, "SKILL.md"), content);
        }
        // Write any extra non-md files into skills directory
        if (extraFiles != null) {
            for (const [relPath, content] of Object.entries(extraFiles)) {
                const fullPath = path.join(sDir, relPath);
                await fs.mkdir(path.dirname(fullPath), { recursive: true });
                await fs.writeFile(fullPath, content);
            }
        }
        skillsDir = sDir;
    }
    return {
        name: skillsetName,
        dir: skillsetDir,
        metadata: { name: skillsetName, version: "1.0.0" },
        skillsDir,
        configFilePath: null,
        slashcommandsDir: null,
        subagentsDir: null,
        mcpDir: null,
    };
};
// ---- test data --------------------------------------------------------------
const TEST_SKILLS = {
    "using-skills": [
        "---",
        "name: Getting Started with Abilities",
        "description: Describes how to use abilities.",
        "---",
        "# Using Skills",
        "",
        "Skills at {{skills_dir}}/some-skill/SKILL.md",
    ].join("\n"),
    "creating-skills": [
        "---",
        "name: Creating-Skills",
        "description: Use when you need to create a new custom skill.",
        "---",
        "# Creating Skills",
    ].join("\n"),
};
// ---- tests ------------------------------------------------------------------
describe("skillsLoader", () => {
    let tempDir;
    let agentDir;
    let skillsDir;
    let noriProfilesDir;
    let agent;
    beforeEach(async () => {
        tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "skills-loader-test-"));
        agentDir = path.join(tempDir, ".test-agent");
        skillsDir = path.join(agentDir, "skills");
        mockHomeDir = tempDir;
        mockNoriDir = path.join(tempDir, ".nori");
        noriProfilesDir = path.join(mockNoriDir, "profiles");
        await fs.mkdir(agentDir, { recursive: true });
        await fs.mkdir(noriProfilesDir, { recursive: true });
        agent = createTestAgent({ agentDir });
        mockCopyBundledSkills.mockResolvedValue(undefined);
    });
    afterEach(async () => {
        await fs.rm(tempDir, { recursive: true, force: true });
        vi.clearAllMocks();
    });
    describe("copying skills", () => {
        it("should copy skills from skillset.skillsDir to the agent skills directory", async () => {
            const config = createTestConfig({
                installDir: tempDir,
                activeSkillset: "test-skillset",
            });
            const skillset = await createTestSkillset({
                skillsetsDir: noriProfilesDir,
                skillsetName: "test-skillset",
                skills: TEST_SKILLS,
            });
            await skillsLoader.run({ agent, config, skillset });
            // Verify skills were copied
            const usingSkillsPath = path.join(skillsDir, "using-skills", "SKILL.md");
            const creatingSkillsPath = path.join(skillsDir, "creating-skills", "SKILL.md");
            const usingSkillsExists = await fs
                .access(usingSkillsPath)
                .then(() => true)
                .catch(() => false);
            const creatingSkillsExists = await fs
                .access(creatingSkillsPath)
                .then(() => true)
                .catch(() => false);
            expect(usingSkillsExists).toBe(true);
            expect(creatingSkillsExists).toBe(true);
        });
    });
    describe("template substitution", () => {
        it("should apply template substitution to .md files", async () => {
            const config = createTestConfig({
                installDir: tempDir,
                activeSkillset: "template-test",
            });
            const skillset = await createTestSkillset({
                skillsetsDir: noriProfilesDir,
                skillsetName: "template-test",
                skills: TEST_SKILLS,
            });
            await skillsLoader.run({ agent, config, skillset });
            const content = await fs.readFile(path.join(skillsDir, "using-skills", "SKILL.md"), "utf-8");
            expect(content).not.toContain("{{skills_dir}}");
            // Should have been replaced with the actual agent skills path
            expect(content).toContain(path.join(agentDir, "skills", "some-skill", "SKILL.md"));
        });
        it("should copy non-.md files directly without template substitution", async () => {
            const config = createTestConfig({
                installDir: tempDir,
                activeSkillset: "non-md-test",
            });
            const skillset = await createTestSkillset({
                skillsetsDir: noriProfilesDir,
                skillsetName: "non-md-test",
                skills: {
                    "my-skill": "---\nname: My Skill\n---\n# My Skill",
                },
                extraFiles: {
                    "my-skill/script.sh": "#!/bin/bash\necho {{skills_dir}}",
                },
            });
            await skillsLoader.run({ agent, config, skillset });
            const scriptContent = await fs.readFile(path.join(skillsDir, "my-skill", "script.sh"), "utf-8");
            // Non-md files should NOT have template substitution applied
            expect(scriptContent).toContain("{{skills_dir}}");
        });
    });
    describe("missing skillsDir", () => {
        it("should handle missing skillset.skillsDir gracefully", async () => {
            const config = createTestConfig({
                installDir: tempDir,
                activeSkillset: "empty-test",
            });
            const skillset = await createTestSkillset({
                skillsetsDir: noriProfilesDir,
                skillsetName: "empty-test",
                // no skills -> skillsDir will be null
            });
            // Should not throw
            await expect(skillsLoader.run({ agent, config, skillset })).resolves.not.toThrow();
        });
    });
    describe("dotfile preservation", () => {
        it("should preserve top-level dotfile entries in destSkillsDir across reinstall", async () => {
            const config = createTestConfig({
                installDir: tempDir,
                activeSkillset: "preserve-dotfiles-test",
            });
            const skillset = await createTestSkillset({
                skillsetsDir: noriProfilesDir,
                skillsetName: "preserve-dotfiles-test",
                skills: TEST_SKILLS,
            });
            // Simulate an external system (e.g. codex) shipping .system/ alongside skills
            const systemDir = path.join(skillsDir, ".system");
            await fs.mkdir(systemDir, { recursive: true });
            const markerPath = path.join(systemDir, "marker.txt");
            await fs.writeFile(markerPath, "external content");
            await skillsLoader.run({ agent, config, skillset });
            // The dotfile entry should survive the reinstall with original content
            const markerContent = await fs.readFile(markerPath, "utf-8");
            expect(markerContent).toBe("external content");
            // And the skillset's skills should still be installed
            const usingSkillsExists = await fs
                .access(path.join(skillsDir, "using-skills", "SKILL.md"))
                .then(() => true)
                .catch(() => false);
            expect(usingSkillsExists).toBe(true);
        });
        it("should still remove a stale non-dotfile skill on reinstall", async () => {
            const config = createTestConfig({
                installDir: tempDir,
                activeSkillset: "stale-removal-test",
            });
            const skillset = await createTestSkillset({
                skillsetsDir: noriProfilesDir,
                skillsetName: "stale-removal-test",
                skills: TEST_SKILLS,
            });
            // Simulate a leftover from a previous skillset
            const staleSkillDir = path.join(skillsDir, "old-skill");
            await fs.mkdir(staleSkillDir, { recursive: true });
            const stalePath = path.join(staleSkillDir, "SKILL.md");
            await fs.writeFile(stalePath, "stale");
            await skillsLoader.run({ agent, config, skillset });
            const staleExists = await fs
                .access(stalePath)
                .then(() => true)
                .catch(() => false);
            expect(staleExists).toBe(false);
        });
    });
    describe("bundled skills", () => {
        it("should copy bundled skills during installation", async () => {
            const config = createTestConfig({
                installDir: tempDir,
                activeSkillset: "bundled-test",
            });
            const skillset = await createTestSkillset({
                skillsetsDir: noriProfilesDir,
                skillsetName: "bundled-test",
                skills: TEST_SKILLS,
            });
            await skillsLoader.run({ agent, config, skillset });
            // Verify copyBundledSkills was called with the correct destination
            expect(mockCopyBundledSkills).toHaveBeenCalled();
            const callArgs = mockCopyBundledSkills.mock.calls[0][0];
            expect(callArgs.destSkillsDir).toBe(skillsDir);
        });
    });
});
//# sourceMappingURL=skillsLoader.test.js.map