import chalk from "chalk";
import { spawn } from "child_process";
import { selectConfig } from "./cli/config-selector.js";
import { APP_NAME, getAgentDir, getSelfUpdateCommand, getSelfUpdateUnavailableInstruction, PACKAGE_NAME, VERSION, } from "./config.js";
import { DefaultPackageManager } from "./core/package-manager.js";
import { SettingsManager } from "./core/settings-manager.js";
import { shouldUseWindowsShell } from "./utils/child-process.js";
import { getLatestPiVersion, isNewerPackageVersion } from "./utils/version-check.js";
const REGISTRY_URL = "https://raw.githubusercontent.com/eminent337/aery-extensions/main/registry.json";
async function resolveRegistrySource(name) {
    // Only resolve plain names (no slashes, colons, dots, or http)
    if (/[/:.@]/.test(name) || name.startsWith("http"))
        return null;
    try {
        const res = await fetch(REGISTRY_URL);
        if (!res.ok)
            return null;
        const registry = (await res.json());
        const pack = registry.packs?.[name];
        if (!pack || pack.coming_soon)
            return null;
        return pack.install ?? (pack.source ? `https://github.com/${pack.source}` : null);
    }
    catch {
        return null;
    }
}
function reportSettingsErrors(settingsManager, context) {
    const errors = settingsManager.drainErrors();
    for (const { scope, error } of errors) {
        console.error(chalk.yellow(`Warning (${context}, ${scope} settings): ${error.message}`));
        if (error.stack) {
            console.error(chalk.dim(error.stack));
        }
    }
}
function getPackageCommandUsage(command) {
    switch (command) {
        case "install":
            return `${APP_NAME} install <source> [-l]`;
        case "remove":
            return `${APP_NAME} remove <source> [-l]`;
        case "update":
            return `${APP_NAME} update [source|self|aery] [--self] [--extensions] [--extension <source>] [--force]`;
        case "list":
            return `${APP_NAME} list`;
    }
}
function printPackageCommandHelp(command) {
    switch (command) {
        case "install":
            console.log(`${chalk.bold("Usage:")}
  ${getPackageCommandUsage("install")}

Install a package and add it to settings.

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

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

Remove a package and its source from settings.
Alias: ${APP_NAME} uninstall <source> [-l]

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

Examples:
  ${APP_NAME} remove npm:@foo/bar
  ${APP_NAME} uninstall npm:@foo/bar
`);
            return;
        case "update":
            console.log(`${chalk.bold("Usage:")}
  ${getPackageCommandUsage("update")}

Update aery and installed packages.

Options:
  --self                  Update aery only
  --extensions            Update installed packages only
  --extension <source>    Update one package only
  --force                 Reinstall aery even if the current version is latest

Short forms:
  ${APP_NAME} update                Update aery and all extensions
  ${APP_NAME} update <source>       Update one package
  ${APP_NAME} update aery             Update aery only (self works as alias to aery)
`);
            return;
        case "list":
            console.log(`${chalk.bold("Usage:")}
  ${getPackageCommandUsage("list")}

List installed packages from user and project settings.
`);
            return;
    }
}
function parsePackageCommand(args) {
    const [rawCommand, ...rest] = args;
    let command;
    if (rawCommand === "uninstall") {
        command = "remove";
    }
    else if (rawCommand === "install" || rawCommand === "remove" || rawCommand === "update" || rawCommand === "list") {
        command = rawCommand;
    }
    if (!command) {
        return undefined;
    }
    let local = false;
    let force = false;
    let help = false;
    let listRegistry = false;
    let invalidOption;
    let invalidArgument;
    let missingOptionValue;
    let conflictingOptions;
    let source;
    let selfFlag = false;
    let extensionsFlag = false;
    let extensionFlagSource;
    for (let index = 0; index < rest.length; index++) {
        const arg = rest[index];
        if (arg === "-h" || arg === "--help") {
            help = true;
            continue;
        }
        if (arg === "--list" && command === "install") {
            listRegistry = true;
            continue;
        }
        if (arg === "-l" || arg === "--local") {
            if (command === "install" || command === "remove") {
                local = true;
            }
            else {
                invalidOption = invalidOption ?? arg;
            }
            continue;
        }
        if (arg === "--self") {
            if (command === "update") {
                selfFlag = true;
            }
            else {
                invalidOption = invalidOption ?? arg;
            }
            continue;
        }
        if (arg === "--extensions") {
            if (command === "update") {
                extensionsFlag = true;
            }
            else {
                invalidOption = invalidOption ?? arg;
            }
            continue;
        }
        if (arg === "--force") {
            if (command === "update") {
                force = true;
            }
            else {
                invalidOption = invalidOption ?? arg;
            }
            continue;
        }
        if (arg === "--extension") {
            if (command !== "update") {
                invalidOption = invalidOption ?? arg;
                continue;
            }
            const value = rest[index + 1];
            if (!value || value.startsWith("-")) {
                missingOptionValue = missingOptionValue ?? arg;
            }
            else if (extensionFlagSource) {
                conflictingOptions = conflictingOptions ?? "--extension can only be provided once";
                index++;
            }
            else {
                extensionFlagSource = value;
                index++;
            }
            continue;
        }
        if (arg.startsWith("-")) {
            invalidOption = invalidOption ?? arg;
            continue;
        }
        if (!source) {
            source = arg;
        }
        else {
            invalidArgument = invalidArgument ?? arg;
        }
    }
    let updateTarget;
    if (command === "update") {
        if (extensionFlagSource) {
            if (selfFlag || extensionsFlag) {
                conflictingOptions = conflictingOptions ?? "--extension cannot be combined with --self or --extensions";
            }
            if (source) {
                conflictingOptions = conflictingOptions ?? "--extension cannot be combined with a positional source";
            }
            updateTarget = { type: "extensions", source: extensionFlagSource };
        }
        else if (source) {
            const sourceIsSelf = source === "self" || source === "aery" || source === "pi";
            if (sourceIsSelf) {
                updateTarget = extensionsFlag ? { type: "all" } : { type: "self" };
            }
            else {
                if (extensionsFlag || selfFlag) {
                    conflictingOptions =
                        conflictingOptions ?? "positional update targets cannot be combined with --self or --extensions";
                }
                updateTarget = { type: "extensions", source };
            }
        }
        else if (selfFlag && extensionsFlag) {
            updateTarget = { type: "all" };
        }
        else if (selfFlag) {
            updateTarget = { type: "self" };
        }
        else if (extensionsFlag) {
            updateTarget = { type: "extensions" };
        }
        else {
            updateTarget = { type: "all" };
        }
    }
    return {
        command,
        source,
        updateTarget,
        local,
        force,
        help,
        listRegistry,
        invalidOption,
        invalidArgument,
        missingOptionValue,
        conflictingOptions,
    };
}
function updateTargetIncludesSelf(target) {
    return target.type === "all" || target.type === "self";
}
function updateTargetIncludesExtensions(target) {
    return target.type === "all" || target.type === "extensions";
}
export function formatExtensionUpdateSuccessMessage(source) {
    return source ? `Updated extension package ${source}` : "Updated installed extensions";
}
function printSelfUpdateUnavailable(npmCommand) {
    console.error(`error: ${APP_NAME} cannot self-update this installation.`);
    console.error(getSelfUpdateUnavailableInstruction(PACKAGE_NAME, npmCommand));
    const entrypoint = process.argv[1];
    if (entrypoint) {
        console.error("");
        console.error(`Location of aery executable: ${entrypoint}`);
    }
}
function printSelfUpdateFallback(command) {
    console.error(chalk.dim(`If this keeps failing, run this command yourself: ${command.display}`));
}
async function shouldRunSelfUpdate(force) {
    if (force) {
        return true;
    }
    let latestVersion;
    try {
        latestVersion = await getLatestPiVersion(VERSION);
    }
    catch {
        return true;
    }
    if (!latestVersion || isNewerPackageVersion(latestVersion, VERSION)) {
        return true;
    }
    console.log(chalk.green(`${APP_NAME} is already up to date (v${VERSION})`));
    return false;
}
async function runSelfUpdate(command) {
    console.log(chalk.dim(`Updating ${APP_NAME} with ${command.display}...`));
    await new Promise((resolve, reject) => {
        // Windows package managers are commonly .cmd shims. Use the shell so Node can execute them.
        const child = spawn(command.command, command.args, {
            stdio: "inherit",
            shell: shouldUseWindowsShell(command.command),
        });
        child.on("error", (error) => {
            reject(error);
        });
        child.on("close", (code, signal) => {
            if (code === 0) {
                resolve();
            }
            else if (signal) {
                reject(new Error(`${command.display} terminated by signal ${signal}`));
            }
            else {
                reject(new Error(`${command.display} exited with code ${code ?? "unknown"}`));
            }
        });
    });
}
export async function handleConfigCommand(args) {
    if (args[0] !== "config") {
        return false;
    }
    const cwd = process.cwd();
    const agentDir = getAgentDir();
    const settingsManager = SettingsManager.create(cwd, agentDir);
    reportSettingsErrors(settingsManager, "config command");
    const packageManager = new DefaultPackageManager({ cwd, agentDir, settingsManager });
    const resolvedPaths = await packageManager.resolve();
    await selectConfig({
        resolvedPaths,
        settingsManager,
        cwd,
        agentDir,
    });
    process.exit(0);
}
export async function handlePackageCommand(args) {
    const options = parsePackageCommand(args);
    if (!options) {
        return false;
    }
    if (options.help) {
        printPackageCommandHelp(options.command);
        return true;
    }
    if (options.invalidOption) {
        console.error(chalk.red(`Unknown option ${options.invalidOption} for "${options.command}".`));
        console.error(chalk.dim(`Use "${APP_NAME} --help" or "${getPackageCommandUsage(options.command)}".`));
        process.exitCode = 1;
        return true;
    }
    if (options.missingOptionValue) {
        console.error(chalk.red(`Missing value for ${options.missingOptionValue}.`));
        console.error(chalk.dim(`Usage: ${getPackageCommandUsage(options.command)}`));
        process.exitCode = 1;
        return true;
    }
    if (options.invalidArgument) {
        console.error(chalk.red(`Unexpected argument ${options.invalidArgument}.`));
        console.error(chalk.dim(`Usage: ${getPackageCommandUsage(options.command)}`));
        process.exitCode = 1;
        return true;
    }
    if (options.conflictingOptions) {
        console.error(chalk.red(options.conflictingOptions));
        console.error(chalk.dim(`Usage: ${getPackageCommandUsage(options.command)}`));
        process.exitCode = 1;
        return true;
    }
    const source = options.source;
    if ((options.command === "install" || options.command === "remove") && !source && !options.listRegistry) {
        console.error(chalk.red(`Missing ${options.command} source.`));
        console.error(chalk.dim(`Usage: ${getPackageCommandUsage(options.command)}`));
        process.exitCode = 1;
        return true;
    }
    const cwd = process.cwd();
    const agentDir = getAgentDir();
    const settingsManager = SettingsManager.create(cwd, agentDir);
    reportSettingsErrors(settingsManager, "package command");
    const selfUpdateNpmCommand = settingsManager.getGlobalSettings().npmCommand;
    const packageManager = new DefaultPackageManager({ cwd, agentDir, settingsManager });
    packageManager.setProgressCallback((event) => {
        if (event.type === "start") {
            process.stdout.write(chalk.dim(`${event.message}\n`));
        }
    });
    try {
        switch (options.command) {
            case "install": {
                if (options.listRegistry) {
                    try {
                        const res = await fetch(REGISTRY_URL);
                        if (!res.ok)
                            throw new Error(`HTTP ${res.status}`);
                        const registry = (await res.json());
                        console.log(chalk.bold("\nAvailable extensions:\n"));
                        for (const [name, pack] of Object.entries(registry.packs ?? {})) {
                            if (pack.coming_soon)
                                continue;
                            console.log(`  ${chalk.cyan(name.padEnd(24))} ${pack.description ?? ""}`);
                        }
                        console.log(chalk.dim(`\nInstall with: ${APP_NAME} install <name>`));
                    }
                    catch (err) {
                        console.error(chalk.red(`Failed to fetch registry: ${err instanceof Error ? err.message : err}`));
                        process.exitCode = 1;
                    }
                    return true;
                }
                let installSource = source;
                const resolved = await resolveRegistrySource(installSource);
                if (resolved) {
                    console.log(chalk.dim(`Resolved ${installSource} → ${resolved}`));
                    installSource = resolved;
                }
                else if (/^[a-z0-9-]+$/.test(installSource) && !installSource.includes("/")) {
                    console.error(chalk.red(`Unknown extension: ${installSource}`));
                    console.error(chalk.dim(`Run: ${APP_NAME} install --list  to see available extensions`));
                    console.error(chalk.dim(`Or use a full source: git:github.com/user/repo`));
                    process.exitCode = 1;
                    return true;
                }
                await packageManager.installAndPersist(installSource, { local: options.local });
                console.log(chalk.green(`Installed ${source}`));
                return true;
            }
            case "remove": {
                const removed = await packageManager.removeAndPersist(source, { local: options.local });
                if (!removed) {
                    console.error(chalk.red(`No matching package found for ${source}`));
                    process.exitCode = 1;
                    return true;
                }
                console.log(chalk.green(`Removed ${source}`));
                return true;
            }
            case "list": {
                const configuredPackages = packageManager.listConfiguredPackages();
                const userPackages = configuredPackages.filter((pkg) => pkg.scope === "user");
                const projectPackages = configuredPackages.filter((pkg) => pkg.scope === "project");
                if (configuredPackages.length === 0) {
                    console.log(chalk.dim("No packages installed."));
                    return true;
                }
                const formatPackage = (pkg) => {
                    const display = pkg.filtered ? `${pkg.source} (filtered)` : pkg.source;
                    console.log(`  ${display}`);
                    if (pkg.installedPath) {
                        console.log(chalk.dim(`    ${pkg.installedPath}`));
                    }
                };
                if (userPackages.length > 0) {
                    console.log(chalk.bold("User packages:"));
                    for (const pkg of userPackages) {
                        formatPackage(pkg);
                    }
                }
                if (projectPackages.length > 0) {
                    if (userPackages.length > 0)
                        console.log();
                    console.log(chalk.bold("Project packages:"));
                    for (const pkg of projectPackages) {
                        formatPackage(pkg);
                    }
                }
                return true;
            }
            case "update": {
                const target = options.updateTarget ?? { type: "all" };
                if (updateTargetIncludesExtensions(target)) {
                    const updateSource = target.type === "extensions" ? target.source : undefined;
                    await packageManager.update(updateSource);
                    console.log(chalk.green(formatExtensionUpdateSuccessMessage(updateSource)));
                }
                if (updateTargetIncludesSelf(target)) {
                    const selfUpdateCommand = getSelfUpdateCommand(PACKAGE_NAME, selfUpdateNpmCommand);
                    if (!selfUpdateCommand) {
                        printSelfUpdateUnavailable(selfUpdateNpmCommand);
                        process.exitCode = 1;
                        return true;
                    }
                    if (!(await shouldRunSelfUpdate(options.force))) {
                        return true;
                    }
                    try {
                        await runSelfUpdate(selfUpdateCommand);
                    }
                    catch (error) {
                        const message = error instanceof Error ? error.message : "Unknown package command error";
                        console.error(chalk.red(`Error: ${message}`));
                        printSelfUpdateFallback(selfUpdateCommand);
                        process.exitCode = 1;
                        return true;
                    }
                    console.log(chalk.green(`Updated ${APP_NAME}`));
                }
                return true;
            }
        }
    }
    catch (error) {
        const message = error instanceof Error ? error.message : "Unknown package command error";
        console.error(chalk.red(`Error: ${message}`));
        process.exitCode = 1;
        return true;
    }
}
//# sourceMappingURL=package-manager-cli.js.map