/**
 * API client for the Nori registrar
 *
 * The registrar is a package registry for Nori skillsets.
 * Read operations (search, packument, download) are public.
 * Write operations (upload) require authentication.
 *
 * Uses /api/skillsets/ as the primary endpoint path, with silent
 * fallback to /api/profiles/ for older registrar instances.
 */
import { ApiError, SkillCollisionError, SubagentCollisionError, } from "../utils/fetch.js";
export { NetworkError, ApiError, SkillCollisionError, isSkillCollisionError, SubagentCollisionError, isSubagentCollisionError, } from "../utils/fetch.js";
export const REGISTRAR_URL = "https://noriskillsets.dev";
/**
 * Compute the fallback URL by replacing /api/skillsets/ with /api/profiles/
 * and /skillset sub-resource with /profile for upload endpoints.
 *
 * @param args - The function arguments
 * @param args.url - The primary URL to compute fallback for
 *
 * @returns The fallback URL with /api/profiles/ path
 */
const buildFallbackUrl = (args) => {
    const { url } = args;
    return url
        .replace("/api/skillsets/", "/api/profiles/")
        .replace(/\/skillset$/, "/profile");
};
/**
 * Execute a fetch with silent fallback to old /api/profiles/ path on 404.
 * Non-404 errors are thrown immediately without retry.
 *
 * @param args - The function arguments
 * @param args.url - The primary URL to fetch
 * @param args.init - The fetch request init options
 *
 * @returns The fetch response from primary or fallback URL
 */
