/**
 * Copyright (c) 2022, Salesforce, Inc.,
 * All rights reserved.
 * For full license text, see the LICENSE.txt file
 */

/*
 *  ATTENTION!
 *  THIS IS A GENERATED FILE FROM https://github.com/salesforce-experience-platform-emu/lds-lightning-platform
 *  If you would like to contribute to LDS, please follow the steps outlined in the git repo.
 *  Any changes made to this file in p4 will be automatically overwritten.
 *  *******************************************************************************************
 */
import { parseAndVisit, gql } from 'force/ldsGraphqlParser';
import * as lightningAppsApi from 'lightning/uiAppsApi';
import * as unstableLightningAppsApi from 'lightning/unstable_uiAppsApi';
import * as lightningLayoutApi from 'lightning/uiLayoutApi';
import * as lightningLayoutUserStateApi from 'lightning/uiLayoutUserStateApi';
import * as unstableLightningLayoutApi from 'lightning/unstable_uiLayoutApi';
import * as lightningListApi from 'lightning/uiListApi';
import * as unstableLightningListApi from 'lightning/unstable_uiListApi';
import * as lightningLookupsApi from 'lightning/uiLookupsApi';
import * as unstableLightningLookupsApi from 'lightning/unstable_uiLookupsApi';
import * as lightningObjectApi from 'lightning/uiObjectInfoApi';
import * as unstableLightningObjectApi from 'lightning/unstable_uiObjectInfoApi';
import * as lightningRecordActionsApi from 'lightning/uiRecordActionsApi';
import * as unstableLightningRecordActionsApi from 'lightning/unstable_uiRecordActionsApi';
import * as lightningRecordApi from 'lightning/uiRecordApi';
import * as unstableLightningRecordApi from 'lightning/unstable_uiRecordApi';
import * as lightningRecordAvatarApi from 'lightning/uiRecordAvatarApi';
import * as unstableLightningRecordAvatarApi from 'lightning/unstable_uiRecordAvatarApi';
import * as lightningRelatedListApi from 'lightning/uiRelatedListApi';
import * as unstableLightningRelatedListApi from 'lightning/unstable_uiRelatedListApi';
import * as lightningGraphQLApi from 'lightning/uiGraphQLApi';
import * as gqlApi from 'force/ldsAdaptersGraphql';
import { getRuntime, O11Y_NAMESPACE_LDS_MOBILE, reportGraphqlQueryParseError } from 'native/ldsRuntimeMobile';
export { registerReportObserver } from 'native/ldsRuntimeMobile';
import { HttpStatusCode } from 'force/luvioEngine';
import { getInstrumentation } from 'o11y/client';
import { API_NAMESPACE, RECORD_REPRESENTATION_NAME, keyBuilderRecord } from 'force/ldsAdaptersUiapi';
import { withDefaultLuvio } from 'native/ldsEngineMobile';
import { evictCacheRecordsByIdsSchema, evictExpiredEntriesSchema } from 'o11y_schema/sf_lds';

const { create, keys } = Object;
const { stringify, parse } = JSON;
const { isArray } = Array;

const IMPERATIVE_ADAPTER_SUFFIX = '_imperative';
const UNSTABLE_ADAPTER_PREFIX = 'unstable_';
function extractWireAdapterName(adapterName) {
    const parts = adapterName.split(UNSTABLE_ADAPTER_PREFIX);
    if (parts.length > 1) {
        return parts[1].substr(0, parts[1].indexOf(IMPERATIVE_ADAPTER_SUFFIX));
    }
    return adapterName.substr(0, adapterName.indexOf(IMPERATIVE_ADAPTER_SUFFIX));
}
let lazyDMLAdapterMap = undefined;
let lazyImperativeAdapterMap = undefined;
function buildAdapterMaps(map) {
    const imperativeAdapterNames = keys(map).filter((name) => name.endsWith(IMPERATIVE_ADAPTER_SUFFIX));
    const imperativeAdapterMap = {};
    for (const adapterName of imperativeAdapterNames) {
        const adapter = map[adapterName];
        imperativeAdapterMap[adapterName] = adapter;
        delete map[adapterName];
        // remove the corresponding wire adapter from the adapter map
        const wireAdapterName = extractWireAdapterName(adapterName);
        delete map[wireAdapterName];
    }
    return {
        dmlAdapterMap: map,
        imperativeAdapterMap,
    };
}
// this function builds the adapter maps lazily
function getAdapterMaps() {
    if (lazyDMLAdapterMap === undefined || lazyImperativeAdapterMap === undefined) {
        // We should eventually be explicit about what we expose here instead of exporting everything from the modules,
        // this is our API contract to native callers and should be explicit
        const map = Object.assign({}, gqlApi, lightningAppsApi, lightningLayoutApi, lightningLayoutUserStateApi, lightningListApi, lightningLookupsApi, lightningObjectApi, lightningRecordActionsApi, lightningRecordApi, lightningRecordAvatarApi, lightningRelatedListApi, lightningGraphQLApi, unstableLightningAppsApi, unstableLightningLayoutApi, unstableLightningListApi, unstableLightningLookupsApi, unstableLightningObjectApi, unstableLightningRecordActionsApi, unstableLightningRecordApi, unstableLightningRecordAvatarApi, unstableLightningRelatedListApi);
        const { dmlAdapterMap, imperativeAdapterMap } = buildAdapterMaps(map);
        lazyDMLAdapterMap = dmlAdapterMap;
        lazyImperativeAdapterMap = imperativeAdapterMap;
    }
    return { dmlAdapterMap: lazyDMLAdapterMap, imperativeAdapterMap: lazyImperativeAdapterMap };
}
function getDMLAdapterFromName(name) {
    const { dmlAdapterMap } = getAdapterMaps();
    return dmlAdapterMap[name];
}
function getImperativeAdapterFromName(name) {
    const { imperativeAdapterMap } = getAdapterMaps();
    return imperativeAdapterMap[name];
}
function getImperativeAdapterNames() {
    const { imperativeAdapterMap } = getAdapterMaps();
    return keys(imperativeAdapterMap);
}

const { draftManager, draftQueue } = getRuntime();

const NATIVE_ERROR_CODE = 'NATIVE_ERROR';
const NON_MUTATING_ADAPTER_MESSAGE = 'adapterId must be a mutating adapter';
const NO_DRAFT_CREATED_MESSAGE = 'the adapter did not generate a draft';
const DRAFT_DOESNT_EXIST_MESSAGE = 'the specified draft does not exist';
/**
 * Error objects don't serialize well over the js-native wire, so our class turns
 * an Error into an object that serializes real nice
 */
