/*
 * Copyright (c) 2025, Salesforce, Inc.
 * SPDX-License-Identifier: Apache-2
 * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
 */
/**
 * Account Manager API client for B2C Commerce.
 *
 * Provides clients for the Account Manager REST APIs including users, roles, and organizations.
 * Uses openapi-fetch with OAuth authentication middleware for users and roles,
 * and fetch with OAuth for organizations.
 *
 * @module clients/am-api
 */
import createClient, {} from 'openapi-fetch';
import { createAuthMiddleware, createLoggingMiddleware } from './middleware.js';
import { globalMiddlewareRegistry } from './middleware-registry.js';
import { DEFAULT_ACCOUNT_MANAGER_HOST } from '../defaults.js';
import { getLogger } from '../logging/logger.js';
/**
 * Regex for Account Manager role tenant filter format:
 * ROLE_ENUM:realm_instance(,realm_instance)*(;ROLE_ENUM:...)*
 * e.g. SALESFORCE_COMMERCE_API:abcd_prd or bm-admin:tenant1,tenant2;ECOM_USER:wxyz_stg
 */
export const ROLE_TENANT_FILTER_PATTERN = /^(\w+:\w{4,}_\w{3,}(,\w{4,}_\w{3,})*(;)?)*$/;
/**
 * Returns true if the value matches the Account Manager role tenant filter format.
 */
export function isValidRoleTenantFilter(value) {
    return value.length > 0 && ROLE_TENANT_FILTER_PATTERN.test(value);
}
/**
 * Fetches all roles from the Account Manager roles API and builds a mapping
 * between role `id` and `roleEnumName`.
 *
 * @param rolesClient - Account Manager Roles client
 * @returns Role mapping
 */
export async function fetchRoleMapping(rolesClient) {
    const result = await listRoles(rolesClient, { size: 100 });
    const byId = new Map();
    const byEnumName = new Map();
    const descriptions = new Map();
    for (const role of result.content || []) {
        if (role.id && role.roleEnumName) {
            byId.set(role.id, role.roleEnumName);
            byEnumName.set(role.roleEnumName, role.id);
            if (role.description) {
                descriptions.set(role.roleEnumName, role.description);
            }
        }
    }
    return { byId, byEnumName, descriptions };
}
/**
 * Resolves a role to its internal `roleEnumName` using an API-fetched role mapping.
 * Accepts either the role `id` (e.g., `bm-admin`) or `roleEnumName` (e.g., `ECOM_ADMIN`).
 * Falls back to a generic transform (uppercase + replace hyphens with underscores) for unknown roles.
 */
export function resolveToInternalRole(role, mapping) {
    // Already a known roleEnumName
    if (mapping.byEnumName.has(role)) {
        return role;
    }
    // Known role id → roleEnumName
    const enumName = mapping.byId.get(role);
    if (enumName) {
        return enumName;
    }
    // Fallback: generic transform
    return role.toUpperCase().replace(/-/g, '_');
}
/**
 * Resolves an internal `roleEnumName` to its external role `id` using an API-fetched role mapping.
 * Falls back to a generic transform (lowercase + replace underscores with hyphens) for unknown roles.
 */
export function resolveFromInternalRole(roleEnumName, mapping) {
    const id = mapping.byEnumName.get(roleEnumName);
    if (id) {
        return id;
    }
    // Fallback: generic transform
    return roleEnumName.toLowerCase().replace(/_/g, '-');
}
/**
 * Creates a typed Account Manager Users API client.
 *
 * Returns the openapi-fetch client directly, with authentication
 * handled via middleware. This gives full access to all openapi-fetch
 * features with type-safe paths, parameters, and responses.
 *
 * @param config - Account Manager Users client configuration
 * @param auth - Authentication strategy (typically OAuth)
 * @returns Typed openapi-fetch client
 *
 * @example
 * // Create Account Manager Users client with OAuth auth
 * const oauthStrategy = new OAuthStrategy({
 *   clientId: 'your-client-id',
 *   clientSecret: 'your-client-secret',
 * });
 *
 * const client = createAccountManagerUsersClient({}, oauthStrategy);
 *
 * // List users
 * const { data, error } = await client.GET('/dw/rest/v1/users', {
 *   params: { query: { pageable: { size: 25, page: 0 } } }
 * });
 */
