/**
 * Domain utility functions for working with app identifiers and data normalization
 */
import { ZapierValidationError, ZapierConfigurationError, } from "../types/errors";
import { toSnakeCase } from "./string-utils";
/**
 * Checks if a string value looks like a direct connection ID rather than a named alias.
 * Returns true for numeric strings and UUIDs.
 */
export function isConnectionId(value) {
    return /^\d+$/.test(value) || isUuid(value);
}
/**
 * Resolves a connectionId from the connection-related parameters that can be passed
 * to SDK methods (connectionId, connection, authenticationId).
 *
 * Handles mutual exclusivity validation, string alias lookup via the connections map,
 * and numeric passthrough.
 */
export async function resolveConnectionId({ connectionId, connection, authenticationId, resolveConnection, label, }) {
    const hasDirectId = connectionId != null || authenticationId != null;
    if (hasDirectId && connection != null) {
        const context = label ? ` for ${label}` : "";
        throw new ZapierValidationError(`Cannot specify both connectionId/authenticationId and connection${context}. Use connectionId for a direct ID, or connection for an alias or numeric ID.`);
    }
    let resolvedConnectionId = connectionId ?? authenticationId ?? undefined;
    if (connection != null && resolvedConnectionId == null) {
        if (typeof connection === "number") {
            resolvedConnectionId = connection;
        }
        else {
            // Try the connections map first, then fall back to direct ID for
            // numeric strings and UUIDs.
            const entry = await resolveConnection(connection);
            if (entry) {
                resolvedConnectionId = entry.connectionId;
            }
            else if (isConnectionId(connection)) {
                resolvedConnectionId = connection;
            }
            else {
                throw new ZapierConfigurationError(`Connection "${connection}" not found in connections map. ` +
                    "Ensure the connection is defined in the manifest connections, or pass a connection ID directly.", { configType: "connections" });
            }
        }
    }
    return resolvedConnectionId;
}
/**
 * Splits a versioned key to extract the base key and version
 *
 * @param versionedKey - Versioned key in format "KeyName@version" (e.g., "SlackCLIAPI@1.21.1")
 * @returns Tuple of [baseKey, version] or [versionedKey, undefined] if no @ symbol
 *
 * @example
 * splitVersionedKey("SlackCLIAPI@1.21.1") // ["SlackCLIAPI", "1.21.1"]
 * splitVersionedKey("SomeAPI") // ["SomeAPI", undefined]
 */
export function splitVersionedKey(versionedKey) {
    const parts = versionedKey.split("@");
    if (parts.length >= 2) {
        const baseKey = parts[0];
        const version = parts.slice(1).join("@"); // Handle edge case of multiple @ symbols
        return [baseKey, version];
    }
    return [versionedKey, undefined];
}
/**
 * Converts a lightweight ImplementationMeta to a slimmed AppItem
 *
 * @param implementationMeta - Raw ImplementationMeta from API
 * @returns Normalized AppItem with essential fields only
 */
export function normalizeImplementationMetaToAppItem(implementationMeta) {
    const [selectedApi, appVersion] = splitVersionedKey(implementationMeta.id);
    const { id, name, ...restOfImplementationMeta } = implementationMeta;
    return {
        ...restOfImplementationMeta,
        title: name,
        key: selectedApi,
        implementation_id: id,
        version: appVersion,
    };
}
export function normalizeActionItem(action) {
    const { name, type, selected_api: selectedApi } = action;
    // Split version from selected_api
    const [appKey, appVersion] = selectedApi
        ? splitVersionedKey(selectedApi)
        : ["", undefined];
    return {
        // Only include the fields we want - explicitly list them
        id: action.id,
        key: action.key,
        description: action.description,
        is_important: action.is_important,
        is_hidden: action.is_hidden,
        // Transformed fields
        app_key: appKey,
        app_version: appVersion,
        action_type: type,
        title: name, // Map name to title
        type: "action",
    };
}
/**
 * Prefer public IDs over numeric IDs in a connection item.
 * The API returns extra public_id, account_public_id, and customuser_public_id
 * fields alongside the stringified numeric IDs. This function swaps them in.
 */
