/**
 * CLI command for downloading skillset packages from the Nori registrar
 * Handles: nori-skillsets download <package>[@version] [--registry <url>]
 */
import * as fs from "fs/promises";
import * as path from "path";
import { Readable } from "stream";
import { pipeline } from "stream/promises";
import zlib from "zlib";
import { log } from "@clack/prompts";
import * as semver from "semver";
import * as tar from "tar";
import { registrarApi, REGISTRAR_URL, NetworkError, } from "../../../api/registrar.js";
import { getRegistryAuthToken } from "../../../api/registryAuth.js";
import { getCommandNames, } from "../../../cli/commands/cliCommandNames.js";
import { initMain } from "../../../cli/commands/init/init.js";
import { getRegistryAuth, hasRegistryAuthCredentials, loadConfig, toRegistryAuth, } from "../../../cli/config.js";
import { AgentRegistry } from "../../../cli/features/agentRegistry.js";
import { registryDownloadFlow } from "../../../cli/prompts/flows/index.js";
import { getNoriSkillsetsDir } from "../../../norijson/skillset.js";
import { resolveInstallDir } from "../../../utils/path.js";
import { parseNamespacedPackage, buildOrganizationRegistryUrl, } from "../../../utils/url.js";
/**
 * Read the .nori-version file from a directory
 * @param args - The function arguments
 * @param args.dir - The directory path containing the .nori-version file
 *
 * @returns The version info or null if not found
 */
const readVersionInfo = async (args) => {
    const { dir } = args;
    const versionFilePath = path.join(dir, ".nori-version");
    try {
        const content = await fs.readFile(versionFilePath, "utf-8");
        return JSON.parse(content);
    }
    catch {
        return null;
    }
};
/**
 * Read the nori.json file from a skillset directory
 * @param args - The function arguments
 * @param args.skillsetDir - The skillset directory path
 *
 * @returns The nori.json content or null if not found
 */
const readNoriJson = async (args) => {
    const { skillsetDir } = args;
    const noriJsonPath = path.join(skillsetDir, "nori.json");
    try {
        const content = await fs.readFile(noriJsonPath, "utf-8");
        return JSON.parse(content);
    }
    catch {
        return null;
    }
};
/**
 * Download and install a single skill dependency (always uses latest version)
 * @param args - The download parameters
 * @param args.skillName - The name of the skill to download
 * @param args.skillsDir - The directory where skills are installed
 * @param args.registryUrl - The registry URL to download from
 * @param args.authToken - Optional authentication token for private registries
 * @param args.silent - If true, collect warnings as return values instead of logging
 *
 * @returns Result indicating whether the skill was downloaded and any warning
 */