export function createAccountManagerUsersClient(config, auth) {
    const hostname = config.hostname ?? DEFAULT_ACCOUNT_MANAGER_HOST;
    const registry = config.middlewareRegistry ?? globalMiddlewareRegistry;
    const client = createClient({
        baseUrl: `https://${hostname}`,
    });
    // Core middleware: auth first
    client.use(createAuthMiddleware(auth));
    // Transform pageable query parameters from bracket notation to flattened format
    // This is needed because the API expects size=X&page=Y, not pageable[size]=X&pageable[page]=Y
    client.use(createPageableTransformMiddleware());
    // Plugin middleware from registry
    for (const middleware of registry.getMiddleware('am-users-api')) {
        client.use(middleware);
    }
    // Logging middleware last (sees complete request with all modifications)
    client.use(createLoggingMiddleware('AM-USERS'));
    return client;
}
/**
 * Retrieves details of a user by ID.
 *
 * @param client - Account Manager Users client
 * @param userId - User ID (UUID)
 * @param expand - Optional array of fields to expand (organizations, roles)
 * @returns User details
 * @throws Error if user is not found or request fails
 */
export async function getUser(client, userId, expand) {
    const result = await client.GET('/dw/rest/v1/users/{userId}', {
        params: {
            path: { userId },
            query: expand && expand.length > 0 ? { expand } : undefined,
        },
    });
    if (result.error) {
        const error = result.error;
        if (result.response?.status === 404) {
            throw new Error(`User ${userId} not found`);
        }
        throw new Error(error.error?.message || `Failed to get user: ${JSON.stringify(result.error)}`);
    }
    if (!result.data) {
        throw new Error('No data returned from API');
    }
    return result.data;
}
/**
 * Lists users with pagination.
 *
 * @param client - Account Manager Users client
 * @param options - List options (size, page)
 * @returns Paginated user collection
 * @throws Error if request fails
 */
export async function listUsers(client, options = {}) {
    const { size = 20, page = 0 } = options;
    const result = await client.GET('/dw/rest/v1/users', {
        params: {
            query: {
                pageable: {
                    size,
                    page,
                },
            },
        },
    });
    if (result.error) {
        const error = result.error;
        // Check for pagination out-of-bounds error
        const errorMessage = error.errors?.[0]?.message || error.error?.message;
        if (errorMessage?.includes('fromIndex') && errorMessage?.includes('toIndex')) {
            throw new Error(`Page ${page} is out of bounds. The requested page exceeds the available data. Try a lower page number.`);
        }
        throw new Error(errorMessage || `Failed to list users: ${JSON.stringify(result.error)}`);
    }
    return result.data || { content: [] };
}
/**
 * Creates a new user.
 *
 * @param client - Account Manager Users client
 * @param user - User details
 * @returns Created user
 * @throws Error if request fails
 */
export async function createUser(client, user) {
    const result = await client.POST('/dw/rest/v1/users', {
        body: user,
    });
    if (result.error) {
        const error = result.error;
        throw new Error(error.error?.message || `Failed to create user: ${JSON.stringify(result.error)}`);
    }
    if (!result.data) {
        throw new Error('No data returned from API');
    }
    return result.data;
}
/**
 * Updates an existing user.
 *
 * @param client - Account Manager Users client
 * @param userId - User ID
 * @param changes - Changes to apply
 * @returns Updated user
 * @throws Error if request fails
 */
export async function updateUser(client, userId, changes) {
    const result = await client.PUT('/dw/rest/v1/users/{userId}', {
        params: { path: { userId } },
        body: changes,
    });
    if (result.error) {
        const error = result.error;
        throw new Error(error.error?.message || `Failed to update user: ${JSON.stringify(result.error)}`);
    }
    if (!result.data) {
        throw new Error('No data returned from API');
    }
    return result.data;
}
/**
 * Disables a user (soft delete - sets userState to DELETED).
 * Users must be disabled before they can be purged.
 *
 * @param client - Account Manager Users client
 * @param userId - User ID
 * @throws Error if request fails
 */
export async function deleteUser(client, userId) {
    const result = await client.POST('/dw/rest/v1/users/{userId}/disable', {
        params: { path: { userId } },
        body: {},
    });
    if (result.error) {
        const error = result.error;
        throw new Error(error.error?.message || `Failed to delete user: ${JSON.stringify(result.error)}`);
    }
}
/**
 * Purges a user (hard delete).
 * Users must be in DELETED state before they can be purged.
 *
 * @param client - Account Manager Users client
 * @param userId - User ID
 * @throws Error if request fails
 */