class NativeNetworkAdapterErrorResponse {
    constructor(error) {
        this.errorType = 'networkAdapterError';
        this.message = error.message;
        this.name = error.name;
    }
}
class NativeFetchResponse {
    constructor(status, body) {
        this.headers = {};
        this.status = status;
        this.body = body;
    }
    get statusText() {
        const { status } = this;
        switch (status) {
            case HttpStatusCode.Ok:
                return 'OK';
            case HttpStatusCode.Created:
                return 'Created';
            case HttpStatusCode.NoContent:
                return 'No Content';
            case HttpStatusCode.BadRequest:
                return 'Bad Request';
            case HttpStatusCode.ServerError:
                return 'Server Error';
            default:
                return `Unexpected HTTP Status Code: ${status}`;
        }
    }
    get ok() {
        return this.status >= 200 && this.status < 300;
    }
}
class NativeFetchErrorResponse extends NativeFetchResponse {
    constructor() {
        super(...arguments);
        this.errorType = 'fetchResponse';
    }
}
function createNativeFetchErrorResponse(message) {
    return new NativeFetchErrorResponse(HttpStatusCode.BadRequest, {
        errorCode: NATIVE_ERROR_CODE,
        message: message,
    });
}
/**
 * This function takes an unknown error and normalizes it to an Error object
 */
function normalizeError(error) {
    if (typeof error === 'object' && error instanceof Error) {
        return error;
    }
    else if (typeof error === 'string') {
        return new Error(error);
    }
    return new Error(stringify(error));
}

const ldsMobileInstrumentation$1 = getInstrumentation(O11Y_NAMESPACE_LDS_MOBILE);
/**
 * Coerces a cache policy passed in from native to a luvio cache policy
 * @param nativeCachePolicy The cache policy passed in from native
 * @returns A coerced luvio cache policy
 */
function buildCachePolicy(nativeCachePolicy) {
    if (nativeCachePolicy === undefined) {
        return undefined;
    }
    // currently the types match exactly, if we ever decide to deviate then we should coerce here
    return nativeCachePolicy;
}
/**
 * Coerces a request context passed in from native to a luvio request context
 * @param nativeRequestContext request context passed in from native
 * @returns Coerced luvio request context
 */
function buildAdapterRequestContext(nativeRequestContext) {
    if (nativeRequestContext === undefined) {
        return undefined;
    }
    const { cachePolicy, priority, observabilityContext, luvioEventObserver } = nativeRequestContext;
    const requestContext = {
        cachePolicy: buildCachePolicy(cachePolicy),
        priority,
    };
    if (observabilityContext !== undefined && observabilityContext !== null) {
        requestContext.requestCorrelator = {
            observabilityContext,
        };
    }
    if (luvioEventObserver !== undefined) {
        requestContext.eventObservers = [luvioEventObserver];
    }
    return requestContext;
}
function buildInvalidConfigError(error) {
    return {
        data: undefined,
        error: {
            ok: false,
            status: 400,
            statusText: 'INVALID_CONFIG',
            body: error,
            headers: {},
            errorType: 'fetchResponse',
        },
    };
}
function buildNativeCallbackValue(adapterCallbackValue) {
    const { error } = adapterCallbackValue;
    // Luvio NetworkAdapterError objects (which are just Error objects) must be
    // turned into NativeNetworkAdapterErrorResponse because it serializes well
    if (error !== undefined && error.errorType === 'networkAdapterError') {
        return {
            data: undefined,
            error: new NativeNetworkAdapterErrorResponse(error),
        };
    }
    // any other callback value does not need coercion, just retype
    return adapterCallbackValue;
}
/**
 *
 * @param adapterId
 * @returns imperative adapter key
 */
function imperativeAdapterKeyBuilder(adapterId) {
    if (adapterId.startsWith(UNSTABLE_ADAPTER_PREFIX)) {
        return `${adapterId}${IMPERATIVE_ADAPTER_SUFFIX}`;
    }
    return `${UNSTABLE_ADAPTER_PREFIX}${adapterId}${IMPERATIVE_ADAPTER_SUFFIX}`;
}
/**
 * Executes the adapter with the given adapterId and config.  Will call onSnapshot
 * callback with data or error.  Returns an unsubscribe function that should
 * be called to stop receiving updated snapshots.
 *
 * This function returns an error if the given adapterId cannot be found or is not
 * a GET wire adapter.  It will also return an error if it fails to parse the config string.
 */
function subscribeToAdapter(adapterId, config, onSnapshot, nativeAdapterRequestContext) {
    const imperativeAdapterIdentifier = imperativeAdapterKeyBuilder(adapterId);
    const imperativeAdapter = getImperativeAdapterFromName(imperativeAdapterIdentifier);
    const onResponseDelegate = (value) => {
        onSnapshot(buildNativeCallbackValue(value));
    };
    if (imperativeAdapter === undefined) {
        // This check is here for legacy purpose
        // So the consumers still get the same errors
        if (getDMLAdapterFromName(adapterId) === undefined) {
            onResponseDelegate({
                data: undefined,
                error: createNativeFetchErrorResponse(`adapter ${adapterId} not recognized`),
            });
            return () => { };
        }
        onResponseDelegate({
            data: undefined,
            error: createNativeFetchErrorResponse(`${adapterId} is not a GET wire adapter.`),
        });
        return () => { };
    }
    const configObject = parse(config);
    // Check if it's graphQl adapter
    // Parse the query in that case
    const isGql = isGqlAdapter(imperativeAdapterIdentifier);
    if (isGql) {
        try {
            // gql config needs gql query string turned into AST object
            configObject.query = parseAndVisit(configObject.query);
        }
        catch (err) {
            const parseError = normalizeError(err);
            reportGraphqlQueryParseError(parseError);
            // call the callback with error
            onResponseDelegate({
                data: undefined,
                error: createNativeFetchErrorResponse(parseError.message),
            });
            return () => { };
        }
    }
    if (imperativeAdapterIdentifier.includes('graphql')) {
        try {
            //we need to use the gql literal to convert into a document node
            //in order to get through the gql imperative adapter
            configObject.query = gql `
                ${configObject.query}
            `;
        }
        catch (err) {
            const parseError = normalizeError(err);
            reportGraphqlQueryParseError(parseError);
            // call the callback with error
            onResponseDelegate({
                data: undefined,
                error: createNativeFetchErrorResponse(parseError.message),
            });
            return () => { };
        }
    }
    try {
        return imperativeAdapter.subscribe(configObject, buildAdapterRequestContext(nativeAdapterRequestContext), onResponseDelegate);
    }
    catch (err) {
        if (isGql) {
            reportGraphqlQueryParseError(err);
        }
        onResponseDelegate(buildInvalidConfigError(err));
        return () => { };
    }
}
/**
 * Checks if the given adapter id is a graphQL adapter
 *
 * @param imperativeAdapterIdentifier The adapter id to check
 * @returns true if the adapter is graphQL, false otherwise
 */
