// Pure-function support for the installer's plugin-install loop. The runtime
// callers in src/index.ts wrap these with the actual `spawnSync` / fs reads;
// keeping the logic side-effect-free here makes the install / configure
// branches unit-testable without stubbing every shell invocation.
/**
 * `claude plugin list` prints rows like:
 *   ❯ work@maxy-platform
 *     Version: 0.1.0
 *     Scope: local
 *
 * Parse the `<name>@<marketplace>` line so the installer can skip already-
 * installed plugins. Plugins whose row lacks the `@` suffix (legacy
 * installer state) are returned with marketplace='' so a name-only check
 * still de-dupes.
 */
export function parsePluginList(stdout) {
    const out = [];
    for (const raw of stdout.split("\n")) {
        const line = raw.replace(/^\s*❯\s+/, "").trim();
        if (!line || !/^[a-z0-9][a-z0-9-]*(@[a-zA-Z0-9-]+)?$/.test(line))
            continue;
        const atIdx = line.indexOf("@");
        if (atIdx < 0)
            out.push({ name: line, marketplace: "" });
        else
            out.push({ name: line.slice(0, atIdx), marketplace: line.slice(atIdx + 1) });
    }
    return out;
}
/**
 * Given the desired plugins, what's already installed, and which marketplaces
 * are directory sources, classify each desired plugin into one of three
 * buckets:
 *
 *   - `toResync`         — already installed AND from a directory-source
 *                          marketplace. Directory marketplaces change content
 *                          in place with no version bump, so `claude plugin
 *                          install`/`update` short-circuit on the pinned
 *                          version and never refresh the runtime cache snapshot
 *                          (Task 643). These must be uninstalled then installed
 *                          to rebuild the snapshot from the live tree.
 *   - `alreadyInstalled` — already installed AND from a remote (version-pinned)
 *                          marketplace. Keeps the original idempotent skip.
 *   - `toInstall`        — not installed yet. A fresh install builds a correct
 *                          cache snapshot, so directory-source and remote
 *                          plugins are handled identically here.
 *
 * "Already installed" matches either the exact `<name>@<marketplace>` tuple OR
 * a row with the same name and any marketplace (legacy install state). The
 * directory-source decision is keyed on the marketplace name regardless of
 * which form matched, so a legacy name-only row for a directory plugin still
 * resyncs.
 */
export function computeInstallActions(desired, installed, directorySourceMarketplaces) {
    const byName = new Map();
    for (const i of installed) {
        if (!byName.has(i.name))
            byName.set(i.name, new Set());
        byName.get(i.name).add(i.marketplace);
    }
    const toInstall = [];
    const toResync = [];
    const alreadyInstalled = [];
    for (const d of desired) {
        const marketplaces = byName.get(d.name);
        const isInstalled = !!marketplaces && (marketplaces.has(d.marketplace) || marketplaces.has(""));
        const isDirectorySource = directorySourceMarketplaces.has(d.marketplace);
        if (isInstalled && isDirectorySource) {
            toResync.push(d);
        }
        else if (isInstalled) {
            alreadyInstalled.push(d);
        }
        else {
            toInstall.push(d);
        }
    }
    return { toInstall, toResync, alreadyInstalled };
}
/** Brand-config validator. brand.json#externalPlugins is optional; when
 *  present, each entry must declare name + marketplace as non-empty
 *  strings. Other fields are optional. Returns a normalised array; throws
 *  on any malformed entry (loud failure at install time beats silent
 *  drop). */
export function parseExternalPlugins(raw) {
    if (raw === undefined || raw === null)
        return [];
    if (!Array.isArray(raw)) {
        throw new Error("brand.json#externalPlugins must be an array");
    }
    const out = [];
    for (const entry of raw) {
        if (!entry || typeof entry !== "object") {
            throw new Error("brand.json#externalPlugins entries must be objects");
        }
        const e = entry;
        if (typeof e.name !== "string" || e.name.length === 0) {
            throw new Error("brand.json#externalPlugins entry missing 'name'");
        }
        if (typeof e.marketplace !== "string" || e.marketplace.length === 0) {
            throw new Error(`brand.json#externalPlugins[${e.name}] missing 'marketplace'`);
        }
        const ref = { name: e.name, marketplace: e.marketplace };
        if (e.channelPlugin === true) {
            ref.channelPlugin = true;
        }
        out.push(ref);
    }
    return out;
}
