// engine/plugin-installer/installer.ts — Per-plugin install logic with idempotency (SPEC-596 + SPEC-605)
//
// Uses `claude plugin list` to detect already-installed plugins before running
// `claude plugin install <id>`. Each install is isolated: one failure never blocks others.
import { promisify } from 'node:util';
import { execFile as nodeExecFile } from 'node:child_process';
import { classifyInstallError } from './error-classifier.js';
const DEFAULT_EXEC = promisify(nodeExecFile);
const CLI_TIMEOUT_MS = 5000;
/** Fetch the list of already-installed plugin IDs. Returns empty set if CLI unavailable. */
async function fetchInstalledPlugins(exec) {
    try {
        const { stdout } = await exec('claude', ['plugin', 'list'], { timeout: CLI_TIMEOUT_MS });
        const lines = stdout
            .split('\n')
            .map((l) => l.trim())
            .filter(Boolean);
        return new Set(lines);
    }
    catch {
        return new Set();
    }
}
function isPluginInstalled(installed, id) {
    // Normalize: strip @marketplace suffix for comparison
    // c8 ignore next — split('@') always yields a non-empty array; [0] is never undefined
    const baseId = id.split('@')[0] ?? id; /* c8 ignore next */
    return (installed.has(id) || installed.has(baseId) || [...installed].some((p) => p.includes(baseId)));
}
/**
 * Install a list of plugins, skipping already-installed ones.
 * Every CLI call is isolated in its own try/catch.
 *
 * @param plugins - Plugins to install
 * @param dryRun - When true, skip all CLI calls and return empty outcome
 * @param exec - Optional execFile override for testing
 */
export async function installPlugins(plugins, dryRun = false, exec = DEFAULT_EXEC) {
    const installed = [];
    const skipped = [];
    const failed = [];
    const failedDetails = [];
    if (plugins.length === 0 || dryRun) {
        return { installed, skipped, failed, failedDetails };
    }
    const alreadyInstalled = await fetchInstalledPlugins(exec);
    for (const plugin of plugins) {
        if (isPluginInstalled(alreadyInstalled, plugin.id)) {
            skipped.push(plugin.id);
            continue;
        }
        try {
            await exec('claude', ['plugin', 'install', plugin.id], { timeout: CLI_TIMEOUT_MS });
            installed.push(plugin.id);
        }
        catch (err) {
            // Isolated failure — classify and report, then continue
            const errorMessage = err instanceof Error ? err.message : String(err);
            failed.push(plugin.id);
            failedDetails.push(classifyInstallError(plugin.id, errorMessage));
        }
    }
    return { installed, skipped, failed, failedDetails };
}
//# sourceMappingURL=installer.js.map