function isGqlAdapter(imperativeAdapterIdentifier) {
    return keys(gqlApi).indexOf(imperativeAdapterIdentifier) > -1;
}
/**
 *  Executes a DML adapter and calls the onResponse callback upon receiving a response.
 *
 * @param adapter : DML Adapter
 * @param configObject : parsed config
 * @param onResponse : OnResponse
 * @param nativeAdapterRequestContext: Specify cache policy, priority and observability parameters
 */
function invokeDmlAdapter(adapter, configObject, onResponse, nativeAdapterRequestContext) {
    try {
        adapter(configObject, buildAdapterRequestContext(nativeAdapterRequestContext)).then((data) => {
            onResponse({ data, error: undefined });
        }, (error) => {
            onResponse({ data: undefined, error });
        });
    }
    catch (err) {
        // For catching the synchronous error in adapter
        onResponse(buildInvalidConfigError(err));
    }
}
/**
 * @deprecated There is no situation in which any consumer actually wants to replace the draft.
 * call {@link invokeAdapterWithDraftToMerge} instead`.
 * Keep it for now for app back compatibility.
 * Will be removed in future, ideally 2 releases later.
 */
function invokeAdapterWithDraftToReplace(adapterId, config, draftIdToReplace, onResponse, nativeAdapterRequestContext) {
    invokeAdapterWithDraftToMerge(adapterId, config, draftIdToReplace, onResponse, nativeAdapterRequestContext);
}
/**
 * Executes the specified adapter with the given @param adapterId and @param config. Then
 * it merges the draft with the given id with the draft generated by
 * the mutating adapter. Will call onResult callback once with data or error.
 *
 * This function returns an error if the given adapterId cannot be found, or if the
 * adapterId is not a mutating adapter if a draft isn't created, or
 * if it fails to parse the given config string.
 *
 * If the @param adapterId is deleteRecod, the invocation of it will generate a delele draft.
 * The newly generated delete draft will replace the draft specified by @param targetDraftId
 */
function invokeAdapterWithDraftToMerge(adapterId, config, targetDraftId, onResponse, nativeAdapterRequestContext) {
    const adapter = getDMLAdapterFromName(adapterId);
    if (adapter === undefined) {
        // Check if the adapter is non-mutating adapter and create proper error message.
        const message = getImperativeAdapterFromName(imperativeAdapterKeyBuilder(adapterId)) !== undefined
            ? NON_MUTATING_ADAPTER_MESSAGE
            : `adapter ${adapterId} not recognized`;
        onResponse({
            data: undefined,
            error: createNativeFetchErrorResponse(message),
        });
        return;
    }
    // deleteRecord adapter call will generate a delete draft and
    // the newly generate draft will replace the target draft
    if (adapterId === 'deleteRecord') {
        invokeAdapterWithDraftToMergeDeleteRecord(adapter, config, targetDraftId, onResponse, nativeAdapterRequestContext);
        return;
    }
    draftManager.getQueue().then((draftInfo) => {
        const draftIds = draftInfo.items.map((draft) => draft.id);
        if (draftIds.includes(targetDraftId) === false) {
            onResponse({
                data: undefined,
                error: createNativeFetchErrorResponse(DRAFT_DOESNT_EXIST_MESSAGE),
            });
            return;
        }
        invokeDmlAdapter(adapter, parse(config), (responseValue) => {
            const draftIds = draftIdsForResponseValue(responseValue);
            if (responseValue.error === undefined &&
                draftIds !== undefined &&
                draftIds.length > 0) {
                const draftId = draftIds[draftIds.length - 1];
                draftManager
                    .mergeActions(targetDraftId, draftId)
                    .then(() => {
                    if (responseValue.data === undefined ||
                        responseValue.data.drafts === undefined) {
                        onResponse(responseValue);
                        return;
                    }
                    const newResponse = removeDraftNodeActionId(draftId, responseValue);
                    onResponse(newResponse);
                })
                    .catch((error) => {
                    onResponse(convertErrorIntoNativeFetchError(error, `Unknown error merging draft`));
                });
            }
            else {
                let response = responseValue;
                response.error = createNativeFetchErrorResponse(NO_DRAFT_CREATED_MESSAGE);
                onResponse(response);
            }
        }, nativeAdapterRequestContext);
    });
}
/**
 * Executes the specified adapter with the given adapterId and config. Then
 * it sets the given metadata on the draft created by the mutating adapter.  Will call
 * onResult callback once with data or error.
 *
 * This function returns an error if the given adapterId cannot be found, or if the
 * adapterId is not a mutating adapter, or if a draft isn't created, or if it
 * fails to parse the given config string.
 */
