import { WeakCache } from "@wry/caches";
import { wrap } from "optimism";
import { Observable } from "rxjs";
import { maskFragment } from "@apollo/client/masking";
import { cacheSizes } from "@apollo/client/utilities";
import { __DEV__ } from "@apollo/client/utilities/environment";
import { getApolloCacheMemoryInternals, getFragmentDefinition, getFragmentQueryDocument, } from "@apollo/client/utilities/internal";
import { invariant } from "@apollo/client/utilities/invariant";
import { equalByQuery } from "../../core/equalByQuery.js";
export class ApolloCache {
    assumeImmutableResults = false;
    // Function used to lookup a fragment when a fragment definition is not part
    // of the GraphQL document. This is useful for caches, such as InMemoryCache,
    // that register fragments ahead of time so they can be referenced by name.
    lookupFragment(fragmentName) {
        return null;
    }
    // Transactional API
    // The batch method is intended to replace/subsume both performTransaction
    // and recordOptimisticTransaction, but performTransaction came first, so we
    // provide a default batch implementation that's just another way of calling
    // performTransaction. Subclasses of ApolloCache (such as InMemoryCache) can
    // override the batch method to do more interesting things with its options.
    batch(options) {
        const optimisticId = typeof options.optimistic === "string" ? options.optimistic
            : options.optimistic === false ? null
                : void 0;
        let updateResult;
        this.performTransaction(() => (updateResult = options.update(this)), optimisticId);
        return updateResult;
    }
    recordOptimisticTransaction(transaction, optimisticId) {
        this.performTransaction(transaction, optimisticId);
    }
    // Optional API
    // Called once per input document, allowing the cache to make static changes
    // to the query, such as adding __typename fields.
    transformDocument(document) {
        return document;
    }
    // Called before each ApolloLink request, allowing the cache to make dynamic
    // changes to the query, such as filling in missing fragment definitions.
    transformForLink(document) {
        return document;
    }
    identify(object) {
        return;
    }
    gc() {
        return [];
    }
    modify(options) {
        return false;
    }
    // DataProxy API
    readQuery(options, optimistic = !!options.optimistic) {
        return this.read({
            ...options,
            rootId: options.id || "ROOT_QUERY",
            optimistic,
        });
    }
    /**
    * Watches the cache store of the fragment according to the options specified and returns an `Observable`. We can subscribe to this `Observable` and receive updated results through an observer when the cache store changes.
    * 
    * You must pass in a GraphQL document with a single fragment or a document with multiple fragments that represent what you are reading. If you pass in a document with multiple fragments then you must also specify a `fragmentName`.
    * 
    * @param options - An object of type `WatchFragmentOptions` that allows the cache to identify the fragment and optionally specify whether to react to optimistic updates.
    * 
    * @since
    * 
    * 3.10.0
    */
    watchFragment(options) {
        const { fragment, fragmentName, from, optimistic = true, ...otherOptions } = options;
        const query = this.getFragmentDoc(fragment, fragmentName);
        // While our TypeScript types do not allow for `undefined` as a valid
        // `from`, its possible `useFragment` gives us an `undefined` since it
        // calls` cache.identify` and provides that value to `from`. We are
        // adding this fix here however to ensure those using plain JavaScript
        // and using `cache.identify` themselves will avoid seeing the obscure
        // warning.
        const id = typeof from === "undefined" || typeof from === "string" ?
            from
            : this.identify(from);
        const dataMasking = !!options[Symbol.for("apollo.dataMasking")];
        if (__DEV__) {
            const actualFragmentName = fragmentName || getFragmentDefinition(fragment).name.value;
            if (!id) {
                __DEV__ && invariant.warn(110, actualFragmentName);
            }
        }
        const diffOptions = {
            ...otherOptions,
            returnPartialData: true,
            id,
            query,
            optimistic,
        };
        let latestDiff;
        return new Observable((observer) => {
            return this.watch({
                ...diffOptions,
                immediate: true,
                callback: (diff) => {
                    let data = dataMasking ?
                        maskFragment(diff.result, fragment, this, fragmentName)
                        : diff.result;
                    // TODO: Remove this once `watchFragment` supports `null` as valid
                    // value emitted
                    if (data === null) {
                        data = {};
                    }
                    if (
                    // Always ensure we deliver the first result
                    latestDiff &&
                        equalByQuery(query, { data: latestDiff.result }, { data }, 
                        // TODO: Fix the type on WatchFragmentOptions so that TVars
                        // extends OperationVariables
                        options.variables)) {
                        return;
                    }
                    const result = {
                        data,
                        complete: !!diff.complete,
                    };
                    if (diff.missing) {
                        result.missing = diff.missing.missing;
                    }
                    latestDiff = { ...diff, result: data };
                    observer.next(result);
                },
            });
        });
    }
    // Make sure we compute the same (===) fragment query document every
    // time we receive the same fragment in readFragment.
    getFragmentDoc = wrap(getFragmentQueryDocument, {
        max: cacheSizes["cache.fragmentQueryDocuments"] ||
            1000 /* defaultCacheSizes["cache.fragmentQueryDocuments"] */,
        cache: WeakCache,
    });
    readFragment(options, optimistic = !!options.optimistic) {
        return this.read({
            ...options,
            query: this.getFragmentDoc(options.fragment, options.fragmentName),
            rootId: options.id,
            optimistic,
        });
    }
    writeQuery({ id, data, ...options }) {
        return this.write(Object.assign(options, {
            dataId: id || "ROOT_QUERY",
            result: data,
        }));
    }
    writeFragment({ id, data, fragment, fragmentName, ...options }) {
        return this.write(Object.assign(options, {
            query: this.getFragmentDoc(fragment, fragmentName),
            dataId: id,
            result: data,
        }));
    }
    updateQuery(options, update) {
        return this.batch({
            update(cache) {
                const value = cache.readQuery(options);
                const data = update(value);
                if (data === void 0 || data === null)
                    return value;
                cache.writeQuery({ ...options, data });
                return data;
            },
        });
    }
    updateFragment(options, update) {
        return this.batch({
            update(cache) {
                const value = cache.readFragment(options);
                const data = update(value);
                if (data === void 0 || data === null)
                    return value;
                cache.writeFragment({ ...options, data });
                return data;
            },
        });
    }
    /**
    * @experimental
    * @internal
    * This is not a stable API - it is used in development builds to expose
    * information to the DevTools.
    * Use at your own risk!
    * 
    * @deprecated This is an internal API and should not be used directly. This can be removed or changed at any time.
    */
    getMemoryInternals;
}
if (__DEV__) {
    ApolloCache.prototype.getMemoryInternals = getApolloCacheMemoryInternals;
}
//# sourceMappingURL=cache.js.map