export async function purgeUser(client, userId) {
    const result = await client.DELETE('/dw/rest/v1/users/{userId}', {
        params: { path: { userId } },
    });
    if (result.error) {
        const error = result.error;
        throw new Error(error.error?.message || `Failed to purge user: ${JSON.stringify(result.error)}`);
    }
}
/**
 * Resets a user to INITIAL state and sends activation instructions.
 *
 * @param client - Account Manager Users client
 * @param userId - User ID
 * @throws Error if request fails
 */
export async function resetUser(client, userId) {
    const result = await client.POST('/dw/rest/v1/users/{userId}/reset', {
        params: { path: { userId } },
        body: {},
    });
    if (result.error) {
        const error = result.error;
        throw new Error(error.error?.message || `Failed to reset user: ${JSON.stringify(result.error)}`);
    }
}
/**
 * Finds a user by login (email) using the dedicated search endpoint.
 *
 * @param client - Account Manager Users client
 * @param login - User login (email)
 * @param expand - Optional array of fields to expand (organizations, roles)
 * @returns User if found, undefined if not found
 * @throws Error if request fails
 */
export async function findUserByLogin(client, login, expand) {
    const result = await client.GET('/dw/rest/v1/users/search/findByLogin', {
        params: {
            query: { login },
        },
    });
    if (result.response?.status === 404) {
        return undefined;
    }
    if (result.error) {
        throw new Error(`Failed to search for user: ${JSON.stringify(result.error)}`);
    }
    const found = result.data;
    if (!found) {
        return undefined;
    }
    // If expand is requested, fetch the full user with expanded fields
    if (expand && expand.length > 0 && found.id) {
        return getUser(client, found.id, expand);
    }
    return found;
}
/**
 * Creates a typed Account Manager Roles API client.
 *
 * Returns the openapi-fetch client directly, with authentication
 * handled via middleware. This gives full access to all openapi-fetch
 * features with type-safe paths, parameters, and responses.
 *
 * @param config - Account Manager Roles client configuration
 * @param auth - Authentication strategy (typically OAuth)
 * @returns Typed openapi-fetch client
 *
 * @example
 * // Create Account Manager Roles client with OAuth auth
 * const oauthStrategy = new OAuthStrategy({
 *   clientId: 'your-client-id',
 *   clientSecret: 'your-client-secret',
 * });
 *
 * const client = createAccountManagerRolesClient({}, oauthStrategy);
 *
 * // List roles
 * const { data, error } = await client.GET('/dw/rest/v1/roles', {
 *   params: { query: { pageable: { size: 25, page: 0 } } }
 * });
 */
export function createAccountManagerRolesClient(config, auth) {
    const hostname = config.hostname ?? DEFAULT_ACCOUNT_MANAGER_HOST;
    const registry = config.middlewareRegistry ?? globalMiddlewareRegistry;
    const client = createClient({
        baseUrl: `https://${hostname}`,
    });
    // Core middleware: auth first
    client.use(createAuthMiddleware(auth));
    // Transform pageable query parameters from bracket notation to flattened format
    // This is needed because the API expects size=X&page=Y, not pageable[size]=X&pageable[page]=Y
    client.use(createPageableTransformMiddleware());
    // Plugin middleware from registry
    for (const middleware of registry.getMiddleware('am-roles-api')) {
        client.use(middleware);
    }
    // Logging middleware last (sees complete request with all modifications)
    client.use(createLoggingMiddleware('AM-ROLES'));
    return client;
}
/**
 * Retrieves details of a role by ID.
 *
 * @param client - Account Manager Roles client
 * @param roleId - Role ID
 * @returns Role details
 * @throws Error if role is not found or request fails
 */
export async function getRole(client, roleId) {
    const result = await client.GET('/dw/rest/v1/roles/{roleId}', {
        params: { path: { roleId } },
    });
    if (result.error) {
        const error = result.error;
        if (result.response?.status === 404) {
            throw new Error(`Role ${roleId} not found`);
        }
        throw new Error(error.error?.message || `Failed to get role: ${JSON.stringify(result.error)}`);
    }
    if (!result.data) {
        throw new Error('No data returned from API');
    }
    return result.data;
}
/**
 * Lists roles with pagination.
 *
 * @param client - Account Manager Roles client
 * @param options - List options (size, page, roleTargetType)
 * @returns Paginated role collection
 * @throws Error if request fails
 */