function invokeAdapterWithMetadata(adapterId, config, metadata, onResponse, nativeAdapterRequestContext) {
    const adapter = getDMLAdapterFromName(adapterId);
    if (adapter === undefined) {
        // This check is here for legacy purpose
        // So the consumers still get the same errors
        if (getImperativeAdapterFromName(imperativeAdapterKeyBuilder(adapterId)) !== undefined) {
            onResponse({
                data: undefined,
                error: createNativeFetchErrorResponse(NON_MUTATING_ADAPTER_MESSAGE),
            });
            return;
        }
        onResponse({
            data: undefined,
            error: createNativeFetchErrorResponse(`adapter ${adapterId} not recognized`),
        });
        return;
    }
    if (adapterId === 'deleteRecord') {
        draftManager.stopQueueWhileRunning(() => {
            return new Promise((resolve) => {
                const onResponseWrapper = (response) => {
                    resolve(response);
                };
                invokeAdapterWithMetadataDeleteRecord(adapter, config, metadata, onResponseWrapper, nativeAdapterRequestContext);
            }).then((callbackValue) => {
                onResponse(callbackValue);
            });
        });
    }
    else {
        draftManager.stopQueueWhileRunning(() => {
            return new Promise((resolve) => {
                invokeDmlAdapter(adapter, parse(config), async (responseValue) => {
                    const draftIds = draftIdsForResponseValue(responseValue);
                    if (responseValue.error === undefined &&
                        draftIds !== undefined &&
                        draftIds.length > 0) {
                        const draftId = draftIds[draftIds.length - 1];
                        const managerState = await draftManager.getQueue();
                        const draftItem = managerState.items.find((x) => x.id === draftId);
                        if (draftItem === undefined) {
                            // draftItem no longer exists, might have already been uploaded
                            ldsMobileInstrumentation$1.log('tried to set metadata on draft item that no longer exists');
                            resolve(responseValue);
                            return;
                        }
                        draftManager
                            .setMetadata(draftId, { ...draftItem.metadata, ...metadata })
                            .then(() => {
                            resolve(responseValue);
                        })
                            .catch((error) => {
                            resolve(convertErrorIntoNativeFetchError(error, 'Unknown error setting metadata'));
                        });
                    }
                    else {
                        let response = responseValue;
                        response.error =
                            createNativeFetchErrorResponse(NO_DRAFT_CREATED_MESSAGE);
                        resolve(response);
                    }
                }, nativeAdapterRequestContext);
            }).then((callbackValue) => {
                onResponse(callbackValue);
            });
        });
    }
}
/*
//TODO W-10284305: Remove this function in 238
This is a special case version of the invokeAdapterWithMetadata function
which should only be used for the deleteRecord wire adapter, since it does not
contain record data in the result and has to do special querying of the draft queue
*/
function invokeAdapterWithMetadataDeleteRecord(adapter, config, metadata, onResponse, nativeAdapterRequestContext) {
    const targetedRecordId = parse(config);
    let priorDraftIds;
    draftManager.getQueue().then((draftState) => {
        priorDraftIds = draftState.items.map((item) => {
            return item.id;
        });
        invokeDmlAdapter(adapter, parse(config), (responseValue) => {
            if (responseValue.error === undefined &&
                responseValue.errors === undefined &&
                responseValue.data === undefined) {
                draftManager.getQueue().then((newState) => {
                    const draftIdsToFilter = priorDraftIds ? priorDraftIds : [];
                    const newDrafts = newState.items;
                    const addedDrafts = newDrafts.filter((item) => {
                        const isNew = draftIdsToFilter.indexOf(item.id) < 0;
                        const targetIdMatches = item.targetId === targetedRecordId;
                        return isNew && targetIdMatches;
                    });
                    if (addedDrafts.length !== 1) {
                        let response = {
                            error: createNativeFetchErrorResponse(NO_DRAFT_CREATED_MESSAGE),
                        };
                        onResponse(response);
                    }
                    else {
                        draftManager
                            .setMetadata(addedDrafts[0].id, metadata)
                            .then(() => {
                            onResponse(responseValue);
                        })
                            .catch((error) => {
                            onResponse(convertErrorIntoNativeFetchError(error, 'Unknown error setting metadata'));
                        });
                    }
                });
            }
            else {
                let response = responseValue;
                response.error = createNativeFetchErrorResponse(NO_DRAFT_CREATED_MESSAGE);
                onResponse(response);
            }
        }, nativeAdapterRequestContext);
    });
}
/*
//TODO W-10284305: Remove this function in 238
This is a special case version of the invokeAdapterWithDraftToMerge function
which should only be used for the deleteRecord wire adapter, since it does not
contain record data in the result and has to do special querying of the draft queue
*/
function invokeAdapterWithDraftToMergeDeleteRecord(adapter, config, draftIdToReplace, onResponse, nativeAdapterRequestContext) {
    const targetedRecordId = parse(config);
    let priorDraftIds;
    draftManager.getQueue().then((draftState) => {
        priorDraftIds = draftState.items.map((item) => {
            return item.id;
        });
        invokeDmlAdapter(adapter, parse(config), (responseValue) => {
            if (responseValue.error === undefined &&
                responseValue.errors === undefined &&
                responseValue.data === undefined) {
                draftManager.getQueue().then((newState) => {
                    const draftIdsToFilter = priorDraftIds ? priorDraftIds : [];
                    const newDrafts = newState.items;
                    const addedDrafts = newDrafts.filter((item) => {
                        const isNew = draftIdsToFilter.indexOf(item.id) < 0;
                        const targetIdMatches = item.targetId === targetedRecordId;
                        return isNew && targetIdMatches;
                    });
                    if (addedDrafts.length !== 1) {
                        let response = {
                            error: createNativeFetchErrorResponse(NO_DRAFT_CREATED_MESSAGE),
                        };
                        onResponse(response);
                    }
                    else {
                        draftManager
                            .replaceAction(draftIdToReplace, addedDrafts[0].id)
                            .then(() => {
                            onResponse(responseValue);
                        })
                            .catch((error) => {
                            onResponse(convertErrorIntoNativeFetchError(error, 'Unknown error replacing action'));
                        });
                    }
                });
            }
            else {
                let response = responseValue;
                response.error = createNativeFetchErrorResponse(NO_DRAFT_CREATED_MESSAGE);
                onResponse(response);
            }
        }, nativeAdapterRequestContext);
    });
}
function removeDraftNodeActionId(targetDraftId, responseValue) {
    const recordDraftsNode = {
        ...responseValue.data.drafts,
        draftActionIds: responseValue.data.drafts.draftActionIds.filter((x) => x !== targetDraftId),
    };
    return { ...responseValue, data: { ...responseValue.data, drafts: recordDraftsNode } };
}
function convertErrorIntoNativeFetchError(error, defaultMessage) {
    let message = defaultMessage;
    if (error instanceof Error) {
        message = error.message;
    }
    else if (typeof error === 'string') {
        message = error;
    }
    return {
        error: createNativeFetchErrorResponse(message),
    };
}
function draftIdsForResponseValue(response) {
    if (response.data !== undefined &&
        response.data.drafts !== undefined &&
        response.data.drafts.draftActionIds !== undefined) {
        return response.data.drafts.draftActionIds;
    }
    if (response.data !== undefined &&
        response.data.contentVersion !== undefined &&
        response.data.contentVersion.drafts !== undefined &&
        response.data.contentVersion.drafts.draftActionIds !== undefined) {
        return response.data.contentVersion.drafts.draftActionIds;
    }
    if (response.data !== undefined &&
        response.data.drafts !== undefined &&
        response.data.drafts.draftActionId !== undefined) {
        return [response.data.drafts.draftActionId];
    }
    return undefined;
}
/**
 * Executes the specified adapter with the given adapterId and config.  Will call
 * onResponse callback once with data or error.
 *
 * This function returns an error if the given adapterId cannot be found or if it
 * fails to parse the given config string.
 */