const fetchWithFallback = async (args) => {
    const { url, init } = args;
    const response = await fetch(url, init);
    if (response.status === 404) {
        const fallbackUrl = buildFallbackUrl({ url });
        if (fallbackUrl !== url) {
            return fetch(fallbackUrl, init);
        }
    }
    return response;
};
export const registrarApi = {
    /**
     * Search for packages in the registrar
     * @param args - The search parameters
     *
     * @returns Array of matching packages
     */
    searchPackages: async (args) => {
        const { query, limit, offset, registryUrl, authToken } = args;
        const baseUrl = registryUrl ?? REGISTRAR_URL;
        const params = new URLSearchParams({ q: query });
        if (limit != null) {
            params.set("limit", limit.toString());
        }
        if (offset != null) {
            params.set("offset", offset.toString());
        }
        const url = `${baseUrl}/api/skillsets/search?${params.toString()}`;
        const headers = {};
        if (authToken != null) {
            headers.Authorization = `Bearer ${authToken}`;
        }
        const response = await fetchWithFallback({
            url,
            init: {
                method: "GET",
                ...(Object.keys(headers).length > 0 ? { headers } : {}),
            },
        });
        if (!response.ok) {
            const errorData = (await response.json().catch(() => ({
                error: `HTTP ${response.status}`,
            })));
            throw new ApiError(errorData.error ?? `HTTP ${response.status}`, response.status);
        }
        return (await response.json());
    },
    /**
     * Search for packages on a specific registry
     * @param args - The search parameters including registry URL
     *
     * @returns Array of matching packages
     */
    searchPackagesOnRegistry: async (args) => {
        const { query, registryUrl, authToken, limit, offset } = args;
        const params = new URLSearchParams({ q: query });
        if (limit != null) {
            params.set("limit", limit.toString());
        }
        if (offset != null) {
            params.set("offset", offset.toString());
        }
        const url = `${registryUrl}/api/skillsets/search?${params.toString()}`;
        const headers = {};
        if (authToken != null) {
            headers["Authorization"] = `Bearer ${authToken}`;
        }
        const response = await fetchWithFallback({
            url,
            init: {
                method: "GET",
                headers,
            },
        });
        if (!response.ok) {
            const errorData = (await response.json().catch(() => ({
                error: `HTTP ${response.status}`,
            })));
            throw new ApiError(errorData.error ?? `HTTP ${response.status}`, response.status);
        }
        return (await response.json());
    },
    /**
     * Get the packument (package metadata) for a package
     * @param args - The request parameters
     *
     * @returns The package packument
     */
    getPackument: async (args) => {
        const { packageName, registryUrl, authToken } = args;
        const baseUrl = registryUrl ?? REGISTRAR_URL;
        const url = `${baseUrl}/api/skillsets/${packageName}`;
        const headers = {};
        if (authToken != null) {
            headers.Authorization = `Bearer ${authToken}`;
        }
        const response = await fetchWithFallback({
            url,
            init: {
                method: "GET",
                ...(Object.keys(headers).length > 0 ? { headers } : {}),
            },
        });
        if (!response.ok) {
            const errorData = (await response.json().catch(() => ({
                error: `HTTP ${response.status}`,
            })));
            throw new ApiError(errorData.error ?? `HTTP ${response.status}`, response.status);
        }
        return (await response.json());
    },
    /**
     * Download a tarball for a package
     *
     * If no version is specified, the latest version is downloaded.
     * @param args - The download parameters
     *
     * @returns The tarball data as ArrayBuffer
     */
    downloadTarball: async (args) => {
        const { packageName, registryUrl, authToken } = args;
        const baseUrl = registryUrl ?? REGISTRAR_URL;
        let { version } = args;
        // If no version specified, resolve latest from packument
        if (version == null) {
            const packument = await registrarApi.getPackument({
                packageName,
                registryUrl,
                authToken,
            });
            version = packument["dist-tags"].latest;
            if (version == null) {
                throw new Error(`No latest version found for package: ${packageName}`);
            }
        }
        const tarballFilename = `${packageName}-${version}.tgz`;
        const url = `${baseUrl}/api/skillsets/${packageName}/tarball/${tarballFilename}`;
        const headers = {};
        if (authToken != null) {
            headers.Authorization = `Bearer ${authToken}`;
        }
        const response = await fetchWithFallback({
            url,
            init: {
                method: "GET",
                ...(Object.keys(headers).length > 0 ? { headers } : {}),
            },
        });
        if (!response.ok) {
            const errorData = (await response.json().catch(() => ({
                error: `HTTP ${response.status}`,
            })));
            throw new ApiError(errorData.error ?? `HTTP ${response.status}`, response.status);
        }
        return await response.arrayBuffer();
    },
    /**
     * Upload a skillset to the registrar
     * @param args - The upload parameters
     *
     * @returns The upload response with package metadata
     */
    uploadSkillset: async (args) => {
        const { packageName, version, archiveData, description, authToken, registryUrl, resolutionStrategy, subagentResolutionStrategy, inlineSkills, inlineSubagents, } = args;
        const baseUrl = registryUrl ?? REGISTRAR_URL;
        const formData = new FormData();
        formData.append("archive", new Blob([archiveData]), `${packageName}.tgz`);
        formData.append("version", version);
        if (description != null) {
            formData.append("description", description);
        }
        if (resolutionStrategy != null) {
            formData.append("resolutionStrategy", JSON.stringify(resolutionStrategy));
        }
        if (inlineSkills != null && inlineSkills.length > 0) {
            formData.append("inlineSkills", JSON.stringify(inlineSkills));
        }
        if (inlineSubagents != null && inlineSubagents.length > 0) {
            formData.append("inlineSubagents", JSON.stringify(inlineSubagents));
        }
        if (subagentResolutionStrategy != null) {
            formData.append("subagentResolutionStrategy", JSON.stringify(subagentResolutionStrategy));
        }
        const url = `${baseUrl}/api/skillsets/${packageName}/skillset`;
        const response = await fetchWithFallback({
            url,
            init: {
                method: "PUT",
                headers: {
                    Authorization: `Bearer ${authToken}`,
                },
                body: formData,
            },
        });
        if (!response.ok) {
            const errorData = (await response.json().catch(() => ({
                error: `HTTP ${response.status}`,
            })));
            // The registrar serializes both SkillCollisionError and
            // SubagentCollisionError into a response body with a single `conflicts`
            // key. Partition by item shape and dispatch: skill items with `skillId`
            // to SkillCollisionError, subagent items with `subagentId` to
            // SubagentCollisionError. When both item kinds appear in a single
            // payload (not currently produced by the server, but defensive),
            // SkillCollisionError wins on the first pass — the retry that follows
            // will surface remaining subagent conflicts.
            if (response.status === 409 &&
                Array.isArray(errorData.conflicts) &&
                errorData.conflicts.length > 0) {
                const skillItems = errorData.conflicts.filter((c) => "skillId" in c);
                const subagentItems = errorData.conflicts.filter((c) => "subagentId" in c);
                if (skillItems.length > 0) {
                    throw new SkillCollisionError({
                        message: errorData.error ?? "Skill conflicts detected",
                        conflicts: skillItems,
                        requiresVersions: errorData.requiresVersions,
                    });
                }
                if (subagentItems.length > 0) {
                    throw new SubagentCollisionError({
                        message: errorData.error ?? "Subagent conflicts detected",
                        conflicts: subagentItems,
                        requiresVersions: errorData.requiresVersions,
                    });
                }
            }
            throw new ApiError(errorData.error ?? `HTTP ${response.status}`, response.status);
        }
        return (await response.json());
    },
    // Skill API methods
    /**
     * Search for skills in the registrar
     * @param args - The search parameters
     *
     * @returns Array of matching skills
     */
    searchSkills: async (args) => {
        const { query, limit, offset, registryUrl, authToken } = args;
        const baseUrl = registryUrl ?? REGISTRAR_URL;
        const params = new URLSearchParams({ q: query });
        if (limit != null) {
            params.set("limit", limit.toString());
        }
        if (offset != null) {
            params.set("offset", offset.toString());
        }
        const url = `${baseUrl}/api/skills/search?${params.toString()}`;
        const headers = {};
        if (authToken != null) {
            headers.Authorization = `Bearer ${authToken}`;
        }
        const response = await fetch(url, {
            method: "GET",
            ...(Object.keys(headers).length > 0 ? { headers } : {}),
        });
        if (!response.ok) {
            const errorData = (await response.json().catch(() => ({
                error: `HTTP ${response.status}`,
            })));
            throw new ApiError(errorData.error ?? `HTTP ${response.status}`, response.status);
        }
        return (await response.json());
    },
    /**
     * Get the packument (package metadata) for a skill
     * @param args - The request parameters
     *
     * @returns The skill packument
     */
    getSkillPackument: async (args) => {
        const { skillName, registryUrl, authToken } = args;
        const baseUrl = registryUrl ?? REGISTRAR_URL;
        const url = `${baseUrl}/api/skills/${skillName}`;
        const headers = {};
        if (authToken != null) {
            headers.Authorization = `Bearer ${authToken}`;
        }
        const response = await fetch(url, {
            method: "GET",
            ...(Object.keys(headers).length > 0 ? { headers } : {}),
        });
        if (!response.ok) {
            const errorData = (await response.json().catch(() => ({
                error: `HTTP ${response.status}`,
            })));
            throw new ApiError(errorData.error ?? `HTTP ${response.status}`, response.status);
        }
        return (await response.json());
    },
    /**
     * Download a tarball for a skill
     *
     * If no version is specified, the latest version is downloaded.
     * @param args - The download parameters
     *
     * @returns The tarball data as ArrayBuffer
     */
    downloadSkillTarball: async (args) => {
        const { skillName, registryUrl, authToken } = args;
        const baseUrl = registryUrl ?? REGISTRAR_URL;
        let { version } = args;
        // If no version specified, resolve latest from packument
        if (version == null) {
            const packument = await registrarApi.getSkillPackument({
                skillName,
                registryUrl,
                authToken,
            });
            version = packument["dist-tags"].latest;
            if (version == null) {
                throw new Error(`No latest version found for skill: ${skillName}`);
            }
        }
        const tarballFilename = `${skillName}-${version}.tgz`;
        const url = `${baseUrl}/api/skills/${skillName}/tarball/${tarballFilename}`;
        const headers = {};
        if (authToken != null) {
            headers.Authorization = `Bearer ${authToken}`;
        }
        const response = await fetch(url, {
            method: "GET",
            ...(Object.keys(headers).length > 0 ? { headers } : {}),
        });
        if (!response.ok) {
            const errorData = (await response.json().catch(() => ({
                error: `HTTP ${response.status}`,
            })));
            throw new ApiError(errorData.error ?? `HTTP ${response.status}`, response.status);
        }
        return await response.arrayBuffer();
    },
    /**
     * Upload a skill to the registrar
     * @param args - The upload parameters
     *
     * @returns The upload response with skill metadata
     */
    uploadSkill: async (args) => {
        const { skillName, version, archiveData, description, authToken, registryUrl, } = args;
        const baseUrl = registryUrl ?? REGISTRAR_URL;
        const formData = new FormData();
        formData.append("archive", new Blob([archiveData]), `${skillName}.tgz`);
        formData.append("version", version);
        if (description != null) {
            formData.append("description", description);
        }
        const url = `${baseUrl}/api/skills/${skillName}/skill`;
        const response = await fetch(url, {
            method: "PUT",
            headers: {
                Authorization: `Bearer ${authToken}`,
            },
            body: formData,
        });
        if (!response.ok) {
            const errorData = (await response.json().catch(() => ({
                error: `HTTP ${response.status}`,
            })));
            throw new ApiError(errorData.error ?? `HTTP ${response.status}`, response.status);
        }
        return (await response.json());
    },
    // Subagent API methods
    /**
     * Search for subagents in a registry
     * @param args - The request parameters
     *
     * @returns Array of matching subagents
     */
    searchSubagents: async (args) => {
        const { query, limit, offset, registryUrl, authToken } = args;
        const baseUrl = registryUrl ?? REGISTRAR_URL;
        const params = new URLSearchParams({ q: query });
        if (limit != null) {
            params.set("limit", limit.toString());
        }
        if (offset != null) {
            params.set("offset", offset.toString());
        }
        const url = `${baseUrl}/api/subagents/search?${params.toString()}`;
        const headers = {};
        if (authToken != null) {
            headers.Authorization = `Bearer ${authToken}`;
        }
        const response = await fetch(url, {
            method: "GET",
            ...(Object.keys(headers).length > 0 ? { headers } : {}),
        });
        if (!response.ok) {
            const errorData = (await response.json().catch(() => ({
                error: `HTTP ${response.status}`,
            })));
            throw new ApiError(errorData.error ?? `HTTP ${response.status}`, response.status);
        }
        return (await response.json());
    },
    /**
     * Get the packument (package metadata) for a subagent
     * @param args - The request parameters
     *
     * @returns The subagent packument
     */
    getSubagentPackument: async (args) => {
        const { subagentName, registryUrl, authToken } = args;
        const baseUrl = registryUrl ?? REGISTRAR_URL;
        const url = `${baseUrl}/api/subagents/${subagentName}`;
        const headers = {};
        if (authToken != null) {
            headers.Authorization = `Bearer ${authToken}`;
        }
        const response = await fetch(url, {
            method: "GET",
            ...(Object.keys(headers).length > 0 ? { headers } : {}),
        });
        if (!response.ok) {
            const errorData = (await response.json().catch(() => ({
                error: `HTTP ${response.status}`,
            })));
            throw new ApiError(errorData.error ?? `HTTP ${response.status}`, response.status);
        }
        return (await response.json());
    },
    /**
     * Download a tarball for a subagent
     *
     * If no version is specified, the latest version is downloaded.
     * @param args - The download parameters
     *
     * @returns The tarball data as ArrayBuffer
     */
    downloadSubagentTarball: async (args) => {
        const { subagentName, registryUrl, authToken } = args;
        const baseUrl = registryUrl ?? REGISTRAR_URL;
        let { version } = args;
        // If no version specified, resolve latest from packument
        if (version == null) {
            const packument = await registrarApi.getSubagentPackument({
                subagentName,
                registryUrl,
                authToken,
            });
            version = packument["dist-tags"].latest;
            if (version == null) {
                throw new Error(`No latest version found for subagent: ${subagentName}`);
            }
        }
        const tarballFilename = `${subagentName}-${version}.tgz`;
        const url = `${baseUrl}/api/subagents/${subagentName}/tarball/${tarballFilename}`;
        const headers = {};
        if (authToken != null) {
            headers.Authorization = `Bearer ${authToken}`;
        }
        const response = await fetch(url, {
            method: "GET",
            ...(Object.keys(headers).length > 0 ? { headers } : {}),
        });
        if (!response.ok) {
            const errorData = (await response.json().catch(() => ({
                error: `HTTP ${response.status}`,
            })));
            throw new ApiError(errorData.error ?? `HTTP ${response.status}`, response.status);
        }
        return await response.arrayBuffer();
    },
};
//# sourceMappingURL=registrar.js.map