export async function listRoles(client, options = {}) {
    const { size = 20, page = 0, roleTargetType } = options;
    const result = await client.GET('/dw/rest/v1/roles', {
        params: {
            query: {
                pageable: {
                    size,
                    page,
                },
                ...(roleTargetType && { roleTargetType }),
            },
        },
    });
    if (result.error) {
        const error = result.error;
        // Check for pagination out-of-bounds error
        const errorMessage = error.errors?.[0]?.message || error.error?.message;
        if (errorMessage?.includes('fromIndex') && errorMessage?.includes('toIndex')) {
            throw new Error(`Page ${page} is out of bounds. The requested page exceeds the available data. Try a lower page number.`);
        }
        throw new Error(errorMessage || `Failed to list roles: ${JSON.stringify(result.error)}`);
    }
    return result.data || { content: [] };
}
/**
 * Creates a typed Account Manager API Clients API client.
 *
 * @param config - Account Manager client configuration
 * @param auth - Authentication strategy (typically OAuth)
 * @returns Typed openapi-fetch client for API Clients API
 */
export function createAccountManagerApiClientsClient(config, auth) {
    const hostname = config.hostname ?? DEFAULT_ACCOUNT_MANAGER_HOST;
    const registry = config.middlewareRegistry ?? globalMiddlewareRegistry;
    const client = createClient({
        baseUrl: `https://${hostname}`,
    });
    client.use(createAuthMiddleware(auth));
    client.use(createPageableTransformMiddleware());
    for (const middleware of registry.getMiddleware('am-apiclients-api')) {
        client.use(middleware);
    }
    client.use(createLoggingMiddleware('AM-APICLIENTS'));
    return client;
}
/**
 * Lists API clients with pagination.
 */
export async function listApiClients(client, options = {}) {
    const { size = 20, page = 0 } = options;
    const result = await client.GET('/dw/rest/v1/apiclients', {
        params: {
            query: {
                pageable: {
                    size,
                    page,
                },
            },
        },
    });
    if (result.error) {
        const error = result.error;
        const errorMessage = error.errors?.[0]?.message || error.error?.message;
        throw new Error(errorMessage || `Failed to list API clients: ${JSON.stringify(result.error)}`);
    }
    return result.data || { content: [] };
}
/**
 * Retrieves an API client by ID.
 */
export async function getApiClient(client, apiClientId, expand) {
    const result = await client.GET('/dw/rest/v1/apiclients/{apiClientId}', {
        params: {
            path: { apiClientId },
            query: expand && expand.length > 0 ? { expand } : undefined,
        },
    });
    if (result.error) {
        const error = result.error;
        if (result.response?.status === 404) {
            throw new Error(`API client ${apiClientId} not found`);
        }
        throw new Error(error.error?.message || `Failed to get API client: ${JSON.stringify(result.error)}`);
    }
    if (!result.data) {
        throw new Error('No data returned from API');
    }
    return result.data;
}
/**
 * Creates a new API client.
 * Omits active when false so the API uses its default (inactive); some implementations
 * reject or mishandle explicit active: false and return "invalid argument APIClient".
 */
export async function createApiClient(client, body) {
    const wireBody = body.active === false
        ? (() => {
            const { active: _a, ...rest } = body;
            return rest;
        })()
        : body;
    const result = await client.POST('/dw/rest/v1/apiclients', {
        body: wireBody,
    });
    if (result.error) {
        const err = result.error;
        const first = err.errors?.[0];
        const fieldHint = first?.fieldErrors
            ?.map((fe) => `${fe.field}: ${fe.defaultMessage ?? ''}`)
            .filter(Boolean)
            .join('; ') || first?.field;
        const errorMessage = fieldHint
            ? `${first?.message ?? err.error?.message ?? 'Bad Request'} (${fieldHint})`
            : (first?.message ?? err.error?.message);
        throw new Error(errorMessage || `Failed to create API client: ${JSON.stringify(result.error)}`);
    }
    if (!result.data) {
        throw new Error('No data returned from API');
    }
    return result.data;
}
/**
 * Updates an existing API client.
 */