const downloadSkillDependency = async (args) => {
    const { skillName, skillsDir, registryUrl, authToken, silent } = args;
    const skillDir = path.join(skillsDir, skillName);
    try {
        // Fetch skill packument to get latest version
        const packument = await registrarApi.getSkillPackument({
            skillName,
            registryUrl,
            authToken: authToken ?? undefined,
        });
        const latestVersion = packument["dist-tags"].latest;
        if (latestVersion == null) {
            const warning = `Warning: No latest version found for skill "${skillName}"`;
            if (!silent) {
                log.warn(warning);
            }
            return { downloaded: false, warning };
        }
        // Check if skill already exists with same version
        let skillExists = false;
        try {
            await fs.access(skillDir);
            skillExists = true;
        }
        catch {
            // Skill doesn't exist
        }
        if (skillExists) {
            const existingVersionInfo = await readVersionInfo({ dir: skillDir });
            if (existingVersionInfo != null) {
                // Skip if already at latest version
                if (existingVersionInfo.version === latestVersion) {
                    return { downloaded: false }; // Already installed with latest version
                }
            }
        }
        // Download the skill tarball (latest version)
        const tarballData = await registrarApi.downloadSkillTarball({
            skillName,
            version: latestVersion,
            registryUrl,
            authToken: authToken ?? undefined,
        });
        // Extract to skill directory
        if (skillExists) {
            // Update existing skill - extract to temp dir first
            const tempDir = path.join(skillsDir, `.${skillName}-download-temp`);
            const backupDir = path.join(skillsDir, `.${skillName}-backup`);
            await fs.mkdir(tempDir, { recursive: true });
            try {
                await extractTarball({ tarballData, targetDir: tempDir });
                // Atomic swap
                await fs.rename(skillDir, backupDir);
                await fs.rename(tempDir, skillDir);
                await fs.rm(backupDir, { recursive: true, force: true });
            }
            catch (err) {
                // Restore from backup if swap failed
                try {
                    await fs.access(backupDir);
                    await fs.rm(skillDir, { recursive: true, force: true }).catch(() => {
                        /* ignore */
                    });
                    await fs.rename(backupDir, skillDir);
                }
                catch {
                    /* ignore */
                }
                await fs.rm(tempDir, { recursive: true, force: true }).catch(() => {
                    /* ignore */
                });
                throw err;
            }
        }
        else {
            // New install
            await fs.mkdir(skillDir, { recursive: true });
            await extractTarball({ tarballData, targetDir: skillDir });
        }
        // Write .nori-version file
        await fs.writeFile(path.join(skillDir, ".nori-version"), JSON.stringify({
            version: latestVersion,
            registryUrl,
        }, null, 2));
        return { downloaded: true };
    }
    catch (err) {
        const errorMessage = err instanceof Error ? err.message : String(err);
        const warning = `Warning: Failed to download skill "${skillName}": ${errorMessage}`;
        if (!silent) {
            log.warn(warning);
        }
        return { downloaded: false, warning };
    }
};
/**
 * Download all skill dependencies from a nori.json (always uses latest versions)
 * @param args - The download parameters
 * @param args.noriJson - The parsed nori.json manifest containing dependencies
 * @param args.skillsDir - The directory where skills are installed
 * @param args.registryUrl - The registry URL to download from
 * @param args.authToken - Optional authentication token for private registries
 * @param args.silent - If true, suppress log output and return warnings instead
 *
 * @returns Array of warning messages from failed dependency downloads
 */
const downloadSkillDependencies = async (args) => {
    const { noriJson, skillsDir, registryUrl, authToken, silent } = args;
    const skillDeps = noriJson.dependencies?.skills;
    if (skillDeps == null || Object.keys(skillDeps).length === 0) {
        return [];
    }
    if (!silent) {
        log.info("Installing skill dependencies...");
    }
    const warnings = [];
    for (const skillName of Object.keys(skillDeps)) {
        const result = await downloadSkillDependency({
            skillName,
            skillsDir,
            registryUrl,
            authToken,
            silent,
        });
        if (result.warning != null) {
            warnings.push(result.warning);
        }
    }
    return warnings;
};
/**
 * Download and install a single subagent dependency (always uses latest version)
 * @param args - The download parameters
 * @param args.subagentName - The name of the subagent to download
 * @param args.subagentsDir - The directory where subagents are installed
 * @param args.registryUrl - The registry URL to download from
 * @param args.authToken - Optional authentication token for private registries
 * @param args.silent - If true, collect warnings as return values instead of logging
 *
 * @returns Result indicating whether the subagent was downloaded and any warning
 */