function invokeAdapter(adapterId, config, onResponse, nativeAdapterRequestContext) {
    const onResponseDelegate = (value) => {
        onResponse(buildNativeCallbackValue(value));
    };
    const imperativeAdapterIdentifier = imperativeAdapterKeyBuilder(adapterId);
    const imperativeAdapter = getImperativeAdapterFromName(imperativeAdapterIdentifier);
    const configObject = parse(config);
    // currently all uiapi GET adapters have a corresponding imperative adapter
    if (imperativeAdapter !== undefined) {
        // Check if it's graphQl adapter
        // Parse the query in that case
        const isGql = isGqlAdapter(imperativeAdapterIdentifier);
        if (isGql) {
            try {
                // gql config needs gql query string turned into AST object
                configObject.query = parseAndVisit(configObject.query);
            }
            catch (err) {
                const parseError = normalizeError(err);
                reportGraphqlQueryParseError(parseError);
                // call the callback with error
                onResponseDelegate({
                    data: undefined,
                    error: createNativeFetchErrorResponse(parseError.message),
                });
                return;
            }
        }
        //unstable_graphql_imperative
        if (imperativeAdapterIdentifier.includes('graphql')) {
            try {
                //we need to use the gql literal to convert into a document node
                //in order to get through the gql imperative adapter
                configObject.query = gql `
                    ${configObject.query}
                `;
            }
            catch (err) {
                const parseError = normalizeError(err);
                reportGraphqlQueryParseError(parseError);
                // call the callback with error
                onResponseDelegate({
                    data: undefined,
                    error: createNativeFetchErrorResponse(parseError.message),
                });
                return;
            }
        }
        try {
            imperativeAdapter.invoke(configObject, buildAdapterRequestContext(nativeAdapterRequestContext), onResponseDelegate);
        }
        catch (err) {
            if (isGql) {
                reportGraphqlQueryParseError(err);
            }
            onResponseDelegate(buildInvalidConfigError(err));
        }
        return;
    }
    const adapter = getDMLAdapterFromName(adapterId);
    if (adapter === undefined) {
        onResponseDelegate({
            data: undefined,
            error: createNativeFetchErrorResponse(`adapter ${adapterId} not recognized`),
        });
        return;
    }
    invokeDmlAdapter(adapter, configObject, onResponseDelegate, nativeAdapterRequestContext);
}
/**
 * @deprecated Use invokeAdapter or subscribeToAdapter instead
 *
 * W-9173084 Will remove this
 */
function executeAdapter(adapterId, config, onSnapshot, nativeAdapterRequestContext) {
    return subscribeToAdapter(adapterId, config, onSnapshot, nativeAdapterRequestContext);
}
/**
 * @deprecated Use invokeAdapter instead
 *
 * W-9173084 Will remove this
 */
function executeMutatingAdapter(adapterId, config, onResult, nativeAdapterRequestContext) {
    invokeAdapter(adapterId, config, onResult, nativeAdapterRequestContext);
}

/* istanbul ignore file */
// TODO [W-9523236]: add test coverage for this file and remove above comment
// A allowlist of methods that we allow to be proxied from another LDS instance
const allowList = ['enqueue', 'getQueueActions'];
/**
 * Implements the DraftQueue interface from nimbus-plugin-lds by passing requests
 * to the instance of the lds-drafts' DraftQueue implementation
 */
const nimbusDraftQueue = {
    callProxyMethod(methodName, serializedArgsArray, resultCallback, 
    // NOTE: the other side of the proxy is expecting an error message string
    // (not a JSON serialized Error object)
    errorCallback) {
        const method = draftQueue[methodName];
        if (method === undefined) {
            return errorCallback('Method does not exist on the draft queue');
        }
        if (allowList.includes(methodName) === false) {
            return errorCallback(`Method ${methodName} is not available for proxy invocation`);
        }
        const parsedArgs = parse(serializedArgsArray);
        // TODO [W-9933226]: we should validate the argument list based on which method is being called
        if (isArray(parsedArgs) === false) {
            return errorCallback('expected array argument list');
        }
        let methodResult = undefined;
        try {
            if (parsedArgs === undefined) {
                methodResult = method.call(draftQueue);
            }
            else {
                methodResult = method.call(draftQueue, ...parsedArgs);
            }
        }
        catch (err) {
            return errorCallback(normalizeError(err).message);
        }
        if (methodResult.then === undefined) {
            return resultCallback(stringify(methodResult));
        }
        methodResult
            .then((result) => {
            resultCallback(stringify(result));
        })
            .catch((err) => {
            errorCallback(normalizeError(err).message);
        });
    },
};

let luvio = undefined;
let internalLuvio = undefined;
let uiApiRecordTTL;
let metadataTTL;
withDefaultLuvio((_luvio) => {
    luvio = _luvio;
    internalLuvio = getRuntime().internalLuvio;
    if (uiApiRecordTTL !== undefined) {
        // Note: This should rarely get hit and the durable store implementation
        // should have read write synchronization to protect against race conditions
        // Here since we are on a synchronous path we cannot await the Promise
        setUiApiRecordTTL(uiApiRecordTTL);
    }
    if (metadataTTL !== undefined) {
        // Note: This should rarely get hit and the durable store implementation
        // should have read write synchronization to protect against race conditions
        // Here since we are on a synchronous path we cannot await the Promise
        setMetadataTTL(metadataTTL);
    }
});
function setUiApiRecordTTL(ttl) {
    uiApiRecordTTL = ttl;
    if (luvio === undefined) {
        return Promise.resolve();
    }
    return luvio.storeSetTTLOverride(API_NAMESPACE, RECORD_REPRESENTATION_NAME, ttl).then(() => {
        if (internalLuvio === undefined) {
            return Promise.resolve();
        }
        internalLuvio.storeSetTTLOverride(API_NAMESPACE, RECORD_REPRESENTATION_NAME, ttl);
    });
}
function setMetadataTTL(ttl) {
    metadataTTL = ttl;
    if (luvio === undefined) {
        return Promise.resolve();
    }
    return luvio.storeSetDefaultTTLOverride(ttl).then(() => {
        if (internalLuvio === undefined) {
            return Promise.resolve();
        }
        internalLuvio.storeSetDefaultTTLOverride(ttl);
    });
}

