import chalk from "chalk";
import { DefaultPackageManager } from "./package-manager.js";
import { prepareLifecycleHooks, runLifecycleHooks } from "./lifecycle-hooks.js";
import { SettingsManager } from "./settings-manager.js";
function reportSettingsErrors(settingsManager, context, stderr) {
    const errors = settingsManager.drainErrors();
    for (const { scope, error } of errors) {
        stderr.write(chalk.yellow(`Warning (${context}, ${scope} settings): ${error.message}`) + "\n");
        if (error.stack) {
            stderr.write(chalk.dim(error.stack) + "\n");
        }
    }
}
export function getPackageCommandUsage(appName, command) {
    switch (command) {
        case "install":
            return `${appName} install <source> [-l]`;
        case "remove":
            return `${appName} remove <source> [-l]`;
        case "update":
            return `${appName} update [source]`;
        case "list":
            return `${appName} list`;
    }
}
function printPackageCommandHelp(appName, command, stdout) {
    switch (command) {
        case "install":
            stdout.write(`${chalk.bold("Usage:")}
  ${getPackageCommandUsage(appName, "install")}

Install a package, add it to settings, and run lifecycle hooks.

Options:
  -l, --local    Install project-locally (.nerf/settings.json)

Examples:
  ${appName} install npm:@foo/bar
  ${appName} install git:github.com/user/repo
  ${appName} install git:git@github.com:user/repo
  ${appName} install https://github.com/user/repo
  ${appName} install ssh://git@github.com/user/repo
  ${appName} install ./local/path
`);
            return;
        case "remove":
            stdout.write(`${chalk.bold("Usage:")}
  ${getPackageCommandUsage(appName, "remove")}

Remove a package and its source from settings.

Options:
  -l, --local    Remove from project settings (.nerf/settings.json)

Example:
  ${appName} remove npm:@foo/bar
`);
            return;
        case "update":
            stdout.write(`${chalk.bold("Usage:")}
  ${getPackageCommandUsage(appName, "update")}

Update installed packages.
If <source> is provided, only that package is updated.
`);
            return;
        case "list":
            stdout.write(`${chalk.bold("Usage:")}
  ${getPackageCommandUsage(appName, "list")}

List installed packages from user and project settings.
`);
            return;
    }
}
export function parsePackageCommand(args, allowedCommands) {
    const [command, ...rest] = args;
    if (command !== "install" && command !== "remove" && command !== "update" && command !== "list") {
        return undefined;
    }
    if (allowedCommands && !allowedCommands.has(command)) {
        return undefined;
    }
    let local = false;
    let help = false;
    let invalidOption;
    let source;
    for (const arg of rest) {
        if (arg === "-h" || arg === "--help") {
            help = true;
            continue;
        }
        if (arg === "-l" || arg === "--local") {
            if (command === "install" || command === "remove") {
                local = true;
            }
            else {
                invalidOption = invalidOption ?? arg;
            }
            continue;
        }
        if (arg.startsWith("-")) {
            invalidOption = invalidOption ?? arg;
            continue;
        }
        if (!source) {
            source = arg;
        }
    }
    return { command, source, local, help, invalidOption };
}
export async function runPackageCommand(options) {
    const stdout = options.stdout ?? process.stdout;
    const stderr = options.stderr ?? process.stderr;
    const parsed = parsePackageCommand(options.args, options.allowedCommands);
    if (!parsed) {
        return { handled: false, exitCode: 0 };
    }
    if (parsed.help) {
        printPackageCommandHelp(options.appName, parsed.command, stdout);
        return { handled: true, exitCode: 0 };
    }
    if (parsed.invalidOption) {
        stderr.write(chalk.red(`Unknown option ${parsed.invalidOption} for "${parsed.command}".`) + "\n");
        stderr.write(chalk.dim(`Use "${options.appName} --help" or "${getPackageCommandUsage(options.appName, parsed.command)}".`) + "\n");
        return { handled: true, exitCode: 1 };
    }
    const source = parsed.source;
    if ((parsed.command === "install" || parsed.command === "remove") && !source) {
        stderr.write(chalk.red(`Missing ${parsed.command} source.`) + "\n");
        stderr.write(chalk.dim(`Usage: ${getPackageCommandUsage(options.appName, parsed.command)}`) + "\n");
        return { handled: true, exitCode: 1 };
    }
    const settingsManager = SettingsManager.create(options.cwd, options.agentDir);
    reportSettingsErrors(settingsManager, "package command", stderr);
    const packageManager = new DefaultPackageManager({
        cwd: options.cwd,
        agentDir: options.agentDir,
        settingsManager,
    });
    packageManager.setProgressCallback((event) => {
        if (event.type === "start" && event.message) {
            stdout.write(chalk.dim(`${event.message}\n`));
        }
    });
    try {
        switch (parsed.command) {
            case "install": {
                const lifecycleOptions = {
                    source: source,
                    local: parsed.local,
                    cwd: options.cwd,
                    agentDir: options.agentDir,
                    appName: options.appName,
                    packageManager,
                    stdout,
                    stderr,
                };
                const beforeInstallHooks = await prepareLifecycleHooks(lifecycleOptions, "source");
                const beforeInstallResult = await runLifecycleHooks(beforeInstallHooks, "beforeInstall");
                await packageManager.install(source, { local: parsed.local });
                packageManager.addSourceToSettings(source, { local: parsed.local });
                const afterInstallHooks = await prepareLifecycleHooks(lifecycleOptions, "installed", {
                    verifyRuntimeDependencies: true,
                });
                const afterInstallResult = await runLifecycleHooks(afterInstallHooks, "afterInstall");
                const hookErrors = beforeInstallResult.hookErrors + afterInstallResult.hookErrors;
                if (hookErrors > 0) {
                    stderr.write(chalk.yellow(`Lifecycle hooks completed with ${hookErrors} hook error(s).`) + "\n");
                }
                stdout.write(chalk.green(`Installed ${source}`) + "\n");
                return { handled: true, exitCode: 0 };
            }
            case "remove": {
                const lifecycleOptions = {
                    source: source,
                    local: parsed.local,
                    cwd: options.cwd,
                    agentDir: options.agentDir,
                    appName: options.appName,
                    packageManager,
                    stdout,
                    stderr,
                };
                const removeHooks = await prepareLifecycleHooks(lifecycleOptions, "installed");
                const beforeRemoveResult = await runLifecycleHooks(removeHooks, "beforeRemove");
                await packageManager.remove(source, { local: parsed.local });
                const removed = packageManager.removeSourceFromSettings(source, { local: parsed.local });
                const afterRemoveResult = await runLifecycleHooks(removeHooks, "afterRemove");
                const hookErrors = beforeRemoveResult.hookErrors + afterRemoveResult.hookErrors;
                if (hookErrors > 0) {
                    stderr.write(chalk.yellow(`Lifecycle hooks completed with ${hookErrors} hook error(s).`) + "\n");
                }
                if (!removed) {
                    stderr.write(chalk.red(`No matching package found for ${source}`) + "\n");
                    return { handled: true, exitCode: 1 };
                }
                stdout.write(chalk.green(`Removed ${source}`) + "\n");
                return { handled: true, exitCode: 0 };
            }
            case "list": {
                const globalSettings = settingsManager.getGlobalSettings();
                const projectSettings = settingsManager.getProjectSettings();
                const globalPackages = globalSettings.packages ?? [];
                const projectPackages = projectSettings.packages ?? [];
                if (globalPackages.length === 0 && projectPackages.length === 0) {
                    stdout.write(chalk.dim("No packages installed.") + "\n");
                    return { handled: true, exitCode: 0 };
                }
                const formatPackage = (pkg, scope) => {
                    const pkgSource = typeof pkg === "string" ? pkg : pkg.source;
                    const filtered = typeof pkg === "object";
                    const display = filtered ? `${pkgSource} (filtered)` : pkgSource;
                    stdout.write(`  ${display}\n`);
                    const path = packageManager.getInstalledPath(pkgSource, scope);
                    if (path) {
                        stdout.write(chalk.dim(`    ${path}`) + "\n");
                    }
                };
                if (globalPackages.length > 0) {
                    stdout.write(chalk.bold("User packages:") + "\n");
                    for (const pkg of globalPackages) {
                        formatPackage(pkg, "user");
                    }
                }
                if (projectPackages.length > 0) {
                    if (globalPackages.length > 0)
                        stdout.write("\n");
                    stdout.write(chalk.bold("Project packages:") + "\n");
                    for (const pkg of projectPackages) {
                        formatPackage(pkg, "project");
                    }
                }
                return { handled: true, exitCode: 0 };
            }
            case "update":
                await packageManager.update(source);
                if (source) {
                    stdout.write(chalk.green(`Updated ${source}`) + "\n");
                }
                else {
                    stdout.write(chalk.green("Updated packages") + "\n");
                }
                return { handled: true, exitCode: 0 };
        }
    }
    catch (error) {
        const message = error instanceof Error ? error.message : "Unknown package command error";
        stderr.write(chalk.red(`Error: ${message}`) + "\n");
        return { handled: true, exitCode: 1 };
    }
}
//# sourceMappingURL=package-commands.js.map