const downloadSubagentDependency = async (args) => {
    const { subagentName, subagentsDir, registryUrl, authToken, silent } = args;
    const subagentDir = path.join(subagentsDir, subagentName);
    try {
        // Fetch subagent packument to get latest version
        const packument = await registrarApi.getSubagentPackument({
            subagentName,
            registryUrl,
            authToken: authToken ?? undefined,
        });
        const latestVersion = packument["dist-tags"].latest;
        if (latestVersion == null) {
            const warning = `Warning: No latest version found for subagent "${subagentName}"`;
            if (!silent) {
                log.warn(warning);
            }
            return { downloaded: false, warning };
        }
        // Check if subagent already exists with same version
        let subagentExists = false;
        try {
            await fs.access(subagentDir);
            subagentExists = true;
        }
        catch {
            // Subagent doesn't exist
        }
        if (subagentExists) {
            const existingVersionInfo = await readVersionInfo({ dir: subagentDir });
            if (existingVersionInfo != null) {
                // Skip if already at latest version
                if (existingVersionInfo.version === latestVersion) {
                    return { downloaded: false }; // Already installed with latest version
                }
            }
        }
        // Download the subagent tarball (latest version)
        const tarballData = await registrarApi.downloadSubagentTarball({
            subagentName,
            version: latestVersion,
            registryUrl,
            authToken: authToken ?? undefined,
        });
        // Extract to subagent directory
        if (subagentExists) {
            // Update existing subagent - extract to temp dir first
            const tempDir = path.join(subagentsDir, `.${subagentName}-download-temp`);
            const backupDir = path.join(subagentsDir, `.${subagentName}-backup`);
            await fs.mkdir(tempDir, { recursive: true });
            try {
                await extractTarball({ tarballData, targetDir: tempDir });
                // Atomic swap
                await fs.rename(subagentDir, backupDir);
                await fs.rename(tempDir, subagentDir);
                await fs.rm(backupDir, { recursive: true, force: true });
            }
            catch (err) {
                // Restore from backup if swap failed
                try {
                    await fs.access(backupDir);
                    await fs
                        .rm(subagentDir, { recursive: true, force: true })
                        .catch(() => {
                        /* ignore */
                    });
                    await fs.rename(backupDir, subagentDir);
                }
                catch {
                    /* ignore */
                }
                await fs.rm(tempDir, { recursive: true, force: true }).catch(() => {
                    /* ignore */
                });
                throw err;
            }
        }
        else {
            // New install
            await fs.mkdir(subagentDir, { recursive: true });
            await extractTarball({ tarballData, targetDir: subagentDir });
        }
        // Write .nori-version file
        await fs.writeFile(path.join(subagentDir, ".nori-version"), JSON.stringify({
            version: latestVersion,
            registryUrl,
        }, null, 2));
        return { downloaded: true };
    }
    catch (err) {
        const errorMessage = err instanceof Error ? err.message : String(err);
        const warning = `Warning: Failed to download subagent "${subagentName}": ${errorMessage}`;
        if (!silent) {
            log.warn(warning);
        }
        return { downloaded: false, warning };
    }
};
/**
 * Download all subagent dependencies from a nori.json (always uses latest versions)
 * @param args - The download parameters
 * @param args.noriJson - The parsed nori.json manifest containing dependencies
 * @param args.subagentsDir - The directory where subagents are installed
 * @param args.registryUrl - The registry URL to download from
 * @param args.authToken - Optional authentication token for private registries
 * @param args.silent - If true, suppress log output and return warnings instead
 *
 * @returns Array of warning messages from failed dependency downloads
 */
const downloadSubagentDependencies = async (args) => {
    const { noriJson, subagentsDir, registryUrl, authToken, silent } = args;
    const subagentDeps = noriJson.dependencies?.subagents;
    if (subagentDeps == null || Object.keys(subagentDeps).length === 0) {
        return [];
    }
    if (!silent) {
        log.info("Installing subagent dependencies...");
    }
    const warnings = [];
    for (const subagentName of Object.keys(subagentDeps)) {
        const result = await downloadSubagentDependency({
            subagentName,
            subagentsDir,
            registryUrl,
            authToken,
            silent,
        });
        if (result.warning != null) {
            warnings.push(result.warning);
        }
    }
    return warnings;
};
/**
 * Check if buffer starts with gzip magic bytes (0x1f 0x8b)
 * @param args - The check parameters
 * @param args.buffer - The buffer to check
 *
 * @returns True if the buffer is gzip compressed
 */
const isGzipped = (args) => {
    const { buffer } = args;
    return buffer.length >= 2 && buffer[0] === 0x1f && buffer[1] === 0x8b;
};
/**
 * Extract a tarball to a directory
 * @param args - The extraction parameters
 * @param args.tarballData - The tarball data as ArrayBuffer
 * @param args.targetDir - The directory to extract to
 */
