// Task 165 — installer must register every bundled specialist subagent
// at $CLAUDE_CONFIG_DIR/agents/<name>.md so Claude Code's `--agent <name>`
// discovery resolves. Pre-165 the installer left those names unreachable;
// every recorder spawn silently fell back to the admin agent.
import test from "node:test";
import assert from "node:assert/strict";
import { mkdtempSync, mkdirSync, writeFileSync, readlinkSync, lstatSync, rmSync, symlinkSync, readdirSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { registerSpecialistAgentsAt, SpecialistSymlinkCollision } from "../specialist-registration.js";
function makeTemplates(dir, names) {
    mkdirSync(dir, { recursive: true });
    for (const n of names)
        writeFileSync(join(dir, `${n}.md`), `---\nname: ${n}\ndescription: "x"\ntools: mcp__x__y\n---\n# ${n}\n`);
}
test("registers every bundled specialist as a symlink under agentsDir", () => {
    const templatesDir = mkdtempSync(join(tmpdir(), "t165-tpl-"));
    const agentsDir = mkdtempSync(join(tmpdir(), "t165-agents-"));
    try {
        const names = ["content-producer", "database-operator", "librarian", "personal-assistant", "project-manager", "research-assistant"];
        makeTemplates(templatesDir, names);
        const result = registerSpecialistAgentsAt(templatesDir, agentsDir);
        assert.equal(result.skipped, null);
        assert.deepEqual(result.registered.sort(), [...names].sort());
        for (const n of names) {
            const linkPath = join(agentsDir, `${n}.md`);
            const stat = lstatSync(linkPath);
            assert.ok(stat.isSymbolicLink(), `${n}.md must be a symlink`);
            const target = resolve(agentsDir, readlinkSync(linkPath));
            assert.equal(target, resolve(templatesDir, `${n}.md`));
        }
    }
    finally {
        rmSync(templatesDir, { recursive: true, force: true });
        rmSync(agentsDir, { recursive: true, force: true });
    }
});
test("re-run refreshes a stale symlink pointing at the wrong target", () => {
    const templatesDir = mkdtempSync(join(tmpdir(), "t165-tpl-"));
    const agentsDir = mkdtempSync(join(tmpdir(), "t165-agents-"));
    try {
        makeTemplates(templatesDir, ["database-operator"]);
        // Seed a stale symlink at the destination pointing somewhere else.
        const stalePath = join(templatesDir, "_stale.md");
        writeFileSync(stalePath, "stale\n");
        const linkPath = join(agentsDir, "database-operator.md");
        symlinkSync(stalePath, linkPath);
        const beforeTarget = resolve(agentsDir, readlinkSync(linkPath));
        assert.equal(beforeTarget, resolve(stalePath));
        registerSpecialistAgentsAt(templatesDir, agentsDir);
        const afterTarget = resolve(agentsDir, readlinkSync(linkPath));
        assert.equal(afterTarget, resolve(templatesDir, "database-operator.md"));
    }
    finally {
        rmSync(templatesDir, { recursive: true, force: true });
        rmSync(agentsDir, { recursive: true, force: true });
    }
});
test("idempotent — second run leaves the same symlink", () => {
    const templatesDir = mkdtempSync(join(tmpdir(), "t165-tpl-"));
    const agentsDir = mkdtempSync(join(tmpdir(), "t165-agents-"));
    try {
        makeTemplates(templatesDir, ["database-operator"]);
        registerSpecialistAgentsAt(templatesDir, agentsDir);
        const linkPath = join(agentsDir, "database-operator.md");
        const firstStat = lstatSync(linkPath);
        const firstTarget = readlinkSync(linkPath);
        registerSpecialistAgentsAt(templatesDir, agentsDir);
        const secondStat = lstatSync(linkPath);
        const secondTarget = readlinkSync(linkPath);
        assert.equal(firstTarget, secondTarget);
        assert.ok(secondStat.isSymbolicLink());
        // The number of entries in agentsDir hasn't grown.
        assert.deepEqual(readdirSync(agentsDir).sort(), ["database-operator.md"]);
    }
    finally {
        rmSync(templatesDir, { recursive: true, force: true });
        rmSync(agentsDir, { recursive: true, force: true });
    }
});
test("skips with reason=no-templates-dir when templates dir is absent", () => {
    const agentsDir = mkdtempSync(join(tmpdir(), "t165-agents-"));
    const missingDir = join(agentsDir, "_definitely-not-here");
    try {
        const r = registerSpecialistAgentsAt(missingDir, agentsDir);
        assert.equal(r.skipped, "no-templates-dir");
        assert.deepEqual(r.registered, []);
    }
    finally {
        rmSync(agentsDir, { recursive: true, force: true });
    }
});
// Task 394 — premium-bundle agents previously registered via this same
// symlink path; they now register through the native Claude Code plugin
// manifest at bundle time. The two `premium bundle:` tests that asserted
// the symlink-channel behaviour were deleted with `registerPremiumBundleAgents`.
test("collides loudly on a regular file at the destination (no silent overwrite)", () => {
    const templatesDir = mkdtempSync(join(tmpdir(), "t165-tpl-"));
    const agentsDir = mkdtempSync(join(tmpdir(), "t165-agents-"));
    try {
        makeTemplates(templatesDir, ["database-operator"]);
        // Drop a regular file at the destination — the installer must
        // never clobber it. The function under test throws
        // SpecialistSymlinkCollision; index.ts maps that to process.exit(1).
        const linkPath = join(agentsDir, "database-operator.md");
        writeFileSync(linkPath, "operator-owned\n");
        assert.throws(() => registerSpecialistAgentsAt(templatesDir, agentsDir), (err) => err instanceof SpecialistSymlinkCollision && err.linkPath === linkPath);
        // The regular file was not unlinked.
        const stat = lstatSync(linkPath);
        assert.ok(!stat.isSymbolicLink(), "regular file must remain untouched after collision");
    }
    finally {
        rmSync(templatesDir, { recursive: true, force: true });
        rmSync(agentsDir, { recursive: true, force: true });
    }
});