function createPrimingSession(config) {
    const { createPrimingSession } = getRuntime();
    const { primingEventHandler, concurrency, batchSize } = config;
    const session = createPrimingSession({ concurrency, batchSize });
    if (primingEventHandler) {
        addEventHandler(session, primingEventHandler);
    }
    const registerEventHandler = (handler) => {
        // bind creates a new function and we need to retain reference to that function
        // in order to be able to remove it
        const boundCallbacks = {
            onError: handler.onError ? handler.onError.bind(handler) : undefined,
            onPrimed: handler.onPrimed ? handler.onPrimed.bind(handler) : undefined,
        };
        addEventHandler(session, boundCallbacks);
        return () => {
            removeEventHandler(session, boundCallbacks);
        };
    };
    return {
        enqueue: session.enqueue.bind(session),
        cancel: session.cancel.bind(session),
        on: session.on.bind(session),
        once: session.once.bind(session),
        off: session.off.bind(session),
        registerEventHandler,
    };
}
function addEventHandler(session, handler) {
    const { onError, onPrimed } = handler;
    if (onError) {
        session.on('error', onError);
    }
    if (onPrimed) {
        session.on('primed', onPrimed);
    }
}
function removeEventHandler(session, handler) {
    const { onError, onPrimed } = handler;
    if (onError) {
        session.off('error', onError);
    }
    if (onPrimed) {
        session.off('primed', onPrimed);
    }
}

const ldsMobileInstrumentation = getInstrumentation(O11Y_NAMESPACE_LDS_MOBILE);
const MessagingDurableSegmentName = 'MESSAGING';
const DEFAULT_MAX_ENTRIES_PER_OPERATION = 500;
const EVICTION_IN_PROGESS_MESSAGE = `Cache eviction in progress. Can't start another until it is finished or canceled.`;
/**
 * Only one eviction is allowed at a time. running status is return when evictCacheRecordsByIds or
 * evictExpiredCacheEntries is called if there's already an eviction in progress.
 */
var activeEvictionInProgress = false;
var cancelCurrentEviction = false;
/**
 * Purging records specified by an array of record id from durable store
 * @param recordIds record id array
 * @onProgressUpdate call back to report evict progress
 *
 * @returns started or already running based whether there's an active eviction going on or not.
 *
 * The record will not be purged if the record has draft.
 */
function evictCacheRecordsByIds(recordIds, onProgressUpdate) {
    // Send error back if an eviction is going on.
    if (activeEvictionInProgress) {
        return {
            status: 'Running',
            message: EVICTION_IN_PROGESS_MESSAGE,
        };
    }
    activeEvictionInProgress = true;
    cancelCurrentEviction = false;
    const o11yActivity = ldsMobileInstrumentation.startActivity('evictCacheRecordsByIds');
    const stat = {
        idCount: recordIds.length,
        evictedCount: 0,
        skippedCount: 0,
    };
    const evictAChunk = () => {
        evictChunksOfRecord(recordIds).then((progress) => {
            handleInstrumentation(o11yActivity, stat, progress);
            onEvicted(progress);
        });
    };
    const onEvicted = getOnEvictedCallback(o11yActivity, onProgressUpdate, evictAChunk, stat);
    evictAChunk();
    return { status: 'Started' };
}
/**
 * Purging the db entries which passed expired time by specified days
 * from durable store.
 * @param expiredByDays
 * @param onProgressUpdate
 *
 * @returns started or already running based whether there's an active eviction going on or not.
 *
 * Important: records with draft, objectInfo entries will not be purged even expired by the specified days.
 */
function evictExpiredCacheEntries(expiredByDays, onProgressUpdate) {
    // Send error back if an eviction is going on.
    if (activeEvictionInProgress) {
        return {
            status: 'Running',
            message: EVICTION_IN_PROGESS_MESSAGE,
        };
    }
    activeEvictionInProgress = true;
    cancelCurrentEviction = false;
    const stat = {
        expiredInDays: expiredByDays,
        evictedCount: 0,
        skippedCount: 0,
    };
    const o11yActivity = ldsMobileInstrumentation.startActivity('evictExpiredCacheEntries');
    const overdueExpirationTimeStamp = Date.now() - expiredByDays * 24 * 3600 * 1000;
    const evictAChunk = () => {
        evictChunkOfOverdueEntries(overdueExpirationTimeStamp).then((progress) => {
            handleInstrumentation(o11yActivity, stat, progress);
            onEvicted(progress);
        });
    };
    const onEvicted = getOnEvictedCallback(o11yActivity, onProgressUpdate, evictAChunk, stat);
    evictAChunk();
    return { status: 'Started' };
}
/**
 * Signal to stop current eviction if there's an active eviction going on.
 */
function stopEviction() {
    if (activeEvictionInProgress) {
        cancelCurrentEviction = true;
    }
}
/**
 * Get a callback function when a chunk of records or entries are removed.
 * @param onProgressUpdate
 * @param evictAChunk
 * @returns a callback to call when a chunk of records or entries are removed.
 */
function getOnEvictedCallback(o11yActivity, onProgressUpdate, evictAChunk, stat) {
    return (progress) => {
        onProgressUpdate(progress);
        const { status } = progress;
        if (status === 'Succeeded' || status === 'Error') {
            activeEvictionInProgress = false;
        }
        if (status === 'Evicted') {
            if (cancelCurrentEviction) {
                activeEvictionInProgress = false;
                const canceledProgress = { status: 'Canceled' };
                onProgressUpdate(canceledProgress);
                handleInstrumentation(o11yActivity, stat, canceledProgress);
            }
            else {
                evictAChunk();
            }
        }
    };
}
/**
 * pull a chunk of record id, evict status back in a promise.
 * @param ids the list of record ids.
 * @returns
 */