const extractTarball = async (args) => {
    const { tarballData, targetDir } = args;
    const buffer = Buffer.from(tarballData);
    const readable = Readable.from(buffer);
    if (isGzipped({ buffer })) {
        await pipeline(readable, zlib.createGunzip(), tar.extract({ cwd: targetDir }));
    }
    else {
        await pipeline(readable, tar.extract({ cwd: targetDir }));
    }
};
/**
 * Search a specific registry for a package
 * @param args - The search parameters
 * @param args.packageName - The package name to search for
 * @param args.registryUrl - The registry URL to search
 * @param args.config - The Nori configuration containing registry auth
 *
 * @returns Object with result (if found) and/or error (if failed)
 */
const searchSpecificRegistry = async (args) => {
    const { packageName, registryUrl, config } = args;
    // Check if this is the public registry
    if (registryUrl === REGISTRAR_URL) {
        try {
            const packument = await registrarApi.getPackument({
                packageName,
                registryUrl: REGISTRAR_URL,
            });
            return {
                result: {
                    registryUrl: REGISTRAR_URL,
                    packument,
                },
                error: null,
            };
        }
        catch (err) {
            if (err instanceof NetworkError) {
                return {
                    result: null,
                    error: {
                        registryUrl: REGISTRAR_URL,
                        isNetworkError: true,
                        message: err.message,
                    },
                };
            }
            // API error (like 404) - package not found
            return { result: null, error: null };
        }
    }
    // Private registry - require auth from config
    if (config == null) {
        return { result: null, error: null };
    }
    const registryAuth = getRegistryAuth({ config, registryUrl });
    if (registryAuth == null) {
        return { result: null, error: null };
    }
    try {
        const authToken = await getRegistryAuthToken({ registryAuth });
        const packument = await registrarApi.getPackument({
            packageName,
            registryUrl,
            authToken,
        });
        return {
            result: {
                registryUrl,
                packument,
                authToken,
            },
            error: null,
        };
    }
    catch (err) {
        if (err instanceof NetworkError) {
            return {
                result: null,
                error: {
                    registryUrl,
                    isNetworkError: true,
                    message: err.message,
                },
            };
        }
        // API error (like 404) - package not found
        return { result: null, error: null };
    }
};
/**
 * Format the list of available versions for a package
 * @param args - The format parameters
 * @param args.packageName - The package name
 * @param args.packument - The packument containing version information
 * @param args.registryUrl - The registry URL
 * @param args.cliName - The CLI name for command hints
 *
 * @returns Formatted version list message
 */
const formatVersionList = (args) => {
    const { packageName, packument, registryUrl, cliName } = args;
    const commandNames = getCommandNames({ cliName });
    const distTags = packument["dist-tags"];
    const versions = Object.keys(packument.versions);
    const timeInfo = packument.time ?? {};
    // Sort versions in descending order (newest first)
    const sortedVersions = versions.sort((a, b) => {
        const timeA = timeInfo[a] ? new Date(timeInfo[a]).getTime() : 0;
        const timeB = timeInfo[b] ? new Date(timeInfo[b]).getTime() : 0;
        return timeB - timeA;
    });
    const lines = [
        `Available versions of "${packageName}" from ${registryUrl}:\n`,
        "Dist-tags:",
    ];
    // Show dist-tags first
    for (const [tag, version] of Object.entries(distTags)) {
        lines.push(`  ${tag}: ${version}`);
    }
    lines.push("\nVersions:");
    // Show all versions with timestamps
    for (const version of sortedVersions) {
        const timestamp = timeInfo[version]
            ? new Date(timeInfo[version]).toLocaleDateString()
            : "";
        const tags = Object.entries(distTags)
            .filter(([, v]) => v === version)
            .map(([t]) => t);
        const tagStr = tags.length > 0 ? ` (${tags.join(", ")})` : "";
        const timeStr = timestamp ? ` - ${timestamp}` : "";
        lines.push(`  ${version}${tagStr}${timeStr}`);
    }
    const cliPrefix = cliName ?? "nori-skillsets";
    lines.push(`\nTo download a specific version:\n  ${cliPrefix} ${commandNames.download} ${packageName}@<version>`);
    return lines.join("\n");
};
/**
 * Format the multiple packages found error message
 * @param args - The format parameters
 * @param args.packageName - The package name that was searched
 * @param args.results - The search results from multiple registries
 * @param args.cliName - The CLI name for command hints
 *
 * @returns Formatted error message
 */