export async function updateApiClient(client, apiClientId, body) {
    const result = await client.PUT('/dw/rest/v1/apiclients/{apiClientId}', {
        params: { path: { apiClientId } },
        body,
    });
    if (result.error) {
        const err = result.error;
        const first = err.errors?.[0];
        const fieldHint = first?.fieldErrors
            ?.map((fe) => `${fe.field}: ${fe.defaultMessage ?? ''}`)
            .filter(Boolean)
            .join('; ') || first?.field;
        const errorMessage = fieldHint
            ? `${first?.message ?? err.error?.message ?? 'Invalid request'} (${fieldHint})`
            : (first?.message ?? err.error?.message);
        throw new Error(errorMessage || `Failed to update API client: ${JSON.stringify(result.error)}`);
    }
    if (!result.data) {
        throw new Error('No data returned from API');
    }
    return result.data;
}
/**
 * Deletes an API client. Only clients disabled for at least 7 days can be deleted.
 */
export async function deleteApiClient(client, apiClientId) {
    const result = await client.DELETE('/dw/rest/v1/apiclients/{apiClientId}', {
        params: { path: { apiClientId } },
    });
    if (result.error) {
        const error = result.error;
        if (result.response?.status === 412) {
            throw new Error('API client must be disabled for at least 7 days before it can be deleted.');
        }
        throw new Error(error.error?.message || `Failed to delete API client: ${JSON.stringify(result.error)}`);
    }
}
/**
 * Changes the password for an API client.
 */
export async function changeApiClientPassword(client, apiClientId, oldPassword, newPassword) {
    const result = await client.PUT('/dw/rest/v1/apiclients/{apiClientId}/password', {
        params: { path: { apiClientId } },
        body: { old: oldPassword, new: newPassword },
    });
    if (result.error) {
        const error = result.error;
        throw new Error(error.error?.message || `Failed to change API client password: ${JSON.stringify(result.error)}`);
    }
}
/**
 * Transforms the API organization representation to an external format.
 * Removes internal properties like 'links' that should not be exposed.
 *
 * @param org - The original organization object
 * @returns The transformed organization object
 */
function toExternalOrg(org) {
    // Create a copy to avoid mutating the original
    const transformed = { ...org };
    // Always delete the links property
    delete transformed.links;
    return transformed;
}
/**
 * Creates an Account Manager Organizations API client.
 *
 * @param config - Account Manager Organizations client configuration
 * @param auth - Authentication strategy (typically OAuth)
 * @returns Organizations API client
 *
 * @example
 * const oauthStrategy = new OAuthStrategy({
 *   clientId: 'your-client-id',
 *   clientSecret: 'your-client-secret',
 * });
 *
 * const client = createAccountManagerOrgsClient({}, oauthStrategy);
 *
 * // List organizations
 * const orgs = await client.listOrgs({ size: 25, page: 0 });
 *
 * // Get organization by ID
 * const org = await client.getOrg('org-id');
 */