function evictChunksOfRecord(ids) {
    const chunk = ids.splice(0, DEFAULT_MAX_ENTRIES_PER_OPERATION);
    if (chunk.length === 0) {
        return Promise.resolve({ status: 'Succeeded' });
    }
    return new Promise((resolve) => {
        // evict the chunk
        const { luvio, nimbusSqliteStore } = getRuntime();
        const paramTemplate = chunk.map((_) => '?').join(',');
        const params = chunk.map((recordId) => keyBuilderRecord(luvio, { recordId }));
        try {
            nimbusSqliteStore
                .query(`DELETE FROM lds_data
                    WHERE key IN (${paramTemplate}) AND JSON_EXTRACT(data,'$.drafts') IS NULL
                    RETURNING key
                `, params)
                .then((result) => {
                const evictedEntries = result.rows.map((row) => row[0]);
                const skippedEntries = params.filter((entryKey) => evictedEntries.indexOf(entryKey) === -1);
                // broadcast entries evicted if some entries are removed
                if (evictedEntries.length > 0) {
                    nimbusSqliteStore.setEntries({ notifyStoreUpdateAvailable: { data: evictedEntries } }, MessagingDurableSegmentName);
                }
                resolve({
                    status: 'Evicted',
                    evictedEntries,
                    skippedEntries,
                });
            })
                .catch((reason) => {
                resolve({
                    status: 'Error',
                    message: reason.toString(),
                });
            });
        }
        catch (reason) {
            resolve({
                status: 'Error',
                message: reason.toString(),
            });
        }
    });
}
/**
 * Evict chunk of overdue entries from db.
 * @param overdueExpirationTimestamp
 * @returns
 */
function evictChunkOfOverdueEntries(overdueExpirationTimestamp) {
    return new Promise((resolve) => {
        // evict the chunk
        const { nimbusSqliteStore } = getRuntime();
        try {
            nimbusSqliteStore
                .query(`DELETE FROM lds_data
                        WHERE key IN (
                        SELECT key
                        FROM lds_data
                        WHERE key NOT LIKE 'UiApi::ObjectInfoRepresentation:%'
                            AND
                            (
                                key NOT LIKE 'UiApi::RecordRepresentation:%' AND
                                JSON_EXTRACT(metadata, '$.expirationTimestamp') < ${overdueExpirationTimestamp}
                                OR
                                key LIKE 'UiApi::RecordRepresentation:%' AND
                                JSON_EXTRACT(data, '$.drafts') IS NULL AND
                                JSON_EXTRACT(metadata, '$.expirationTimestamp') < ${overdueExpirationTimestamp}
                            )
                        LIMIT ${DEFAULT_MAX_ENTRIES_PER_OPERATION}
                        )
                    RETURNING key`, [])
                .then((result) => {
                const evictedEntries = result.rows.map((row) => row[0]);
                if (evictedEntries.length > 0) {
                    // broadcast entries evicted
                    nimbusSqliteStore.setEntries({ notifyStoreUpdateAvailable: { data: evictedEntries } }, MessagingDurableSegmentName);
                    resolve({
                        status: 'Evicted',
                        evictedEntries,
                        skippedEntries: [],
                    });
                }
                else {
                    resolve({ status: 'Succeeded' });
                }
            })
                .catch((reason) => {
                resolve({
                    status: 'Error',
                    message: reason.toString(),
                });
            });
        }
        catch (reason) {
            resolve({
                status: 'Error',
                message: reason.toString(),
            });
        }
    });
}
function handleInstrumentation(activity, stat, progress) {
    const status = progress.status;
    if (status === 'Evicted') {
        const evicted = progress;
        stat.evictedCount += evicted.evictedEntries.length;
        stat.skippedCount += evicted.skippedEntries.length;
        return;
    }
    const schema = 'idCount' in stat ? evictCacheRecordsByIdsSchema : evictExpiredEntriesSchema;
    const userData = {
        ...stat,
        isCanceled: status === 'Canceled',
    };
    if (status === 'Error') {
        activity.error(progress.message);
    }
    activity.stop(schema, userData);
}

const LUVIO_ADAPTER_FAMILY = /^force\/ldsAdapters/;
async function importLuvioAdapterModule(specifier) {
    if (!LUVIO_ADAPTER_FAMILY.test(specifier)) {
        throw `${specifier} is not an allowed luvio adapter module`;
    }
    let module;
    try {
        module = await import(specifier);
    }
    catch {
        throw `module not found for specifier ${specifier}`;
    }
    return new LuvioAdapterModule(module);
}
class LuvioAdapterModule {
    constructor(module) {
        const map = Object.assign({}, module);
        const { dmlAdapterMap, imperativeAdapterMap } = buildAdapterMaps(map);
        this.imperativeAdapterMap = imperativeAdapterMap;
        this.dmlAdapterMap = dmlAdapterMap;
    }
    async invokeAdapter(adapterName, config, nativeAdapterRequestContext) {
        const imperativeAdapterIdentifier = `${adapterName}_imperative`;
        const imperativeAdapter = this.imperativeAdapterMap[imperativeAdapterIdentifier];
        if (imperativeAdapter === undefined) {
            const dmlAdapter = this.dmlAdapterMap[adapterName];
            if (dmlAdapter === undefined) {
                return {
                    data: undefined,
                    error: createNativeFetchErrorResponse(`adapter ${adapterName} not recognized`),
                };
            }
            return this.invokeDmlAdapter(dmlAdapter, config, nativeAdapterRequestContext);
        }
        return new Promise((resolve) => {
            try {
                imperativeAdapter.invoke(config, buildAdapterRequestContext(nativeAdapterRequestContext), (response) => {
                    resolve(buildNativeCallbackValue(response));
                });
            }
            catch (error) {
                resolve(buildInvalidConfigError(error));
            }
        });
    }
    subscribeAdapter(adapterName, config, nativeAdapterRequestContext, callback) {
        const imperativeAdapterIdentifier = `${adapterName}_imperative`;
        const imperativeAdapter = this.imperativeAdapterMap[imperativeAdapterIdentifier];
        if (imperativeAdapter === undefined) {
            const dmlAdapter = this.dmlAdapterMap[adapterName];
            if (dmlAdapter === undefined) {
                const nativeValue = buildNativeCallbackValue({
                    data: undefined,
                    error: createNativeFetchErrorResponse(`adapter ${adapterName} not recognized`),
                });
                callback(nativeValue);
                return () => { };
            }
            const nativeValue = buildNativeCallbackValue({
                data: undefined,
                error: createNativeFetchErrorResponse(`${adapterName} is not a GET wire adapter and cannot be subscribed.`),
            });
            callback(nativeValue);
            return () => { };
        }
        try {
            return imperativeAdapter.subscribe(config, buildAdapterRequestContext(nativeAdapterRequestContext), callback);
        }
        catch (error) {
            callback(buildInvalidConfigError(error));
            return () => { };
        }
    }
    invokeDmlAdapter(adapter, config, nativeAdapterRequestContext) {
        return new Promise((resolve) => {
            try {
                adapter(config, buildAdapterRequestContext(nativeAdapterRequestContext)).then((data) => {
                    resolve({ data, error: undefined });
                }, (error) => {
                    resolve({ data: undefined, error });
                });
            }
            catch (err) {
                resolve(buildInvalidConfigError(err));
            }
        });
    }
}