const formatMultiplePackagesError = (args) => {
    const { packageName, results, cliName } = args;
    const commandNames = getCommandNames({ cliName });
    const cliPrefix = cliName ?? "nori-skillsets";
    const lines = ["Multiple packages with the same name found.\n"];
    for (const result of results) {
        const version = result.packument["dist-tags"].latest ?? "unknown";
        const description = result.packument.description ?? "";
        lines.push(result.registryUrl);
        lines.push(`  -> ${packageName}@${version}: ${description}\n`);
    }
    lines.push("To download, please specify the registry with --registry:");
    for (const result of results) {
        lines.push(`${cliPrefix} ${commandNames.download} ${packageName} --registry ${result.registryUrl}`);
    }
    return lines.join("\n");
};
/**
 * Download and install a skillset from the registrar
 * @param args - The download parameters
 * @param args.packageSpec - Package name with optional version (e.g., "my-profile" or "my-profile@1.0.0")
 * @param args.cwd - Current working directory (defaults to process.cwd())
 * @param args.installDir - Optional explicit install directory
 * @param args.registryUrl - Optional registry URL to download from
 * @param args.listVersions - If true, list available versions instead of downloading
 * @param args.cliName - CLI name for user-facing messages (defaults to nori-skillsets)
 * @param args.nonInteractive - If true, skip interactive prompts and use defaults
 * @param args.silent - If true, suppress output (implies nonInteractive)
 *
 * @returns Result indicating success or failure
 */