export function createAccountManagerOrgsClient(config, auth) {
    const hostname = config.hostname ?? DEFAULT_ACCOUNT_MANAGER_HOST;
    const baseUrl = `https://${hostname}/dw/rest/v1`;
    const logger = getLogger();
    const registry = config.middlewareRegistry ?? globalMiddlewareRegistry;
    // Get middleware from registry for am-orgs-api
    const pluginMiddleware = registry.getMiddleware('am-orgs-api');
    /**
     * Applies middleware chain to a request.
     * Adapts openapi-fetch middleware to work with fetch requests.
     */
    async function applyMiddleware(request) {
        let processedRequest = request;
        // Apply auth middleware (core)
        if (auth.getAuthorizationHeader) {
            const authHeader = await auth.getAuthorizationHeader();
            processedRequest.headers.set('Authorization', authHeader);
        }
        // Apply plugin middleware from registry
        for (const middleware of pluginMiddleware) {
            if (middleware.onRequest) {
                // Create minimal openapi-fetch context
                const result = await middleware.onRequest({
                    request: processedRequest,
                    schemaPath: '',
                    params: {},
                    id: '',
                    options: {
                        baseUrl: baseUrl,
                        parseAs: 'json',
                        querySerializer: (params) => new URLSearchParams(params).toString(),
                        bodySerializer: JSON.stringify,
                        fetch: fetch,
                    },
                });
                // Middleware can return Request or Response, but we only want Request here
                if (result && result instanceof Request) {
                    processedRequest = result;
                }
            }
        }
        // Apply logging middleware (last, so it sees all modifications)
        logger.debug({ method: processedRequest.method, url: processedRequest.url }, '[AM-ORGS] Making request');
        logger.trace({
            method: processedRequest.method,
            url: processedRequest.url,
            headers: (() => {
                const o = {};
                processedRequest.headers.forEach((value, key) => {
                    o[key] = value;
                });
                return o;
            })(),
        }, '[AM-ORGS] Request details');
        return processedRequest;
    }
    /**
     * Applies middleware chain to a response.
     */
    async function processResponse(request, response) {
        let processedResponse = response;
        // Apply plugin middleware from registry
        for (const middleware of pluginMiddleware) {
            if (middleware.onResponse) {
                const result = await middleware.onResponse({
                    request,
                    response: processedResponse,
                    schemaPath: '',
                    params: {},
                    id: '',
                    options: {
                        baseUrl: baseUrl,
                        parseAs: 'json',
                        querySerializer: (params) => new URLSearchParams(params).toString(),
                        bodySerializer: JSON.stringify,
                        fetch: fetch,
                    },
                });
                if (result) {
                    processedResponse = result;
                }
            }
        }
        // Apply logging middleware (last)
        logger.debug({ method: request.method, url: request.url, status: processedResponse.status }, '[AM-ORGS] Received response');
        return processedResponse;
    }
    /**
     * Makes an authenticated request to the Account Manager API with middleware support.
     */
    async function makeRequest(path, options = {}) {
        const url = `${baseUrl}${path}`;
        let request = new Request(url, {
            ...options,
            headers: new Headers(options.headers),
        });
        // Apply middleware chain to request
        request = await applyMiddleware(request);
        const response = await fetch(request);
        // Apply middleware chain to response
        const processedResponse = await processResponse(request, response);
        // Handle errors
        if (processedResponse.status === 401) {
            throw new Error('Authentication invalid. Please (re-)authenticate.');
        }
        if (processedResponse.status === 403) {
            throw new Error('Operation forbidden. Please make sure you have the permission to perform this operation.');
        }
        if (processedResponse.status >= 400) {
            throw new Error(`Operation failed. Error code ${processedResponse.status}`);
        }
        if (!processedResponse.ok) {
            throw new Error(`Request failed: ${processedResponse.statusText}`);
        }
        return processedResponse.json();
    }
    return {
        async getOrg(orgId) {
            logger.debug({ orgId }, '[AM-ORGS] Getting organization by ID');
            try {
                const org = await makeRequest(`/organizations/${orgId}`);
                return toExternalOrg(org);
            }
            catch (error) {
                if (error instanceof Error && error.message.includes('Error code 404')) {
                    throw new Error(`Organization ${orgId} not found`);
                }
                throw error;
            }
        },
        async getOrgByName(name) {
            logger.debug({ name }, '[AM-ORGS] Getting organization by name');
            const encodedName = encodeURIComponent(name);
            let result;
            try {
                result = await makeRequest(`/organizations/search/findByName?startsWith=${encodedName}&ignoreCase=false`);
            }
            catch (error) {
                if (error instanceof Error && error.message.includes('Error code 404')) {
                    throw new Error(`Organization ${name} not found`);
                }
                throw error;
            }
            if (result.content.length === 0) {
                throw new Error(`Organization ${name} not found`);
            }
            if (result.content.length > 1) {
                // Attempt to find exact match
                const exactMatch = result.content.find((org) => org.name === name);
                if (exactMatch) {
                    return toExternalOrg(exactMatch);
                }
                throw new Error(`Organization name "${name}" is ambiguous. Multiple organizations found.`);
            }
            return toExternalOrg(result.content[0]);
        },
        async listOrgs(options = {}) {
            const { size = 25, page = 0, all = false } = options;
            const pageSize = all ? 5000 : size;
            logger.debug({ size: pageSize, page }, '[AM-ORGS] Listing organizations');
            const result = await makeRequest(`/organizations?page=${page}&size=${pageSize}`);
            // Remove links from all organizations in the collection
            return {
                ...result,
                content: result.content.map((org) => toExternalOrg(org)),
            };
        },
    };
}
// ============================================================================
// Shared Utilities
// ============================================================================
/**
 * Middleware to transform pageable query parameters from bracket notation
 * (pageable[size]=X&pageable[page]=Y) to flattened format (size=X&page=Y)
 * that the Account Manager API expects.
 */