export function transformConnectionItem(item) {
    const raw = item;
    const publicId = raw.public_id;
    const accountPublicId = raw.account_public_id;
    const customuserPublicId = raw.customuser_public_id;
    return {
        id: String(publicId ?? item.id),
        date: item.date,
        account_id: String(accountPublicId ?? item.account_id),
        is_invite_only: item.is_invite_only,
        is_private: item.is_private,
        shared_with_all: item.shared_with_all,
        title: item.title,
        lastchanged: item.lastchanged,
        is_stale: item.is_stale,
        is_shared: item.is_shared,
        identifier: item.identifier,
        groups: item.groups,
        members: item.members,
        permissions: item.permissions,
        implementation_id: item.implementation_id,
        profile_id: customuserPublicId
            ? String(customuserPublicId)
            : item.profile_id,
        is_expired: item.is_expired,
        expired_at: item.expired_at,
        app_key: item.app_key,
        app_version: item.app_version,
    };
}
/**
 * Groups app keys by their type based on format patterns
 *
 * @param appKeys Array of app key strings to categorize
 * @returns Object with arrays of keys grouped by type: selectedApi (CLI APIs), slug (everything else)
 *
 * @example
 * groupVersionedAppKeysByType([
 *   'FormatterCLIAPI@1.0.0',
 *   'slack',
 *   'AnotherAPI'
 * ])
 * // Returns: {
 * //   selectedApi: ['FormatterCLIAPI@1.0.0', 'AnotherAPI'],
 * //   slug: ['slack']
 * // }
 */
export function groupVersionedAppKeysByType(appKeys) {
    const result = {
        selectedApi: [],
        slug: [],
    };
    // Use Sets to track seen keys for deduplication
    const seenSelectedApi = new Set();
    const seenSlugs = new Set();
    for (const key of appKeys) {
        const appLocator = toAppLocator(key);
        if (appLocator.slug) {
            // For slugs, we need to reconstruct the versioned slug
            const versionedSlug = appLocator.version
                ? `${appLocator.slug}@${appLocator.version}`
                : appLocator.slug;
            if (!seenSlugs.has(versionedSlug)) {
                seenSlugs.add(versionedSlug);
                result.slug.push(versionedSlug);
            }
        }
        else {
            // For implementation names (selectedApi)
            if (!seenSelectedApi.has(key)) {
                seenSelectedApi.add(key);
                result.selectedApi.push(key);
            }
        }
    }
    return result;
}
export function groupAppKeysByType(appKeys) {
    const grouped = groupVersionedAppKeysByType(appKeys);
    // Extract base keys and dedupe by using Set
    return {
        selectedApi: [
            ...new Set(grouped.selectedApi.map((key) => key.split("@")[0])),
        ],
        slug: [...new Set(grouped.slug.map((key) => key.split("@")[0]))],
    };
}
export function isSlug(slug) {
    return !!slug.match(/^[a-z0-9]+(?:-[a-z0-9]+)*$/);
}
export function isSnakeCasedSlug(slug) {
    // Allow leading underscore for slugs starting with a number.
    if (slug.match(/^_[0-9]/)) {
        slug = slug.slice(1);
    }
    return !!slug.match(/^[a-z0-9]+(?:_[a-z0-9]+)*$/);
}
export function dashifySnakeCasedSlug(slug) {
    // Only dashify if it's a valid snake_cased slug.
    if (!isSnakeCasedSlug(slug)) {
        return slug;
    }
    // Allow one leading underscore for slugs starting with a number.
    if (slug.startsWith("_")) {
        slug = slug.slice(1);
    }
    return slug.replace(/_/g, "-");
}
export function isUuid(appKey) {
    return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(appKey);
}
export function toAppLocator(appKey) {
    const [appKeyWithoutVersion, version] = splitVersionedKey(appKey);
    if (isUuid(appKeyWithoutVersion)) {
        throw new Error(`UUID app keys are not supported. Use app slug or implementation ID instead of: ${appKey}`);
    }
    const slug = isSlug(appKeyWithoutVersion)
        ? appKeyWithoutVersion
        : isSnakeCasedSlug(appKeyWithoutVersion)
            ? dashifySnakeCasedSlug(appKeyWithoutVersion)
            : undefined;
    return {
        lookupAppKey: appKeyWithoutVersion,
        slug,
        implementationName: slug ? undefined : appKeyWithoutVersion,
        version,
    };
}
export function isResolvedAppLocator(appLocator) {
    return !!appLocator.implementationName;
}
export function toImplementationId(appLocator) {
    return `${appLocator.implementationName}@${appLocator.version || "latest"}`;
}
export function getAppKeyList(app) {
    const keys = new Set([app.slug, toSnakeCase(app.slug), app.key].filter(Boolean));
    return Array.from(keys);
}