export const registryDownloadMain = async (args) => {
    const { packageSpec, installDir, registryUrl, listVersions, cliName, nonInteractive, silent, } = args;
    const commandNames = getCommandNames({ cliName });
    const cliPrefix = cliName ?? "nori-skillsets";
    // Parse the namespaced package spec (e.g., "myorg/my-profile@1.0.0")
    const parsed = parseNamespacedPackage({ packageSpec });
    if (parsed == null) {
        log.error(`Invalid package specification: "${packageSpec}".\nExpected format: skillset-name or org/skillset-name[@version]`);
        return {
            success: false,
            cancelled: false,
            message: `Invalid package specification: "${packageSpec}"`,
        };
    }
    const { orgId, packageName, version } = parsed;
    // Display name includes org prefix for namespaced packages (e.g., "myorg/my-profile")
    const profileDisplayName = orgId === "public" ? packageName : `${orgId}/${packageName}`;
    // Resolve install directory from config and auto-init if needed
    const config = await loadConfig();
    const resolvedInstallDir = resolveInstallDir({
        cliInstallDir: installDir,
        configInstallDir: config?.installDir,
        agentDirNames: AgentRegistry.getInstance().getAgentDirNames(),
    }).path;
    // Auto-init if no config exists yet (first time use)
    if (config == null) {
        log.info("Setting up Nori for first time use...");
        try {
            await initMain({
                installDir: resolvedInstallDir,
                nonInteractive: nonInteractive ?? silent ?? false,
                skipWarning: true,
            });
        }
        catch (err) {
            log.error(`Failed to initialize Nori: ${err instanceof Error ? err.message : String(err)}`);
            return {
                success: false,
                cancelled: false,
                message: `Failed to initialize Nori: ${err instanceof Error ? err.message : String(err)}`,
            };
        }
    }
    const skillsetsDir = getNoriSkillsetsDir();
    // For namespaced packages, the skillset is in a nested directory (e.g., profiles/myorg/my-profile)
    const targetDir = orgId === "public"
        ? path.join(skillsetsDir, packageName)
        : path.join(skillsetsDir, orgId, packageName);
    // Check if skillset already exists and get its version info
    let existingVersionInfo = null;
    let profileExists = false;
    try {
        await fs.access(targetDir);
        profileExists = true;
        existingVersionInfo = await readVersionInfo({ dir: targetDir });
    }
    catch {
        // Directory doesn't exist - continue
    }
    // Closure variable shared between onSearch and onDownload callbacks
    let foundRegistry = null;
    let resolvedTargetVersion = "";
    const result = await registryDownloadFlow({
        packageDisplayName: profileDisplayName,
        nonInteractive: nonInteractive ?? silent ?? null,
        callbacks: {
            onSearch: async () => {
                // Inline search logic
                let flowSearchResults;
                const hasUnifiedAuth = config?.auth != null &&
                    hasRegistryAuthCredentials({ auth: config.auth }) &&
                    config.auth.organizations != null;
                if (registryUrl != null) {
                    const publicRegistryUrl = buildOrganizationRegistryUrl({
                        orgId: "public",
                    });
                    if (registryUrl !== REGISTRAR_URL &&
                        registryUrl !== publicRegistryUrl) {
                        let hasAuth = false;
                        if (hasUnifiedAuth) {
                            const userOrgs = config.auth.organizations;
                            for (const userOrgId of userOrgs) {
                                const orgRegistryUrl = buildOrganizationRegistryUrl({
                                    orgId: userOrgId,
                                });
                                if (orgRegistryUrl === registryUrl) {
                                    hasAuth = true;
                                    break;
                                }
                            }
                        }
                        if (!hasAuth) {
                            const registryAuth = config != null
                                ? getRegistryAuth({ config, registryUrl })
                                : null;
                            if (registryAuth == null) {
                                return {
                                    status: "error",
                                    error: `No authentication configured for registry: ${registryUrl}`,
                                    hint: "Add registry credentials to your .nori-config.json file.",
                                };
                            }
                        }
                    }
                    const { result: searchResult, error: searchError } = await searchSpecificRegistry({
                        packageName,
                        registryUrl,
                        config,
                    });
                    if (searchError?.isNetworkError) {
                        return {
                            status: "error",
                            error: `Network error while connecting to ${registryUrl}:\n\n${searchError.message}`,
                        };
                    }
                    flowSearchResults = searchResult != null ? [searchResult] : [];
                }
                else if (orgId === "public") {
                    try {
                        const packument = await registrarApi.getPackument({
                            packageName,
                            registryUrl: REGISTRAR_URL,
                        });
                        flowSearchResults = [{ registryUrl: REGISTRAR_URL, packument }];
                    }
                    catch (err) {
                        if (err instanceof NetworkError) {
                            return {
                                status: "error",
                                error: `Network error while connecting to registry:\n\n${err.message}`,
                            };
                        }
                        flowSearchResults = [];
                    }
                }
                else if (hasUnifiedAuth) {
                    const targetRegistryUrl = buildOrganizationRegistryUrl({ orgId });
                    const userOrgs = config.auth.organizations;
                    if (!userOrgs.includes(orgId)) {
                        return {
                            status: "error",
                            error: `You do not have access to organization "${orgId}".`,
                            hint: `Your available organizations: ${userOrgs.length > 0 ? userOrgs.join(", ") : "(none)"}`,
                        };
                    }
                    const registryAuth = toRegistryAuth({
                        auth: config.auth,
                        registryUrl: targetRegistryUrl,
                    });
                    try {
                        const authToken = await getRegistryAuthToken({ registryAuth });
                        const packument = await registrarApi.getPackument({
                            packageName,
                            registryUrl: targetRegistryUrl,
                            authToken,
                        });
                        flowSearchResults = [
                            { registryUrl: targetRegistryUrl, packument, authToken },
                        ];
                    }
                    catch (err) {
                        if (err instanceof NetworkError) {
                            return {
                                status: "error",
                                error: `Network error while connecting to ${targetRegistryUrl}:\n\n${err.message}`,
                            };
                        }
                        flowSearchResults = [];
                    }
                }
                else {
                    return {
                        status: "error",
                        error: `Skillset "${orgId}/${packageName}" not found.`,
                        hint: `To download from organization "${orgId}", log in with:\n  nori-skillsets login`,
                    };
                }
                if (flowSearchResults.length === 0) {
                    return {
                        status: "error",
                        error: `Skillset "${profileDisplayName}" not found in any registry.`,
                    };
                }
                if (flowSearchResults.length > 1) {
                    return {
                        status: "error",
                        error: formatMultiplePackagesError({
                            packageName,
                            results: flowSearchResults,
                            cliName,
                        }),
                    };
                }
                foundRegistry = flowSearchResults[0];
                if (listVersions) {
                    return {
                        status: "list-versions",
                        formattedVersionList: formatVersionList({
                            packageName,
                            packument: foundRegistry.packument,
                            registryUrl: foundRegistry.registryUrl,
                            cliName,
                        }),
                        versionCount: Object.keys(foundRegistry.packument.versions).length,
                    };
                }
                resolvedTargetVersion =
                    version ?? foundRegistry.packument["dist-tags"].latest;
                if (profileExists && existingVersionInfo != null) {
                    const installedVersion = existingVersionInfo.version;
                    const installedValid = semver.valid(installedVersion) != null;
                    const targetValid = semver.valid(resolvedTargetVersion) != null;
                    if (installedValid && targetValid) {
                        if (semver.gte(installedVersion, resolvedTargetVersion)) {
                            let depWarnings = [];
                            const noriJson = await readNoriJson({ skillsetDir: targetDir });
                            if (noriJson != null) {
                                const profileSkillsDir = path.join(targetDir, "skills");
                                depWarnings = await downloadSkillDependencies({
                                    noriJson,
                                    skillsDir: profileSkillsDir,
                                    registryUrl: foundRegistry.registryUrl,
                                    authToken: foundRegistry.authToken,
                                    silent: true,
                                });
                                const profileSubagentsDir = path.join(targetDir, "subagents");
                                const subagentWarnings = await downloadSubagentDependencies({
                                    noriJson,
                                    subagentsDir: profileSubagentsDir,
                                    registryUrl: foundRegistry.registryUrl,
                                    authToken: foundRegistry.authToken,
                                    silent: true,
                                });
                                depWarnings = [...depWarnings, ...subagentWarnings];
                            }
                            return {
                                status: "already-current",
                                version: installedVersion,
                                warnings: depWarnings,
                            };
                        }
                        return {
                            status: "ready",
                            targetVersion: resolvedTargetVersion,
                            isUpdate: true,
                            currentVersion: installedVersion,
                        };
                    }
                    else if (installedVersion === resolvedTargetVersion) {
                        let depWarnings = [];
                        const noriJson = await readNoriJson({ skillsetDir: targetDir });
                        if (noriJson != null) {
                            const profileSkillsDir = path.join(targetDir, "skills");
                            depWarnings = await downloadSkillDependencies({
                                noriJson,
                                skillsDir: profileSkillsDir,
                                registryUrl: foundRegistry.registryUrl,
                                authToken: foundRegistry.authToken,
                                silent: true,
                            });
                            const profileSubagentsDir = path.join(targetDir, "subagents");
                            const subagentWarnings = await downloadSubagentDependencies({
                                noriJson,
                                subagentsDir: profileSubagentsDir,
                                registryUrl: foundRegistry.registryUrl,
                                authToken: foundRegistry.authToken,
                                silent: true,
                            });
                            depWarnings = [...depWarnings, ...subagentWarnings];
                        }
                        return {
                            status: "already-current",
                            version: installedVersion,
                            warnings: depWarnings,
                        };
                    }
                }
                if (profileExists && existingVersionInfo == null) {
                    return {
                        status: "error",
                        error: `Skillset "${packageName}" already exists at:\n${targetDir}\n\nThis skillset has no version information (.nori-version file).`,
                        hint: `To reinstall:\n  rm -rf "${targetDir}"\n  ${cliPrefix} ${commandNames.download} ${packageName}`,
                    };
                }
                return {
                    status: "ready",
                    targetVersion: resolvedTargetVersion,
                    isUpdate: false,
                };
            },
            onDownload: async () => {
                const selectedRegistry = foundRegistry;
                try {
                    const tarballData = await registrarApi.downloadTarball({
                        packageName,
                        version: version ?? undefined,
                        registryUrl: selectedRegistry.registryUrl,
                        authToken: selectedRegistry.authToken ?? undefined,
                    });
                    if (profileExists) {
                        const tempDir = path.join(skillsetsDir, `.${packageName}-download-temp`);
                        await fs.mkdir(tempDir, { recursive: true });
                        try {
                            await extractTarball({ tarballData, targetDir: tempDir });
                        }
                        catch (extractErr) {
                            await fs.rm(tempDir, { recursive: true, force: true });
                            throw extractErr;
                        }
                        const existingFiles = await fs.readdir(targetDir);
                        for (const file of existingFiles) {
                            if (file !== ".nori-version" &&
                                file !== "skills" &&
                                file !== "subagents") {
                                await fs.rm(path.join(targetDir, file), {
                                    recursive: true,
                                    force: true,
                                });
                            }
                        }
                        const extractedFiles = await fs.readdir(tempDir);
                        for (const file of extractedFiles) {
                            if (file === "skills" || file === "subagents") {
                                await fs.rm(path.join(tempDir, file), {
                                    recursive: true,
                                    force: true,
                                });
                                continue;
                            }
                            await fs.rename(path.join(tempDir, file), path.join(targetDir, file));
                        }
                        await fs.rm(tempDir, { recursive: true, force: true });
                    }
                    else {
                        await fs.mkdir(targetDir, { recursive: true });
                        try {
                            await extractTarball({ tarballData, targetDir });
                        }
                        catch (extractErr) {
                            await fs.rm(targetDir, { recursive: true, force: true });
                            throw extractErr;
                        }
                    }
                    await fs.writeFile(path.join(targetDir, ".nori-version"), JSON.stringify({
                        version: resolvedTargetVersion,
                        registryUrl: selectedRegistry.registryUrl,
                    }, null, 2));
                    // Download skill and subagent dependencies and collect warnings
                    let warnings = [];
                    const noriJson = await readNoriJson({ skillsetDir: targetDir });
                    if (noriJson != null) {
                        const profileSkillsDir = path.join(targetDir, "skills");
                        warnings = await downloadSkillDependencies({
                            noriJson,
                            skillsDir: profileSkillsDir,
                            registryUrl: selectedRegistry.registryUrl,
                            authToken: selectedRegistry.authToken,
                            silent: true,
                        });
                        const profileSubagentsDir = path.join(targetDir, "subagents");
                        const subagentWarnings = await downloadSubagentDependencies({
                            noriJson,
                            subagentsDir: profileSubagentsDir,
                            registryUrl: selectedRegistry.registryUrl,
                            authToken: selectedRegistry.authToken,
                            silent: true,
                        });
                        warnings = [...warnings, ...subagentWarnings];
                    }
                    return {
                        success: true,
                        version: resolvedTargetVersion,
                        isUpdate: profileExists,
                        installedTo: targetDir,
                        switchHint: `${cliPrefix} ${commandNames.switchSkillset} ${profileDisplayName}`,
                        profileDisplayName,
                        warnings,
                    };
                }
                catch (err) {
                    const errorMessage = err instanceof Error ? err.message : String(err);
                    return {
                        success: false,
                        error: `Failed to download skillset "${profileDisplayName}": ${errorMessage}`,
                    };
                }
            },
        },
    });
    if (result == null) {
        return { success: false, cancelled: true, message: "" };
    }
    return {
        success: true,
        cancelled: false,
        message: result.statusMessage,
    };
};
/**
 * Register the 'registry-download' command with commander
 * @param args - Configuration arguments
 * @param args.program - Commander program instance
 */
export const registerRegistryDownloadCommand = (args) => {
    const { program } = args;
    program
        .command("registry-download <package>")
        .description("Download and install a skillset package from the Nori registrar")
        .option("--registry <url>", "Download from a specific registry URL instead of searching all registries")
        .option("--list-versions", "List available versions for the package instead of downloading")
        .action(async (packageSpec, options) => {
        const globalOpts = program.opts();
        const result = await registryDownloadMain({
            packageSpec,
            installDir: globalOpts.installDir || null,
            registryUrl: options.registry || null,
            listVersions: options.listVersions || null,
            nonInteractive: globalOpts.nonInteractive || null,
            silent: globalOpts.silent || null,
        });
        if (!result.success) {
            process.exit(1);
        }
    });
};
//# sourceMappingURL=registryDownload.js.map