function createPageableTransformMiddleware() {
    const logger = getLogger();
    return {
        async onRequest({ request }) {
            const url = new URL(request.url);
            // Check if URL has pageable[size] or pageable[page] parameters
            const pageableSize = url.searchParams.get('pageable[size]');
            const pageablePage = url.searchParams.get('pageable[page]');
            if (pageableSize !== null || pageablePage !== null) {
                // Remove the bracket notation parameters
                url.searchParams.delete('pageable[size]');
                url.searchParams.delete('pageable[page]');
                // Add flattened parameters
                if (pageableSize !== null) {
                    url.searchParams.set('size', pageableSize);
                }
                if (pageablePage !== null) {
                    url.searchParams.set('page', pageablePage);
                }
                logger.trace({
                    originalUrl: request.url,
                    transformedUrl: url.toString(),
                    size: pageableSize,
                    page: pageablePage,
                }, '[AM] Transformed pageable query parameters from bracket to flattened notation');
                return new Request(url.toString(), {
                    method: request.method,
                    headers: request.headers,
                    body: request.body,
                });
            }
            return request;
        },
    };
}
/**
 * Creates a unified Account Manager API client.
 *
 * This client provides direct access to all Account Manager API methods (users, roles, orgs)
 * through a single interface, while internally using separate typed clients for type safety.
 *
 * @param config - Account Manager client configuration
 * @param auth - Authentication strategy (typically OAuth)
 * @returns Unified Account Manager client
 *
 * @example
 * const oauthStrategy = new OAuthStrategy({
 *   clientId: 'your-client-id',
 *   clientSecret: 'your-client-secret',
 * });
 *
 * const client = createAccountManagerClient({}, oauthStrategy);
 *
 * // Users API
 * const users = await client.listUsers({ size: 25, page: 0 });
 * const user = await client.getUser('user-id');
 * await client.createUser({ mail: 'user@example.com', ... });
 *
 * // Roles API
 * const roles = await client.listRoles({ size: 20, page: 0 });
 * const role = await client.getRole('bm-admin');
 *
 * // Organizations API
 * const orgs = await client.listOrgs({ size: 25, page: 0 });
 * const org = await client.getOrg('org-id');
 */
