// Unit tests for the installer's plugin-install pure functions.
// Tests are ephemeral per the sprint workflow — they prove behaviour
// during the sprint; the installer's runtime exercise on a real Pi is the
// permanent verification artifact.
import test from "node:test";
import assert from "node:assert/strict";
import { parsePluginList, computeInstallActions, parseExternalPlugins, } from "../lib/plugin-install.js";
test("parsePluginList extracts name@marketplace tuples", () => {
    const stdout = `
  ❯ work@maxy-platform
    Version: 0.1.0
    Scope: local

  ❯ telegram@claude-plugins-official
    Version: 1.2.3
`;
    const out = parsePluginList(stdout);
    assert.deepEqual(out, [
        { name: "work", marketplace: "maxy-platform" },
        { name: "telegram", marketplace: "claude-plugins-official" },
    ]);
});
test("parsePluginList returns empty array for empty stdout", () => {
    assert.deepEqual(parsePluginList(""), []);
    assert.deepEqual(parsePluginList("No plugins installed.\n"), []);
});
// `dirSrc` is the directory-source-marketplace discriminator passed as the
// third argument. maxy-platform is directory-source; claude-plugins-official
// is a remote (GitHub) marketplace that keeps version-pinned idempotence.
const dirSrc = new Set(["maxy-platform"]);
test("computeInstallActions: every desired plugin needs installing when none present", () => {
    const desired = [
        { name: "admin", marketplace: "maxy-platform" },
        { name: "telegram", marketplace: "claude-plugins-official" },
    ];
    const { toInstall, toResync, alreadyInstalled } = computeInstallActions(desired, [], dirSrc);
    assert.equal(toInstall.length, 2);
    assert.equal(toResync.length, 0);
    assert.equal(alreadyInstalled.length, 0);
});
test("computeInstallActions: directory-source plugin already installed goes to resync, not skip", () => {
    const desired = [
        { name: "admin", marketplace: "maxy-platform" },
        { name: "telegram", marketplace: "claude-plugins-official" },
    ];
    const installed = [
        { name: "admin", marketplace: "maxy-platform" },
        { name: "telegram", marketplace: "claude-plugins-official" },
    ];
    const { toInstall, toResync, alreadyInstalled } = computeInstallActions(desired, installed, dirSrc);
    // admin's directory source changes in place with no version bump, so it must
    // be re-synced even though it is already in `plugin list`. The remote
    // telegram stays idempotent-skipped.
    assert.deepEqual(toInstall.map(p => p.name), []);
    assert.deepEqual(toResync.map(p => p.name), ["admin"]);
    assert.deepEqual(alreadyInstalled.map(p => p.name), ["telegram"]);
});
test("computeInstallActions: full directory-marketplace set resyncs when every plugin already installed", () => {
    const desired = [
        { name: "admin", marketplace: "maxy-platform" },
        { name: "work", marketplace: "maxy-platform" },
    ];
    const installed = [
        { name: "admin", marketplace: "maxy-platform" },
        { name: "work", marketplace: "maxy-platform" },
    ];
    const { toInstall, toResync, alreadyInstalled } = computeInstallActions(desired, installed, dirSrc);
    assert.deepEqual(toResync.map(p => p.name), ["admin", "work"]);
    assert.equal(toInstall.length, 0);
    assert.equal(alreadyInstalled.length, 0);
});
test("computeInstallActions: remote plugin already installed stays idempotent-skipped", () => {
    const desired = [{ name: "telegram", marketplace: "claude-plugins-official" }];
    const installed = [{ name: "telegram", marketplace: "claude-plugins-official" }];
    const { toInstall, toResync, alreadyInstalled } = computeInstallActions(desired, installed, dirSrc);
    assert.equal(toInstall.length, 0);
    assert.equal(toResync.length, 0);
    assert.deepEqual(alreadyInstalled.map(p => p.name), ["telegram"]);
});
test("computeInstallActions: directory-source plugin not yet installed goes to install (fresh build is clean)", () => {
    const desired = [{ name: "admin", marketplace: "maxy-platform" }];
    const { toInstall, toResync, alreadyInstalled } = computeInstallActions(desired, [], dirSrc);
    assert.deepEqual(toInstall.map(p => p.name), ["admin"]);
    assert.equal(toResync.length, 0);
    assert.equal(alreadyInstalled.length, 0);
});
test("computeInstallActions: name-only match on a remote marketplace skips install (legacy install state)", () => {
    const desired = [{ name: "telegram", marketplace: "claude-plugins-official" }];
    const installed = [{ name: "telegram", marketplace: "" }];
    const { toInstall, toResync, alreadyInstalled } = computeInstallActions(desired, installed, dirSrc);
    assert.equal(toInstall.length, 0);
    assert.equal(toResync.length, 0);
    assert.equal(alreadyInstalled.length, 1);
});
test("computeInstallActions: name-only legacy match on a directory marketplace still resyncs", () => {
    const desired = [{ name: "admin", marketplace: "maxy-platform" }];
    const installed = [{ name: "admin", marketplace: "" }];
    const { toInstall, toResync, alreadyInstalled } = computeInstallActions(desired, installed, dirSrc);
    assert.equal(toInstall.length, 0);
    assert.deepEqual(toResync.map(p => p.name), ["admin"]);
    assert.equal(alreadyInstalled.length, 0);
});
test("parseExternalPlugins: undefined returns []", () => {
    assert.deepEqual(parseExternalPlugins(undefined), []);
    assert.deepEqual(parseExternalPlugins(null), []);
});
test("parseExternalPlugins: well-formed array round-trips", () => {
    const raw = [
        { name: "telegram", marketplace: "claude-plugins-official", channelPlugin: true },
        { name: "discord", marketplace: "claude-plugins-official" },
    ];
    const out = parseExternalPlugins(raw);
    assert.deepEqual(out, [
        { name: "telegram", marketplace: "claude-plugins-official", channelPlugin: true },
        { name: "discord", marketplace: "claude-plugins-official" },
    ]);
});
test("parseExternalPlugins: missing marketplace throws", () => {
    assert.throws(() => parseExternalPlugins([{ name: "telegram" }]));
});
test("parseExternalPlugins: missing name throws", () => {
    assert.throws(() => parseExternalPlugins([{ marketplace: "x" }]));
});