const ONESTORE_ADAPTER_FAMILY = /^force\/ldsAdapters/;
function buildOneStoreError(error) {
    let errorMessage;
    if (typeof error === 'string') {
        errorMessage = error;
    }
    else if (error && error.message) {
        errorMessage = error.message;
    }
    else if (error !== null && error !== undefined) {
        errorMessage = error.toString();
    }
    else {
        errorMessage = error;
    }
    return {
        data: undefined,
        error: errorMessage,
    };
}
function buildInvalidAdapterError(adapterOrCommandName) {
    return {
        data: undefined,
        error: `Invalid adapter or command: ${adapterOrCommandName}`,
    };
}
function buildNativeResponse(response) {
    if (response && response.error) {
        return buildOneStoreError(response.error);
    }
    return response;
}
async function importOneStoreAdapterModule(specifier) {
    if (!ONESTORE_ADAPTER_FAMILY.test(specifier)) {
        throw `${specifier} is not an allowed onestore adapter module`;
    }
    let module;
    try {
        module = await import(specifier);
    }
    catch {
        throw `module not found for specifier ${specifier}`;
    }
    return new OneStoreAdapterModule(module);
}
class OneStoreAdapterModule {
    constructor(module) {
        this.module = module;
    }
    async invoke(adapterOrCommandName, config) {
        const adapterOrCommand = this.module[adapterOrCommandName];
        if (adapterOrCommand === undefined) {
            return buildInvalidAdapterError(`Command ${adapterOrCommandName} not recognized`);
        }
        return new Promise((resolve) => {
            try {
                if (this.isCommandWireAdapter(adapterOrCommand)) {
                    // WIRE COMMAND
                    const adapter = new adapterOrCommand((result) => {
                        resolve(buildNativeResponse(result));
                        adapter.disconnect();
                    }, undefined, { skipEmptyEmit: true });
                    adapter.connect();
                    adapter.update(config);
                }
                else if (this.hasInvoke(adapterOrCommand)) {
                    // IMPERATIVE LEGACY INVOKER
                    adapterOrCommand.invoke(config, null, (result) => {
                        resolve(buildNativeResponse(result));
                    });
                }
                else {
                    // IMPERATIVE INVOKER
                    adapterOrCommand(config)
                        .then((response) => {
                        resolve(buildNativeResponse(response));
                    })
                        .catch((error) => {
                        resolve(buildOneStoreError(error));
                    });
                }
            }
            catch (error) {
                resolve(buildInvalidAdapterError(adapterOrCommandName));
            }
        });
    }
    subscribe(adapterOrCommandName, config, callback) {
        const adapterOrCommand = this.module[adapterOrCommandName];
        if (adapterOrCommand === undefined) {
            callback(buildInvalidAdapterError(adapterOrCommandName));
            return Promise.resolve(undefined);
        }
        return new Promise((resolve) => {
            try {
                if (this.isCommandWireAdapter(adapterOrCommand)) {
                    // WIRE COMMAND
                    const adapter = new adapterOrCommand((result) => {
                        callback(buildNativeResponse(result));
                    }, undefined, { skipEmptyEmit: true });
                    adapter.connect();
                    adapter.update(config);
                    resolve(() => adapter.disconnect());
                }
                else if (this.hasSubscribe(adapterOrCommand)) {
                    // IMPERATIVE LEGACY INVOKER
                    const unsubscribe = adapterOrCommand.subscribe(config, null, (result) => {
                        callback(buildNativeResponse(result));
                    });
                    resolve(unsubscribe);
                }
                else {
                    // IMPERATIVE INVOKER
                    adapterOrCommand(config)
                        .then((response) => {
                        if (response.subscribe === undefined) {
                            callback(buildNativeResponse(response));
                            resolve(undefined);
                        }
                        else {
                            const unsubscribe = response.subscribe((subscriptionResult) => {
                                callback(buildNativeResponse(subscriptionResult));
                            });
                            resolve(unsubscribe);
                        }
                    })
                        .catch((error) => {
                        callback(buildOneStoreError(error));
                        resolve(undefined);
                    });
                }
            }
            catch (error) {
                callback(buildInvalidAdapterError(adapterOrCommandName));
                resolve(undefined);
            }
        });
    }
    isCommandWireAdapter(adapterOrCommand) {
        return Object.getPrototypeOf(adapterOrCommand).name === 'CommandWireAdapterConstructor';
    }
    hasInvoke(adapterOrCommand) {
        return typeof adapterOrCommand.invoke === 'function';
    }
    hasSubscribe(adapterOrCommand) {
        return typeof adapterOrCommand.subscribe === 'function';
    }
}

if (process.env.NODE_ENV !== 'production') {
    // eslint-disable-next-line no-undef
    withDefaultLuvio((luvio) => {
        // eslint-disable-next-line no-undef
        const global = typeof globalThis === 'undefined' ? {} : globalThis;
        global.luvio = luvio;
    });
}

export { createPrimingSession, draftManager, draftQueue, evictCacheRecordsByIds, evictExpiredCacheEntries, executeAdapter, executeMutatingAdapter, getImperativeAdapterNames, importLuvioAdapterModule, importOneStoreAdapterModule, invokeAdapter, invokeAdapterWithDraftToMerge, invokeAdapterWithDraftToReplace, invokeAdapterWithMetadata, nimbusDraftQueue, setMetadataTTL, setUiApiRecordTTL, stopEviction, subscribeToAdapter };
// version: 1.430.0-6adb3245d5