export function createAccountManagerClient(config, auth) {
    const logger = getLogger();
    // Create internal clients (all use the same config, however, specifications are different)
    const usersClient = createAccountManagerUsersClient(config, auth);
    const rolesClient = createAccountManagerRolesClient(config, auth);
    const apiClientsClient = createAccountManagerApiClientsClient(config, auth);
    const orgsClient = createAccountManagerOrgsClient(config, auth);
    // Lazily cached role mapping
    let cachedRoleMapping;
    async function getRoleMapping() {
        if (!cachedRoleMapping) {
            cachedRoleMapping = await fetchRoleMapping(rolesClient);
        }
        return cachedRoleMapping;
    }
    // Lazily cached org mapping
    let cachedOrgMapping;
    async function getOrgMapping() {
        if (!cachedOrgMapping) {
            const result = await orgsClient.listOrgs({ all: true });
            const byId = new Map();
            for (const org of result.content || []) {
                if (org.id && org.name) {
                    byId.set(org.id, org.name);
                }
            }
            cachedOrgMapping = { byId };
        }
        return cachedOrgMapping;
    }
    // Return unified client with all methods
    return {
        // Users API methods
        getUser: (userId, expand) => getUser(usersClient, userId, expand),
        listUsers: (options) => listUsers(usersClient, options),
        createUser: (user) => createUser(usersClient, user),
        updateUser: (userId, changes) => updateUser(usersClient, userId, changes),
        deleteUser: (userId) => deleteUser(usersClient, userId),
        purgeUser: (userId) => purgeUser(usersClient, userId),
        resetUser: (userId) => resetUser(usersClient, userId),
        findUserByLogin: (login, expand) => findUserByLogin(usersClient, login, expand),
        grantRole: async (userId, role, scope) => {
            const roleMapping = await getRoleMapping();
            // Resolve to both formats: role ID for roles array, roleEnumName for roleTenantFilter
            const enumName = resolveToInternalRole(role, roleMapping);
            const roleId = resolveFromInternalRole(enumName, roleMapping);
            logger.debug({ role, roleId, enumName }, `[AM] Resolved role '${role}' → id='${roleId}', enum='${enumName}'`);
            const user = await getUser(usersClient, userId);
            // Build updated roles (uses role ID format, e.g. 'bm-admin')
            const currentRoles = Array.isArray(user.roles)
                ? user.roles.map((r) => (typeof r === 'string' ? r : r.id || ''))
                : [];
            const updatedRoles = currentRoles.includes(roleId) ? currentRoles : [...currentRoles, roleId];
            // Build updated roleTenantFilter (uses roleEnumName format, e.g. 'ECOM_ADMIN')
            let roleTenantFilter = user.roleTenantFilter || '';
            if (scope) {
                const scopes = scope.split(',');
                // Parse existing filter
                const filters = roleTenantFilter.split(';').filter(Boolean);
                const filterMap = new Map();
                for (const filter of filters) {
                    const [r, tenants] = filter.split(':');
                    if (tenants) {
                        filterMap.set(r, tenants.split(','));
                    }
                }
                // Add new scopes
                const existingScopes = filterMap.get(enumName) || [];
                const allScopes = [...new Set([...existingScopes, ...scopes])];
                filterMap.set(enumName, allScopes);
                // Rebuild filter string
                roleTenantFilter = Array.from(filterMap.entries())
                    .map(([r, tenants]) => `${r}:${tenants.join(',')}`)
                    .join(';');
            }
            return updateUser(usersClient, userId, {
                roles: updatedRoles,
                roleTenantFilter: roleTenantFilter || undefined,
            });
        },
        revokeRole: async (userId, role, scope) => {
            const roleMapping = await getRoleMapping();
            const enumName = resolveToInternalRole(role, roleMapping);
            const roleId = resolveFromInternalRole(enumName, roleMapping);
            logger.debug({ role, roleId, enumName }, `[AM] Resolved role '${role}' → id='${roleId}', enum='${enumName}'`);
            const user = await getUser(usersClient, userId);
            // Build updated roles (uses role ID format, e.g. 'bm-admin')
            const currentRoles = Array.isArray(user.roles)
                ? user.roles.map((r) => (typeof r === 'string' ? r : r.id || ''))
                : [];
            let updatedRoles = currentRoles;
            // Build updated roleTenantFilter (uses roleEnumName format, e.g. 'ECOM_ADMIN')
            let roleTenantFilter = user.roleTenantFilter || '';
            if (!scope) {
                // Remove entire role
                updatedRoles = currentRoles.filter((r) => r !== roleId);
                // Remove role from filter
                const filters = roleTenantFilter.split(';').filter(Boolean);
                roleTenantFilter = filters.filter((filter) => !filter.startsWith(`${enumName}:`)).join(';');
            }
            else {
                // Remove specific scope
                const scopes = scope.split(',');
                const filters = roleTenantFilter.split(';').filter(Boolean);
                const filterMap = new Map();
                for (const filter of filters) {
                    const [r, tenants] = filter.split(':');
                    if (tenants) {
                        filterMap.set(r, tenants.split(','));
                    }
                }
                const existingScopes = filterMap.get(enumName) || [];
                const remainingScopes = existingScopes.filter((s) => !scopes.includes(s));
                if (remainingScopes.length === 0) {
                    // No scopes left, remove role entirely
                    updatedRoles = currentRoles.filter((r) => r !== roleId);
                    filterMap.delete(enumName);
                }
                else {
                    filterMap.set(enumName, remainingScopes);
                }
                // Rebuild filter string
                roleTenantFilter = Array.from(filterMap.entries())
                    .map(([r, tenants]) => `${r}:${tenants.join(',')}`)
                    .join(';');
            }
            return updateUser(usersClient, userId, {
                roles: updatedRoles,
                roleTenantFilter: roleTenantFilter || undefined,
            });
        },
        // Roles API methods
        getRole: (roleId) => getRole(rolesClient, roleId),
        listRoles: (options) => listRoles(rolesClient, options),
        getRoleMapping: () => getRoleMapping(),
        getOrgMapping: () => getOrgMapping(),
        // API Clients API methods
        listApiClients: (options) => listApiClients(apiClientsClient, options),
        getApiClient: (apiClientId, expand) => getApiClient(apiClientsClient, apiClientId, expand),
        createApiClient: (body) => createApiClient(apiClientsClient, body),
        updateApiClient: (apiClientId, body) => updateApiClient(apiClientsClient, apiClientId, body),
        deleteApiClient: (apiClientId) => deleteApiClient(apiClientsClient, apiClientId),
        changeApiClientPassword: (apiClientId, oldPassword, newPassword) => changeApiClientPassword(apiClientsClient, apiClientId, oldPassword, newPassword),
        // Organizations API methods
        getOrg: (orgId) => orgsClient.getOrg(orgId),
        getOrgByName: (name) => orgsClient.getOrgByName(name),
        listOrgs: (options) => orgsClient.listOrgs(options),
    };
}
//# sourceMappingURL=am-api.js.map